1

My app has been facing a lot of crashes and I am not able to replicate any of those crashes and could not able to even trace it back with the crash reports generated by Crashlytics.

Here is one scenario:

class ColumnDisplayFragment : BaseFragment() {

    private lateinit var viewModelType: ColumnDetailViewModelType

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setHasOptionsMenu(true)
        showBackButton(true)
        setActionBarTitle(viewModelType.title)
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            setHasFixedSize(true)
            adapter = ColumnDetailAdapter(viewModelType)
        }
    }

    companion object {
      fun newInstance(viewModelType: ColumnDetailViewModelType) = ColumnDetailFragment().apply {
          this.viewModelType = viewModelType
      }
    }
}

And this is how I am creating the fragment:

ColumnDetailFragment.newInstance(viewModelType)

My ViewModelType:

interface ColumnDetailViewModelType: Serializable {
    val columns: List<Column>
    val title: String
    val clickableColumns: List<Column>
    val chartButtons: List<ButtonType>
    val menu: Int
    val itemSelected: BehaviorSubject<Column>
    val buttonModel: DetailColumnButtonCell.DetailColumnButtonModel
    val showFragmentObservable: Observable<ShowDetailFragment>
    val showPopUps: Observable<ShowPopDetailUp>

    fun bindCloseButton(clicks: Observable<Unit>)
    fun onMenuItemSelected(item: Int)
}

But somewhere down the line, the viewModelType is lost and is causing the crash.

Here is the crash report generated in Crashlytics:

Fatal Exception: kotlin.UninitializedPropertyAccessException: lateinit property viewModelType has not been initialized
       at com.app.android.traderpro.etx.fragments.columnDetailFragments.ColumnDetailFragment.onViewCreated(ColumnDetailFragment.java:51)
       at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:892)
       at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
       at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
       at androidx.fragment.app.BackStackRecord.executePopOps(BackStackRecord.java:500)
       at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2076)
       at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
       at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
       at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:310)
       at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:253)
       at androidx.fragment.app.FragmentManagerImpl.handleOnBackPressed(FragmentManagerImpl.java:233)
       at androidx.fragment.app.FragmentManagerImpl$1.handleOnBackPressed(FragmentManagerImpl.java:108)
       at androidx.activity.OnBackPressedDispatcher.onBackPressed(OnBackPressedDispatcher.java:189)
       at androidx.activity.ComponentActivity.onBackPressed(ComponentActivity.java:286)
       at com.app.android.traderpro.etx.activities.homeActivity.HomeActivity.onBackPressed(HomeActivity.java:202)
       at android.app.Activity.onKeyUp(Activity.java:3756)
       at android.view.KeyEvent.dispatch(KeyEvent.java:2949)
       at android.app.Activity.dispatchKeyEvent(Activity.java:4092)
       at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:115)
       at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84)
       at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:133)
       at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:558)
       at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
       at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2814)
       at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
       at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:454)
       at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5742)
       at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5610)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5058)
       at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5111)
       at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5077)
       at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5234)
       at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5085)
       at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5291)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5058)
       at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5111)
       at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5077)
       at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5085)
       at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5058)
       at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5111)
       at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5077)
       at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5267)
       at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:5437)
       at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:3072)
       at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2615)
       at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2606)
       at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:3049)
       at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:143)
       at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
       at android.os.MessageQueue.next(MessageQueue.java:363)
       at android.os.Looper.loop(Looper.java:173)
       at android.app.ActivityThread.main(ActivityThread.java:8147)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)

But when I try to replicate it, everything seems to work fine and the app is not crashing. Any help would be appreciated.

Thank you.

srisindhu saride
  • 391
  • 6
  • 25
  • Why don't you put `ColumnDetailViewModelType` inside a constructor and don't leave the possibility to have uninitialized fields? – Neo Mar 10 '20 at 12:34
  • But that wouldn't fix the issue as the crash would still happen when app has been terminated – srisindhu saride Mar 10 '20 at 16:57
  • It would - because you get rid of `lateinit var` and make it `val` instead. `UninitializedPropertyAccessException` can never happen when you don't use `lateinit ` – Neo Mar 11 '20 at 08:29
  • @Neo not only is that impossible with Fragments due to the way Fragments work, it also ignores the underlying issue at hand (Fragments can be recreated by the system through the no-args constructor WITHOUT ever going through the `static` new instance method). – EpicPandaForce Mar 12 '20 at 08:54

1 Answers1

