livefront / bridge

An Android library for avoiding TransactionTooLargeException during state saving and restoration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Calling of `Bridge.restoreInstanceState(this, savedInstanceState)` without checking `savedInstanceState != null` could cause accidental removal of saved sharedPreferences

elye opened this issue · comments

commented

The bridgeDelegate has the below functions

    void restoreInstanceState(@NonNull Object target, @Nullable Bundle state) {
        boolean isFirstRestoreCall = mIsFirstRestoreCall;
        mIsFirstRestoreCall = false;
        if (state == null) {
            if (isFirstRestoreCall) {
                mSharedPreferences.edit()
                        .clear()
                        .apply();
            }
            return;
        }
        // ... other codes
    }

With this, we are suppose to call the restoreInstanceState without checking the savedInstanceState to help clear the sharedPreference, as we assume this is the state where it doesn't have state restoration needed.

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        Bridge.restoreInstanceState(this, savedInstanceState)
        // .... other codes
    }

However, there could be chance where isFirstRestoreCall is true, and savedInstanceState is also null, but it is actually in a restored state. This would cause accidental removal of all saved and needed sharedPreference

This issue is further describe in https://medium.com/@elye.project/handling-transactiontoolargeexception-with-livefront-bridge-a846459420bb, the Uses restoreInstanceState to help clear the data is called without StateRestoration section

I'm not really following your argument here. mIsFirstRestoreCall will be set to false the moment the onCreate of the very first Activity to be created / re-created has been called. This should happen before onCreateView of any Fragment has been called. So I'm not really sure what scenario would cause the problems you are describing. Do you have any logs? Or are you just thinking there might be a problem based on the code?

I'll also note that the "clearing" logic is based on the fact that if the app is restoring from a saved state, savedInstanceState for any Activity being created when the app launches should be non-null. If it is null, then the app is not restoring from a saved state and any saved state data can be safely cleared.

Also, in response to this part of your article:

After that, terminate the App, so the App would start from beginning. No state restoration is needed. That also means the saved sharedPreferences is not cleared.

The part about "terminate the App, so the App would start from beginning" is the scenario where the mIsFirstRestoreCall boolean is reset to true because the app's process would have been destroyed. Then, because there is no saved state, this logic triggers clearing the data:

        if (state == null) {
            if (isFirstRestoreCall) {
                mSharedPreferences.edit()
                        .clear()
                        .apply();
            }
            return;
        }

So again, if are you saying that this is not the way this is working for you, please share some logs.

I'll note that there is an assumption here that when you use Bridge, every Activity is using Bridge because you'd put the calls to Bridge in a base class. If that's not the case, I can see how maybe there could be an issue here under certain circumstances.

commented

The issue is replicated in actual situation, not just based on code analysis.
Do check out my example App with the example steps to replicate the issue.

In short, to make this happens, we just need to have state == null and isFirstRestoreCall is true.
The isFirstRestoreCall is a application level state variable. So that could be reset if the app level is killed, as per the article https://medium.com/inloopx/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

How to replicate the issue, is basically we have 2 fragments. One is using Bridge, and not the other one, and they stack on each other as we could create multiple of them.

Imagine we create 2 fragments that uses Bridge and stack on top of each other. And the 3rd one we create 1 fragment not using Bridge. Then we onBackground the App and have the system kill the App. This time the first 2 fragments state is stored.

On restoration, we would restore all fragments, but the first 2 is not called yet as it is not on the top. But the 3rd one (which doesn't have Bridge using) is surface. Instead of going back to fragment 2... one create a 4th fragment that have the Bridge used. This time, it will call the restoreInstanceState where it will have state == null and isFirstRestoreCall is true. This will then clear all the sharedPreferences. And when one go back to the fragment 1, 2... the restored data is all gone.

Hope this clarifies the issue.

Ya, this is more of a whitebox testing, as looking into the code and try to figure out any issue. One might not easily get into this problem. But when it does, it will take a long long time to figure out or replicate the scenario.

@elye OK yeah I can understand the scenario you are talking about now. Thanks for clarifying that. As mentioned above, I wasn't really expecting people to "selectively" use Bridge in just a few Activity / Fragment; the thought was that all screens would use Bridge by just placing the calls in a base class. Because there is no requirement to do so, though, I can see how this issue can pop up for some people unexpectedly and is something I should fix.

Fortunately there is a pretty easy fix for this : rather than doing the isFirstRestoreCall check inside restoreInstanceState (which might not be called for every Activity / Fragment in the scenario you've described) I can just do the check inside the ActivityLifecycleCallbacks.onActivityCreated callback, which will be triggered for every Activity (not just those that use Bridge). So I think this is a fix I can incorporate soon.

Thanks for helping find this issue!

@elye The new release should fix this issue.

@elye I'm going to close this issue now. If you still seem to have problems, feel free to re-open it.