Android UIKit's BaseFragment isActive() / isFragmentAlive() methods are not comprehensive and still allow crashes

[Problem/Question]

We have made a few customizations and bug fixes to UIKit v2 in our own forked version. We would love to get to a world where we are no longer using a fork. We are exploring moving to UIKit v3. Fortunately, it looks like UIKit v3 has opened up customization enough that we no longer need our own fork for visual changes. Unfortunately, there are some non-visual bug fixes we made in our fork that are still an issue in UIKit v3.

One such issue is in BaseFragment’s check for if the fragment is active / alive. This method is good for checking if a screen is still around when some async callback finally happens.

UIKit v2:

protected boolean isActive() {
    boolean isDeactivated = isRemoving() || isDetached() || getContext() == null;
    return !isDeactivated;
}

UIKit v3:

protected boolean isFragmentAlive() {
    boolean isDeactivated = isRemoving() || isDetached() || getContext() == null;
    return !isDeactivated;
}

Unfortunately, the check is not comprehensive enough and we were seeing crashes. In our fork, we changed the implementation to also check !Fragment.isAdded() as well.

protected boolean isFragmentAlive() {
    boolean isDeactivated = !isAdded() || isRemoving() || isDetached() || getContext() == null;
    return !isDeactivated;
}

[UIKit Version]
UIKit v3
UIKit v2

[Reproduction Steps]
Unfortunately, we no longer have the stack traces of crashes where this was an issue. It was most likely triggered by an async callback whose logic requires the fragment be added, but it has since been removed.

[Current impact]
It isn’t affecting us since we fixed in a forked version of UIKit. Ideally, we wouldn’t need a forked version of UIKit and this bug would be fixed in the original repository.

@stephen-mojo,

It looks like this would be better served by reporting it via the Dashboard as your an organization with a paid support plan. I would suggest relaying this to our support team via your organization’s Sendbird Dashboard.

I found a way to repro this issue pretty easily.

Repro Steps:

  • Go to a channel
  • Send a large file to the channel
  • Click the file message
  • Leave the screen by pressing back before the file has a chance to load

Expected Results:
Nothing happens. You simply go back to the previous screen.

Actual Results
By the time the file loads, the user has left the screen. Internally, Sendbird checks if the screen is still active before attempting to show the file. The problem is, like I said above, the check is not comprehensive enough. The application ends up crashing since the user has left the fragment but the fragment still thinks it is open and able to show the file.

java.lang.IllegalStateException: Fragment CustomChannelFragment{ac9ba41} (b7e564e0-f361-4d94-9e8c-9a3f5e887ed4) not attached to Activity
at androidx.fragment.app.Fragment.startActivity(Fragment.java:1443)
at androidx.fragment.app.Fragment.startActivity(Fragment.java:1433)
at com.sendbird.uikit.fragments.BaseMessageListFragment$3.onResultForUiThread(BaseMessageListFragment.java:514)
at com.sendbird.uikit.fragments.BaseMessageListFragment$3.onResultForUiThread(BaseMessageListFragment.java:496)
at com.sendbird.uikit.internal.tasks.JobResultTask$task$1.call$lambda-0(JobResultTask.kt:28)
at com.sendbird.uikit.internal.tasks.JobResultTask$task$1.$r8$lambda$I93HbT_cXW5oIuar0oBO9AHj9J4(Unknown Source:0)
at com.sendbird.uikit.internal.tasks.JobResultTask$task$1$$ExternalSyntheticLambda0.run(Unknown Source:8)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8757)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)