OKHttp crashes on when switching Fragments - android

I have no formal coding education so please let me know if something I did is considered bad coding
I am using a bottom navigation view for a train schedule application I am making. One of the options is a nearby functionality - when nearby is clicked on the bottom navigation drawer, the NearbyFragment is launched and my server gets a request via OKHttp automatically without additional user interaction
The problem is that if you switch from Nearby to another button on the bottom navigation view OR if you tapped the nearby button multiple times, the app would crash. Initially, this was because the runOnUiThread method wouldn't be able to find the UI thread since I moved on to a different fragment (and presumably the main thread no longer existed). I tried fixing this by creating a 1 second delay before the getLocation method was called in onViewCreated
// Delay before calling on location to prevent crashing when rapidly switching from Nearby Fragment
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (getActivity() != null) {
getLocation();
}
}
}, 1000);
I also wrapped the above, as well as OKHttp onSuccess and onFailure methods in an if statement (getActivity != null) to check that the UI thread still existed before proceeding
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
final String myResponse = response.body().string();
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
processJSON(myResponse);
}
});
}
}
}
I would still get crashes associated with the onFailure method with NullPointerExceptions and cannot find views (such as the snackbar) IF I changed from the NearbyFragment to a different fragment after 1 second of loading but before the loading had finished, so I wrapped the onFailure in a try/catch to catch all exceptions and ignore them. This feels like a hack, but my app no longer crashes/acts as expected when switching from from the NearbyFragment to another fragment via bottom navigation.
#Override
public void onFailure(Call call, IOException e) {
try {
mSwipeToRefresh.setRefreshing(false);
Log.e("Main", e.toString());
mSnackbar = Snackbar.make(getActivity().findViewById(R.id.swipe_container_nearby), "ERROR!", Snackbar.LENGTH_INDEFINITE);
snackBarCoordinator(mSnackbar);
mSnackbar.show();
} catch (Exception j) {
Log.e("Failed Get Closest Stations ", j.toString());
}
}
Is there a cleaner was to stop the crashes without the combination of three failsafes i.e., handler.postDelayed, (getActivity() != null), and an empty try/catch block?

To know whether activity or fragment is on UI.
For Activity, you can call isFinishing()
For Fragment call isAttached().
i.e before show loader, snack bar or any widgets wrap with if(...) {}
if(isAttached()) { snackbar.show(); }

Related

Loop blocking asynchronous request from being beformed. Why and how to solve?

