invokeOnCompletionHandler cause is null despite the exception - android

I'm handling the exception within the CoroutineExceptionHandler
It's a bit of contradiction, variables in the debugger are not null, showing a cause and message but the code behaves as if cause is null and navigates away.
I have attached the debugger screenshot
As per the documentation
Cause is null when the job has completed normally.
lifecycleScope.launch(CoroutineExceptionHandler { _, e ->
println(e) // works
}) {
val user = viewModel.findById(userId) // throws an exception
}.invokeOnCompletion {
if (it?.cause == null) // debug breakpoint shows the cause not null
findNavController().navigate(R.id.action_userForm_to_userList) // this still executes
}
Screenshot
first two watches clearly demonstrates cause as null but then the 4th watch it is not null.

Related

List find method finds nothing, but let is called (?!)

i have the following code:
myList.find { it.code == item.bin }.let {
// 1
} ?: run {
// 2
}
I would expect that, if item is found, I enter block 1 , otherwise block 2 ;
But instead I enter block 1 in all cases, and if nothing is found , it is null
Android studio seems aware of this, as the block 2 is grey (it is code never called), but I can't figure why
Please may someone explain why ?
null is still a value so it makes sense that the first block is run. it'd be the same as if you ran null.let { println("hey!") }.
you probably want to run let with a null check: myList.find { it.code == item.bin }?.let { ... }. this way the block will only run if there is indeed a value being returned that is not null.
You are using classic dot call operator ., this operator is not allowed on nullable types. If you want to call this operator on nullable type insert !! before operator, but if you call it on null it throws NullPointerException
You have to use Kotlins safe call operator ?., which call method when insatce is not null and when is it returns null.
?: operator is called Elvis operator and it returns first value if it is not null, else it returns second value.
So just change in your code dot operator . to safe call operator ?.:
myList.find { it.code == item.bin }.let {
// 1
} ?: run {
// 2
}

Compose Snackbar not appearing on repeated error

I am new to jetpack compose and is trying to show an error snackbar whenever the error message I am observing is not null.
Scaffold(scaffoldState = scaffoldState) {
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
//some ui components inside here
}
}
The issue in the above code is that, the first time the error message changes from null to a particular message it appears fine. However on a repeated user action that produces the same error message it's not coming again.
P.S - I know this is happening due to placing the errorMessage as key inside the LaunchedEffect. My doubt is that, is there a different approach to achieve what I want?
This is happening because the LaunchedEffect will run again just in case the errorMessage has changed.
What you can do is:
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
resetErrorMessage() // reset errorMessage
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
The resetErrorMessage must set the errorMessage to null, so the LaunchedEffect will run again, but since you're checking if it is not null, nothing will happen. But as soon you receive a new error message, the LaunchedEffect will be executed again.

Why is my null check unreachable?

In the following example i have an nullable property userId. I would like throw an Exception if it null. Android studio is telling me the code block inside if(userId == null) is unreachable. Can anyone explain why this is unreachable?
return Observable.create<Optional<UserEntity>> { it ->
val userId: String? = firebaseAuth.currentUser?.uid
if(userId == null){
it.onError(throw RuntimeException("Unauthorised"))
it.onComplete()
}else{
//Do something
}
}
Ok... I see... in fact it is the following line that contains the unreachable code:
it.onError(throw RuntimeException("Unauthorised"))
The reason: you throw your exception immediately and not when there occurs an error in processing. In fact the onError itself becomes unreachable.
onError however, needs the exception to throw as passed argument, so what you rather want is something like:
it.onError(RuntimException("Unauthorised"))
i.e. omit the throw.

How to effectively group non fatal exceptions in Crashlytics (Fabrics)?