1

But when I try to replicate it, everything seems to work fine and the app is not crashing. Any help would be appreciated.

Welcome to Process Death induced crashes in production, where your app is terminated by the system to reclaim resources, you come back to the app and lo and behold, what is not saved in setArguments, intent.putExtra, or onSaveInstanceState, (and not saved to disk either), is lost entirely :)

As also outlined in Singleton object becomes null after app is resumed:

Try it out:

  • put your application in background with HOME button

  • click the TERMINATE button on Logcat tab in Android Studio (NOTE: Android Studio 4.x behaves differently, and you need to use adb shell am kill <packagename>.)

  • then re-launch the app from the launcher.

You'll experience this phenomenon.

terminate button

In Android Studio 4.0, the Terminate button issues am force-stop, and so you need to use am kill variant.

You can also trigger the pre-4.0 behavior of Terminate Application with the following terminal command:

 $ adb shell am kill your.app.package.name

So with that in mind, the problem is that you set an instance field rather than pass the viewModelType through setArguments.

companion object {
  fun newInstance(viewModelType: ColumnDetailViewModelType) = ColumnDetailFragment().apply {
      this.arguments = Bundle().also { bundle ->
          putSerializable("viewModelType", viewModelType)
      }
  }
}

And on other side:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModelType = requireArguments().getSerializable("viewModelType")
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Thanks for giving some insight. But to send it as Serialisable in setArguments the viewModelType is a complex interface. I have updated my question that shows u what my viewModelType looks like. Please have a look – srisindhu saride Mar 09 '20 at 16:22
  • You will need to share the ViewModel from a possible super-scope and preserve the state using either `onSaveInstanceState(Bundle)`, or use what Jetpack Lifecycle offers for this (https://codelabs.developers.google.com/codelabs/android-lifecycles/#6). You need to handle that your app restarts on any screen, and persist/restore your state accordingly. You won't be able to make your "view model type" into Serializable, I expected that to be an `enum` based on that name. – EpicPandaForce Mar 09 '20 at 16:30
  • Have you succeeded in reproducing the crash? – EpicPandaForce Mar 09 '20 at 18:41
  • No, I was not able to reproduce the crash – srisindhu saride Mar 10 '20 at 09:23
  • It should actually be fairly straightforward: navigate in your app to `ColumnDisplayFragment`, put app in background, press TERMINATE in Logcat tab in Android Studio (3.6 or lower), then restart your app from launcher, then press BACK button on device – EpicPandaForce Mar 10 '20 at 09:29
  • After I go to ColumnDisplayFragment and put the app to background and terminate the app, and then try to open it from the launcher, the app is just starting from the beginning and is not crashing – srisindhu saride Mar 10 '20 at 10:11
  • If you are using Android Studio 4.0, then you must use the `adb shell am kill ` command instead of using the Terminate button. – EpicPandaForce Mar 10 '20 at 11:03
  • I tried with the command as well, if I don't kill it, and try to open the app from launcher after it has been put to the background, it is normally resuming from the fragment. If I kill the app, and then open the app from the launcher, then the app is being launched from start. But I couldn't replicate the crash. – srisindhu saride Mar 10 '20 at 16:06
  • Put the app in background, use `adb shell am kill your.packge.name`, then restart from launcher (NOT from Android Studio) – EpicPandaForce Mar 10 '20 at 16:22
  • Oh yes, I am able to replicate the crash now. But as u mentioned I need to persist/restore the viewModelType which is very difficult to do as that interface is extended by multiple classes which are massive again. And I can't save them to a bundle as well – srisindhu saride Mar 10 '20 at 16:56
  • You have to store their state and restore it, then be able to recreate it. – EpicPandaForce Mar 10 '20 at 23:06
  • @EpicPandaForce can we store/restore lambda action, like (()-> Unit ) ? – Mahmoud Mabrok Apr 26 '22 at 12:53
  • 1
    @MahmoudMabrok just today i had to separate callbacks from data because the callback does not make sense after it was recreated, the references would be wrong if they even exist. So I think not. – EpicPandaForce Apr 26 '22 at 13:40
  • I have dialog fragment that accepts lambda action in arguments, after process death when back, actions are null, how could i solve this? – Mahmoud Mabrok Apr 26 '22 at 14:16
  • if they set callbacks as field variables then your only bet is trying to find the fragment via the tag and then set it again if it exists in `onCreate` – EpicPandaForce Apr 26 '22 at 20:44