I define how to handle the response I receive from my server in an anonymous inner class, where I store the response in a Map to enable access from outside that inner class.
Since my server call is asynchronous, I implemented a while-loop to wait for the result to be put into the map, before return it to the calling function.
However, somehow my loop isn't executed (as far as I can tell at least), and the loop appear to block my request from ever being finished, which obviously leads to un endless loop.
public Login getLoginByUsername(String username)
{
long callID = counter.incrementAndGet();
System.out.println("Start call");
ServerConnection.get("myURI",null, new JsonHttpResponseHandler(){
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response){
try {
System.out.println("Call success, enter result:");
callResults.put(callID, new CallResult(Login.parseFromJson(response)));
System.out.println(callResults.get(callID));
} catch (JSONException e) {
e.printStackTrace();
}
}
});
System.out.println("start waiting");
while(callResults.get(callID) == null){
//wait
System.out.print(".");
}
System.out.println("\nwaiting over, return result:");
System.out.println(callResults.get(callID));
return (Login) callResults.remove(callID).content;
}
I'm sure that the request works just fine. If I remove the loop (and return null to avoid the NPE from accessing the not yet finished result), I can see from the console outputs that the request is performed and yiels a valid result after the function terminates.
I also tried to move the whole request (ServerConnection.get) to another method in case the method performing the asynchronous call must for some reason terminate for the call to be performed. This didn't help either.
Also, I never get the console output defined within the loop, so I doubt it's even executed properly...
What is happening here, and how can I fix it?
The while loop will probably max out your CPU and throttle all other execution. In such waiting loops you should always introduce some kind of delay, even 100-10ms is enough.
Just call Thread.sleep(100); in your loop (and handle the InterruptedException somewhere).
So for example:
while(callResults.get(callID) == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return null; // Return null or just throw a RuntimeException. You can be pretty sure that this will never be called unless your app gets closed or your thread intentionally interrupted.
}
System.out.print(".");
}
Upon updating the code due to the related issue/question (How to return response to calling function? (Android-App calling Java REST-Server via JSON) if you're curious), I put a thread around my request:
public Login getLoginByUsername(String username)
{
long callID = counter.incrementAndGet();
new Thread(new Runnable() {
#Override
public void run() {
ServerConnection.get("person-login-relations?byUsername="+username,null, new JsonHttpResponseHandler(){
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response){
try {
callResults.put(callID, new CallResult(Login.parseFromJson(response)));
System.out.println(callResults.get(callID));
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
}) .start();
while(callResults.get(callID) == null){
//wait
}
return (Login) callResults.remove(callID).content;
}
This seems to have solved the problem I decribed here of the loop blocking the request. I guess this may be because the request or the response-handling might have been handled by the same thread that contained the loop and thus was waiting for the loop to finish. I'm not entirely sure though, especiall since I was using async requests before, which have been handled in a thread outside my control

Crashlytics and AsyncTask

I'm adding Crashlytics into an app and I ran a couple tests. When I throw an exception within an async task the report didn't appear in the console. Is this a known issue or should it be coming through?
AsyncTask.execute(new Runnable()) {
#Override public void run() {
throw new RuntimeException("THIS IS A TEST");
}
}
I know that Crashlytics is set up correctly because an exception thrown from the same function but outside the AsyncTask wrapper shows up just fine.
Can anyone else share their experience with crashes that occur asynchronously?
UPDATE
I ran more tests and I found that part of my issue was that I had a handler for uncaught exceptions. I had this in place so testers would get a dialog box and they could just tap OK to get a logcat attached to an email. (Thanks to need-to-handle-uncaught-exception-and-send-log-file) I tried a number of things and I in my case I just need to pick one or the other, the uncaught exception handler or the crashlytics. It works for me this way since I only really want the crashlytics in place for the production+release variant.
I tried including Crashlytics.logException(e) in the body of the exception handler but that didn't work. Possibly because the function calls System.exit(1) right after. Anyway... this is what I have now that does the job.
To use a custom application class, update the manifest
<application
android:name=".util.App"
In the App class I either set up the uncaught exception handler or the crashlytics.
public class App extends Application {
public void onCreate() {
super.onCreate();
Constants.IS_DEV = BuildConfig.FLAVOR.equals("dev");
if (Constants.IS_DEV || BuildConfig.DEBUG) {
setupUncaughtExceptionHandler();
} else {
Fabric.with(this, new Crashlytics());
}
[SNIP]
}
private void setupUncaughtExceptionHandler() {
// Setup handler for uncaught exceptions.
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread thread, Throwable e) {
handleUncaughtException(thread, e);
}
});
}
public void handleUncaughtException(Thread thread, Throwable e) {
// Crashlytics.logException(e); did not work here
// create intent to launch new instance and show 'send_log' dialog
Intent intent = new Intent();
intent.setAction(BuildConfig.APPLICATION_ID + ".SEND_LOG");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(1); // kill this instance
}
[SNIP]
}
My tests were just functions I grabbed in the settings page. They're just text items with onClick method set to 'onClick' (how original :)
public class SettingsActivity extends DataSourcedActivity {
[SNIP]
public void onClick(View view) {
if (view == findViewById(R.id.txtSettingsRemove)) {
launchRemoveItemsPage();
} else if (view == findViewById(R.id.txtSettingsRestorePurch)) {
launchRestorePurchases();
} else if (view == findViewById(R.id.txtContactSupport)) {
launchContactSupport();
} else if (view == findViewById(R.id.txtGetUpdates)) {
launchGetUpdates();
} else {
throw new DevException(Constants.UNKNOWN_SETTINGS_OPTION);
}
}
private void launchRemoveCollectionsPage() {
AsyncTask.execute(new Runnable()) {
#Override
public void run() {
throw new RuntimeException("THIS IS AN ASYNCHRONOUS TEST");
}
}
[SNIPPED ORIGINAL CONTENTS OF FUNCTION]
}
private void launchRestorePurchases() {
throw new RuntimeException("THIS IS A TEST");
[SNIPPED ORIGINAL CONTENTS OF FUNCTION]
}
[SNIP]
}
When I tried to use both the Crashlytics and the uncaughtException handler I got different results depending on which I set up first. If I setup Crashlytics first and then my uncaughtExceptionHandler then it appeared that mine overrode Crashlytics, no crash report made it to the console. If I setup my uncaughtExceptionHandler first then I do get the crash report on the console.
So I'm leaving this here just in case it might be helpful to others who run into this.
Mike
The crash comes on the next subsequent launch. The crash gets logged locally, then the next time you launch the 'same build or app' it sends the report up on startup.
Please ensure that you are starting crashlytics properly, and make sure you are launching the app a second time from the same app on your device to ensure it gets sent. Hitting play again and again from your debugger may have undesired results of sending the issue to the dashboard.
Also, in debug you may find slightly delayed posting, I've seen it take as much as 5 minutes before.

Espresso - Check if the screen is Visible or not

I'm trying to write some tests with the new android-test-kit (Espresso). But I can't find any information on how to check if a view is displayed and perform some actions on it (like clicking buttons, e.t.c.). Note that the view I need to check if it exists or not. If it does perform action on the view and if not proceed to the next view.
Any help would be appreciated. I just need a link, or some example code for the basics:
Check if the view exists
If yes,Perform action
If not, proceed to next screen
You can use a classic option "try/catch":
try {
onView(withText("Text")).check(matches(isDisplayed()));
//perform some actions on this view
} catch (NoMatchingViewException notExist) {
//proceed to the next screen
}
Object currentActivity;
#Nullable
private Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity = resumedActivities.iterator().next();
}
}
});
return (Activity) currentActivity;
}
with this, you can get the Activity that is currently displayed. After that by doing something like this, you can make a safe code section
HOMESCREEN:
{
for (; ; ) {
if (getCurrentActivity() != null) {
//check if it is the required screen
if (getCurrentActivity().getLocalClassName().toLowerCase().contains("homescreen")) {
//if it is the required screen, break the
//loop and continue execution
break HOMESCREEN;
} else {
//wait for 2 seconds and run the loop again
sleep(2000);
}
} else {
break HOMESCREEN;
}
}
}
sleep(2000) is a custom function which just calls thread.sleep like shown below:
private void sleep(long milliseconds) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException("Cannot execute Thread.sleep()");
}
}
You must control the behavior of your test. So, you must have to add some preconditions, or create the #Rule that will control the behavior, for example by adding parameters depending on which your view will be displayed or not.

