I am extending AsyncTaskLoader which I use later in a Fragment through LoaderManager. Is there suggested way to handle errors which occur during async task? I would like to popup message asking user to cancel or retry when internet connection error occurs.
Only way which I can think of now is to make Loader to return null on error or wrap loaded object into another which would have status code.
What we did in our last project: Create a Wrapper that holds a generic and an excpetion like this:
public class AsyncTaskResult<T> {
private final T result;
private final Exception error;
//getter & setter
}
Catch all Exceptions in your doInBackground(...) and pack them into the result wrapper (or the result if no error). In your UI check the wrapper if it's an exception, then show according error message, otherwise populate the fields with the result.
For us it was also good practice to define what unique types of exceptions, there are (e.g. exception with a recoverable error where you only show a dialog or an app failure where you need to kick the user to the main menu) and only throw these kinds (on catching the specific in your asynctask), so you don't have to bother with hundreds of different exceptions and also abstract your error handling. You could also provide String keys with the correct I18n error message so you only have to write e.getMessage()
I've seen good results when returning a composite object that contains the payload (if any) and a status code, as you suggested. Then the Fragment that's hosting the AsyncTaskLoader can display an appropriate and informative error. This approach has the added advantage that it uses the built-in loader lifecycle.
Another option is to register a listener that your AsyncTaskLoader will notify when errors occur. The Facebook SDK has an example of using error listeners with loaders.
Related
I have a fragment that asks for some input and then has a submit button. When the submit button on that page is hit it goes to a viewmodel routine.
submitButton.setOnClickListener {
viewModel.submitted(binding)
}
The viewModel.submitted routine checks the sanity of the values entered that are available in the binding value. If the sanity checks fail I want to show an AlertDialog. If everything is sane then viewModel.submitted adds a record to the database. I would like to show an AlertDialog to show a new record was created successfully.
I am following the instructions here https://developer.android.com/guide/topics/ui/dialogs. It says to create a DialogFragment that defines a AlertDialog. I modified the onClickListener lamda so that if there is a sanity check error, ViewModel.submitted throws an exception which is caught and shows an alertDialog.
submitButton.setOnClickListener {
try {
viewModel.submitted(binding)
} catch (e: Exception) {
val alert = AddSymbolDialogFragment()
alert.message = e.message.toString()
alert.show(parentFragmentManager, "Test1")
}
}
This worked for the checks in the viewModel.submitted() routine that were just checking the values in the binding that is passed to it. But in viewModel.submitted() I need to check some of the binding values against values in the database. So, inside viewModel.submitted() I launch a coroutine to get the database info to compare against the binding values. If I throw an exception inside the coroutine the app dies. This makes sense because the setOnClickListener() lambda function has already completed. So, there is nothing to catch it.
I have worked around this by passing the parentFormatManager to viewModel.submitted:
submitButton.setOnClickListener {
viewModel.submitted(binding, parentFragmentManager)
}
Note: the instruction page mentioned above in the "Showing a Dialog" section it says to use getFragmentManager from Fragment, but Android Studio says that is deprecated and to use parentFormatManager.
Then inside viewModel.submitted() I show the alert when I get an error either before the coroutine or after the coroutine:
val alert = AddSymbolDialogFragment()
alert.message = "bad Category"
alert.show(fragmentManager, "Test1")
That seems to work.
It is the viewModel.submitted() routine's job to update the data with the info in the binding parameter. When it is successful I will also want to show an AlertDialog just to show it was successful.
So, I have a setup that works. But, I am unsure it is the correct way to do it. It just doesn't feel right to have the viewModel show an alertDialog. Seems that ought to be done at the ui level.
Is there a more appropriate way to show alertDialogs rather than passing the Fragment's parentFragmentManager to the viewModel routine that performs the I/O work?
There is also kind of a bigger question here. When I launch a coroutine and an exception occurs in the coroutine how in general do you pass that exception up to higher levels of the code.
Thanks
I am trying to set up a test for my project - to test that a progress bar is displayed when my app performs a server request.
The code under test uses an AsyncTask to perform the network call.
I have created a blocking server (MockWebServer) to catch and hold the network call - it receives request but doesn't provide a response until i call ".release()". This allows me to verify before the server response occurs.
My logic flows like this:
// Mock server will catch the next network request
BlockingServer blockingServer = createBlockingServer();
// onResume() activity performs network request and shows Progress Spinner
activityTestRule.launchActivity(null);
// onView() waits on UiController.loopUntilIdle() <- Fails here due to timeout.
onView(withId(progressBar)).check(matches(isDisplayed()));
// Tells the server to respond to the network request
blockingServer.release();
onView(withId(progressBar)).check(matches(not(isDisplayed())));
My problem is that because the Code Under Test uses AsyncTask for the server request, Espresso naturally blocks on the verify call (onView()) in order to wait for the AsyncTask to complete before verifying.
What I need is to temporarily stop Espresso idling while waiting for AsyncTask in order to perform the verify while the server is blocking the app logic flow.
(Changing the Code Under Test is not an option)
Can someone help?
So... this is the answer I've arrived at and some working out behind it:
Espresso (specifically calls to onView(), onData(), injectEvent and Actions) uses UiControllerImpl.loopMainThreadUntilIdle() to wait until all "idle-causing" signals are false. It loops over AsyncTask, CompatAsyncTask and something called dynamicIdle to all be idle.
When this method returns the main flow continues.
loopMainThreadUtilIdle() checks an IdleNotifier to check the idle state of each of those three elements. Obviously if you want to stop espresso waiting for AsyncTask the asyncIdle is of particular interest to you.
The IdleNotifier classes are fed into UiControllerImpl at it's construction - this takes place via dagger so you'll need to look at DaggerBaseLayerComponent which uses Providers to grab the construction arguments and pass them into the UiControllerProvider to construct it.
Everything in all of these classes is locked down very tightly. Method and class visibility is usually protected or package-private and final.
The only way I found was to create my own Espresso.java class (onView() and onData()) which used custom DaggerBaseLayerComponent allowing me to use either: My own Providers or My own UiController.
I found however this doesn't solve the whole problem. There is one more mechanism that needs to be coded around - When you're starting activities they use a waitForIdleSync in the Instrumentation class. Usually this is the Runner which is provided in your gradle file. I created my own AndroidJUnitRunner and provided this in gradle to allow me to return from waitForIdleSync on command.
And finally, in startActivitySync in the Instrumentation base class, it uses an array of ActivityWaiter objects to hold up your launchIntent() calls. I couldn't think of a reasonable way of avoiding this so I cheated and created this method in my Runner:
public void clearActivityWaitQueue() {
Object mSync = Whitebox.getInternalState(this, "mSync");
List mWaitingActivities = Whitebox.getInternalState(this, "mWaitingActivities");
if (mSync != null && mWaitingActivities != null) {
mWaitingActivities.clear();
synchronized (mSync) {
mSync.notifyAll();
}
}
}
It uses PowerMock to give me the convenience Whitebox methods to set internal state of Instrumentation:
// Used to give access to Whitebox
androidTestImplementation 'org.powermock:powermock-reflect:1.6.5'
And that's it! Easy right?
(Please tell me it's easier than this and how!!)
I had a bad crash case that was caused due to some Asyncs doing stuff in improper order in a SQLite and thing blew up. It took me some time to debug all that and access to the internal db would have helped immensely. I know how to access that internal db on a dev device but in case something goes wrong I would like to be able to get an instance of that db no matter the device. For error reporting I am using Crashlytics.
The question is: Is there a way to have Crashlytics run a piece of code (method, etc) during the crash collection/reporting? (For example, get db copy and email it, or something)
Couldn't find something in the documentation.
It is possible to get control prior to Crashlytics logging a crash. You essentially have to create your own uncaught exception handler and call Crashlytics' handler from there. Something like this in your Application class:
private UncaughtExceptionHandler originalUncaughtHandler;
#Override
public void onCreate() {
// initialize Fabric with Crashlytics
originalUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
// do the rest of your oncreate stuff
}
#Override
public void uncaughtException(Thread thread, Throwable ex) {
// do your work to add data to Crashlytics log
originalUncaughtHandler.uncaughtException(thread, ex);
}
No you can't. You can however set certain values before initiating Crashlytics. Like adding values to parameters so as to identify user. Like adding email id of user before creating a crashlytics session.
As #basu-singh said, you can add context to the crash, see https://docs.fabric.io/android/crashlytics/enhanced-reports.html
Or you can use your own UncaughtExceptionHandler, and then call Crashlytics. Though your code needs to be extra safe !
I need to perform an unit test where I need to check if an error message is logged when a certain condition occurs in my app.
try {
//do something
} catch (ClassCastException | IndexOutOfBoundsException e) {
Log.e(INFOTAG, "Exception "+e.getMessage());
}
How can I test this? I am getting the below error while unit testing.
Caused by: java.lang.RuntimeException: Method e in android.util.Log not mocked.
There are two ways to do this:
You turn to Powermock or Powermokito; as those mocking frameworks will allow you to mock/check that static call to Log.e().
You could consider replacing the static call.
Example:
interface LogWrapper {
public void e( whatever Log.e needs);
}
class LogImpl implements LogWrapper {
#Override
e ( whatever ) {
Log.e (whatever) ;
}
And then, you have to use dependency injection to make a LogWrapper object available within the classes you want to log. For normal "production" usage, that object is simply an instance of LogImpl; for testing, you can either use a self-written impl (that keeps track of the logs send to it); or you can use any of the non-power mocking frameworks (like EasyMock or Mokito) to mock it. And then you use the checking/verification aspect of the mocking framework to check "log was called with the expected parametes".
Please note: depending on your setup, option 2 might be overkill. But me, personally, I avoid any usage of Powermock; simply because I have wasted too many hours of my life hunting down bizarre problems with Powermock. And I like to do coverage measurements; and sometimes Powermock gives you problems there, too.
But as you are asking about Powermock, you basically want to look here (powermockito) or here (powermock). And for the record: try using your favorite search engine the next time. It is really not like you are the first person asking this.
In my android application I have written try-catch in every event method. So when an exception occurs, the catch gets the exception and a method shows a message box containing the exception details and I can handle and find my application's bugs.
For example:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
}
catch (Exception e) {
MessageBox.showException(this, e);
}
}
Now in Robolectric which there is no device to show the ui results, I cannot find out if an exception occurred. Now I want to do something when my code went to catch part or when MessageBox.showException is called, the test fails.
How can I do that?
The only way I can think of solving this is for you to inject the component that handles the errors into the classes that use it and after that, load a customized one for your tests.
There are several ways to achieve this and probably some better than what I will suggest, but I will try and present the option that requires minimum changes to what I think is your current architecture.
1 - Whatever you use for showing exceptions, instantiate this in your Application class and keep it there. Or at least provide it from your application class, so now whenever you need to use MessageBox, instead of a static method, you fetch it from the Application first. For example:
((MyApplication)getApplication()).getMessageBox().showException(this,e)
2 - Create a TestMessageBox and a TestApplication (that extends your normal Application class). In your TestApplication, override getMessageBox() to return the TestMessageBox instead of the normal MessageBox. In your TestMessageBox do whatever you want to be able to observe the errors in your tests.
3 - In your tests, use the TestApplication. When you run tests, Robolectric will load this instead of the normal application so your tests will now use your TestMessageBox and you can capture the info you need.
#Config(application = TestApplication.class)