mitchtabian/Open-API-Android-App

Crash when selecting "Home" tab

Zhuinden opened this issue ยท 14 comments

11-30 04:54:27.700 4977-4977/com.codingwithmitch.openapi E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.codingwithmitch.openapi, PID: 4977
    java.lang.NullPointerException: Attempt to invoke virtual method 'com.codingwithmitch.openapi.ui.Data com.codingwithmitch.openapi.ui.DataState.getData()' on a null object reference
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:79)
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:21)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.Transformations$2$1.onChanged(Transformations.java:155)
        at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Interestingly, it seems to load like this after returning from low memory condition (when not selecting it from another tab).

image

Actually, that first crash is trickier than I thought. I can't get a consistent repro of it. ๐Ÿค”

Ah, you need to actually select a Blog by clicking it first.

Then it will happen consistently. โค๏ธ

11-30 05:40:32.359 5634-5634/com.codingwithmitch.openapi E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.codingwithmitch.openapi, PID: 5634
    java.lang.NullPointerException: Attempt to invoke virtual method 'com.codingwithmitch.openapi.ui.Data com.codingwithmitch.openapi.ui.DataState.getData()' on a null object reference
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:79)
        at com.codingwithmitch.openapi.ui.main.blog.ViewBlogFragment$subscribeObservers$1.onChanged(ViewBlogFragment.kt:21)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at androidx.lifecycle.Transformations$2$1.onChanged(Transformations.java:155)

Can you help me reproduce? mitch@tabian.ca
If you don't mind. :)

1.) login

2.) on the initial tab, select a blog post

3.) select another tab

4.) put the application in background with HOME

5.) press "Terminate Application" in the Logcat tab in Android Studio

6.) restart the app from launcher

7.) try to go to the blog tab again


The 4-5-6th steps simulate when Android kills a backgrounded app then the user comes back to it from either the recent tasks or the launcher intent.

Thank you I'll take a look.

Yikes this is a big problem

Process ID before manually killing application.
PID before process death

Process ID after relaunch.
PID after relaunch

Issue:

New process after relaunch results in all data lost in viewmodel. Fragment backstack is maintained.

Resolution:

There's two options that I see:

  1. Save the state of viewmodel and restore.

    • See: Saved State for ViewModel
      • This seems very complex when using with dagger, but is likely the best option.
    • More research needed...
  2. Reset the app after process death

    • This is more of a bandaid than a solution.
    • This would be much easier. The downside is the user would not be able to continue doing whatever they were doing before the process died. Theoretically this shouldn't happen often. Probably only low memory conditions.
    • How?
      • Save PID to SharedPreferences in launcher activity (AuthActivity)
      • Check PID in MainActivity. If does not match, restart application.

Thank you @Zhuinden for pointing this out. I had no idea how to test process death.

Theoretically this shouldn't happen often.

Mate, I use a Pixel XL with 4 GB RAM, all I need to do to trigger this is take a photo. Sometimes that's enough, sometimes I also share it to Google Maps which is definitely enough.

Can't even imagine what it's like on a lower end device. ๐Ÿค”

Please tell me if I'm missing something.

I ran on a OnePlus One , which has 3 GB of ram and was released in 2014.

  1. Launched Open Api app
    - Clicked a blog
    - Selected another tab
    - Sent to background by pressing HOME button on device
  2. Opened Camera App
    • Took several pictures
    • Viewed several pictures
  3. Opened Google Maps:
    • Uploaded a photo to a park I go to frequently
    • Searched for places
    • Got directions
    • Scrolled around aimlessly
  4. Opened Instagram:
    • Scrolled around
    • Watched stories
    • Published a story of my own by taking a picture
    • Also I would note that I received about 100 push notifications when I opened it
  5. Opened facebook:
    • Scrolled around
  6. Opened YouTube:
    • Played some videos (making sure in 1080p)
  7. Updated the following apps (since I haven't ran this device in a while)
    - Instagram
    - Facebook
    - Google maps
    - YouTube

While doing all this I kept checking the process using ADB and it was never killed. I obviously also opened the Open-Api app from the recently used app list after doing all this, and it was functioning fine. I thought maybe I need to "relaunch" the app to cause the issue but that didn't either.

Am I doing something wrong?

Issue is partially fixed.

What is resolved:

  1. AuthToken is restored in SessionManager after process death:

  1. ViewStates are restored in BaseFragments on process death:
    • BaseAuthFragment restores AuthViewState
    • BaseAccountFragment restores AccountViewState
    • BaseBlogFragment restores BlogViewState
      • !!!!NOTE: Saving the search query results (the list of blogs) is not recommended. This is too much data for onSaveInstanceState. Instead, the search query itself (String value), should be saved using onSaveInstanceState and the query should be made again once the app is restored. Make sure the RecyclerView scrolls to the same position they left it. See reference.
    • BaseCreateBlogFragment restores CreateBlogViewState

Issues that still remain:

Interestingly the issue still remains if you trigger a configuration change immediately upon relaunching the app after killing the process.

Still working on this...

onViewStateRestored only happens if onViewCreated happened, which is skipped if the View is not actually used due to immediately cancelling out on creation / rotation.

You can workaround this by restoring state in Fragment.onCreate instead of onViewStateRestored.

Solved. See commit.

Merging to master.

TODO (future improvement):

Saving the search query results (the list of blogs) is not recommended. This is too much data for onSaveInstanceState. Instead, the search query itself (String value), should be saved using onSaveInstanceState and the query should be made again once the app is restored. Make sure the RecyclerView scrolls to the same position they left it. See reference.

Update:

  1. Restored List by saving query to outState and making query again.
  2. Fixed BottomNav backstack issue:

Will merge to master after I film their respective videos.