Contexts and callbacks from asynchronous tasks

I've been experiencing this problem on some devices, especially Samsung.
I have an activity that has 7 fragments. In most of them, I start an async task for getting some data. I handle the exceptions by creating a handler in onCreateView().
handler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
if (msg.what == 0) {
Toast.makeText(getActivity().getApplicationContext(), getActivity().getResources().getString(R.string.connection_timed_out), Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
When making the API call, in catch blocks, if exceptions occur, I cancel the async task and in the onCancelled() callback, I notify the handler.
new AsyncTask<Void, Void, ArrayList<UserProfile>>() {
#Override
protected ArrayList<UserProfile> doInBackground(Void... params) {
try {
return new APIService().GetUserProfiles(EnloopApplication.getCurrent().getAuthenticationToken(), thisUserProfile.getEnloop_friends());
} catch (WampNetworkException e) {
cancel(true);
e.printStackTrace();
} catch (WampApiException e) {
e.printStackTrace();
} catch (SocketTimeoutException e) {
cancel(true);
}
return null;
}
#Override
protected void onCancelled() {
super.onCancelled();
handler.sendEmptyMessage(0);
}
On most devices, it works, but on Samsung devices sometimes I get null pointer exception since getActivity() returns nothing, as if the activity was destroyed and never got created again. Problem is the activity is not destroyed in code, since only thing that is happening is switching between fragments in same activity.
getActivity() returning null is a perfectly valid scenario which you should expect as well.
This happens because by creating anonymous Handler in your onCreateView you're referencing Fragment which was already detached from Activity (therefore getActivity() returns null). Same goes for your AsyncTask - if you're creating it as an anonymous class inside Fragment then it also references same detached Fragment.
I would suggest you to revise your architecture. How? I believe, is that your UI components should not perform asynchronous operations by themselves. Instead, you should use any sort of publish/subscribe mechanism out there (there are solutions which are using LoaderManager, HandlerThread, RxJava, RoboSpice and other 3rd party libraries).

Android: setRefreshing(false) on DDP callback hangs app

Here is the code:
public void onRefresh() {
MyDDPState.getInstance().getItems(null, new DDPListener() {
#Override
public void onResult(Map<String, Object> json) {
Log.d(TAG, "refreshed");
if (json.get("result") == null) {
/*
* Result is null action
*/
Log.d(TAG, "Null");
swipeLayout.setRefreshing(false);
return;
}
List<Map<String, Object>> temp = (ArrayList) json.get("result");
Log.d(TAG, temp.toString());
MyDDPState.getInstance().initItems(temp);
Log.d(TAG, "converted" + MyDDPState.getInstance().getItems().toString());
Log.d(TAG, swipeLayout.toString());
Log.d(TAG, "Finished refreshing");
swipeLayout.setRefreshing(false);
Log.d(TAG, "Refresh closed");
}
});
}
swipeLayout refers to a private variable in the class that holds the SwipeRefreshLayout. On the callback, I try to call setrefreshing(false) to get rid of the progress indicator, but this call hangs the async function. All the other Logs work except for the "Refresh closed" log.
For some reason, I think because of the library I'm using, errors inside DDP Listeners are not logged either, so I can't trace it. swipeLayout.setRefreshing when called outside of the DDP call work fine.
Anyway, I managed to solve the issue.
When I tried to change a ui variable, the function would stop running.
Turns out the issue was that I was changing UI variables on the wrong thread. To fix it, you have to make the UI calls inside a ui thread by calling:
activity.runOnUiThread(new Runnable() {
#Override
public void run () {
/*
* UI code
*/
}
});
More info here: http://developer.android.com/tools/testing/activity_testing.html

Categories

Resources