We are using Crashlytics in our app as the crash reporting tool.
For Android native crashes, it's working fine and grouping the crashes correctly.
Our app also has few components in react-native. For the crashes which occur in these components, we catch them and then log them to Crashlytics as non-fatal exceptions.
public class PlatformNativeModuleCallExceptionhandler implements
NativeModuleCallExceptionHandler {
#Override
public void handleException(Exception e) {
try {
.
.
.
Crashlytics.logException(new Exception(exceptionString));
} catch (Exception ex) {}
}
Crashes are getting logged in Crashlytics dashboard, but it's showing all the crashes inside a single tab. These might be different crashes of the same or different react-native components.
Due to this we are not able to find out the instances of a particular crash. Need to manually go through each instance of the crash.
I guess it takes the name of the class where exception gets created, in this case PlatformNativeModuleCallExceptionHandler.
I tried creating my own custom exception class but that also did not help.
Does anybody know how we can group the non fatal exceptions better here?
All the similar crashes should be grouped together with their total instances.
Crashlytics uses the method and crash line number to group crashes, so if you have an exception handler method for all of your non-fatals, they'll be grouped together. There isn't currently a workaround for this.
Best way I've found to do this is to manually chop the shared parts of the stacktrace off:
private fun buildCrashlyticsSyntheticException(message: String): Exception {
val stackTrace = Thread.currentThread().stackTrace
val numToRemove = 8
val lastToRemove = stackTrace[numToRemove - 1]
// This ensures that if the stacktrace format changes, we get notified immediately by the app
// crashing (as opposed to silently mis-grouping crashes for an entire release).
check(lastToRemove.className == "timber.log.Timber" && lastToRemove.methodName == "e",
{ "Got unexpected stacktrace: $stackTrace" })
val abbreviatedStackTrace = stackTrace.takeLast(stackTrace.size - numToRemove).toTypedArray()
return SyntheticException("Synthetic Exception: $message", abbreviatedStackTrace)
}
class SyntheticException(
message: String,
private val abbreviatedStackTrace: Array<StackTraceElement>
) : Exception(message) {
override fun getStackTrace(): Array<StackTraceElement> {
return abbreviatedStackTrace
}
}
This way the message can be parameterized Timber.e("Got a weird error $error while eating a taco") and all of that line's calls will be grouped together.
Obviously, numToRemove will need to change depending on your exact mechanism for triggering nonfatals.
I resolved this by setting a custom stack trace to the exception. A new Exception(exceptionMessage) will create the exception there itself, what we did was to throw an exception which in catch called my counterpart of handleException() with the actual stack trace furnished in the exceptionMessage. Some parsing and the exceptionMessage can be used to set the stack trace on the newly created exception using exception.setStackTrace(). Actually, this was required in my project only because it is cross-language, for regular projects, simply passing the exception thrown and caught at the place of interest should work.
Crashlytics groups by the line number that the exception was generated on and labels it with the exception type. If you know all the types of the exceptions you can generate each one on a different line. And you could also map your strings to custom Exception types to make it more easy to identify them in Crashlytics.
Here's an example:
public void crashlyticsIsGarbage(String exceptionString) {
Exception exception = null;
switch(exceptionString) {
case "string1": exception = new String1Exception(exceptionString);
case "string2": exception = new String2Exception(exceptionString);
case "string3": exception = new String3Exception(exceptionString);
case "string4": exception = new String4Exception(exceptionString);
default: exception = new Exception(exceptionString);
}
Crashlytics.logException(exception);
}
class String1Exception extends Exception { String1Exception(String exceptionString) { super(exceptionString); } }
class String2Exception extends Exception { String2Exception(String exceptionString) { super(exceptionString); } }
class String3Exception extends Exception { String3Exception(String exceptionString) { super(exceptionString); } }
class String4Exception extends Exception { String4Exception(String exceptionString) { super(exceptionString); } }
BTW, Crashlytics will ignore the message string in the Exception.
I was looking into this just now, because the documentation says:
Logged Exceptions are grouped by Exception type and message.
Warning:
Developers should avoid using unique values, such as user ID, product ID, and timestamps, in the Exception message field. Using unique values in these fields will cause a high cardinality of issues to be created. In this case, Crashlytics will limit the reporting of logged errors in your app. Unique values should instead be added to Logs and Custom Keys.
But my experience was different. From what I found out, what Alexizamerican said in his answer is true, with a small caveat:
Issues are grouped by the method and line where the exception was created, with the caveat that it is the root cause of the exception that is being taken into account here.
By root cause I mean this:
public static Throwable getRootCause(Throwable throwable) {
Throwable cause = throwable;
while (cause.getCause() != null) {
cause = cause.getCause();
}
return cause;
}
Therefore, if you did:
#Override
public void handleException(Exception e) {
// ...
Crashlytics.logException(e);
}
That should correctly group the exceptions together.
Furthermore, if you did:
#Override
public void handleException(Exception e) {
// ...
Crashlytics.logException(new Exception(exceptionString, e));
}
That would also correctly group the exceptions, because it would look at e or its cause, or the cause of that, and so on, until it reaches an exception that doesn't have any other cause, and look at the stack trace where it was created.
Finally, unlike what miguel said, exception type or message doesn't affect grouping at all in my experience. If you have FooException("foo") at some particular line in a particular method, and you replace it with BarException("bar"), the two will be grouped together, because the line and method didn't change.

Is there a function call to enter the debugger?

For example, the flash platform provides the flash.debugger.enterDebugger() API call that does the job:
if (some_cond())
{
...
}
else
{
enterDebugger();
}
In that case, if some_cond() evaluates to false and we're in a debug session (it does nothing if we're in a normal session), then the execution will be halted at the line where enterDebugger is invoked and control given to the debugger, as if a breakpoint was set at that line.
I've looked at the android.os package but found nothing like it. Throwing an exception does the job of giving the control to the debugger, but then the code execution cannot be resumed at the spot where the exception was thrown.
Java debugging supports suspending on exceptions. You could write:
void enterDebugger() {
try {
throw new DebugException();
}
catch (DebugException e) { //no-op
}
}
And setup your IDE to suspend on caught exceptions of type DebugException.
if (someCond()) { .... }
else {
android.os.Debug.waitForDebugger();
}
See android.os.Debug.

Categories

Resources