https://kotlinlang.org logo
#compose
Title
# compose
n

Neal Sanche

10/28/2021, 9:40 PM
I have an app using Compose Navigation, and it is handling a deep link. The app processed the deep link just fine, however the user backgrounds the app and sometime later the app is returned to, and that same deep link is delivered to the app again. This time, however, since it's been processed already, returns an error. Is there any way to mark an intent in Android as 'processed' and not have it re-delivered to the app? I suspect the answer is no, but if anyone has an idea, I'd love to hear about it.
i

Ian Lake

10/28/2021, 10:22 PM
NavController already tracks whether it handled any deep link from the activity level (and it saves and restores that flag to handle even the process death and recreation case)
What does 'returns an error' mean?
n

Neal Sanche

10/28/2021, 10:41 PM
So, after process death and recreation, simulated through the 'Cached process stop' in the developer settings, the app starts up again, and our ViewModel savedStateHandle is again given the values in our deep link, and so the ViewModel thinks it should send that info to our server, and since this was a one-time code, it fails, and we're handling it as a failure. What I did was keep track of used codes client side, and avoid sending them to the server again and failing. It's a workaround. I just wonder why we are getting the deep-link info again.
My ViewModel init looks like this:
Copy code
init {
        println(savedStateHandle.keys())

        savedStateHandle.remove<String>("code")?.let { code ->
            // If we get here, we've probably been given a deep link with the code
            // to verify. We only want to do this once, so we remove the code from the
            // saved state.
            viewModelScope.launch {
                if (!(sharedPreferences.getStringSet(CODES_KEY, null)
                        ?: setOf()).contains(code)
                ) {
                    onVerifyCode(code)
                } else {
                    println("Already succeeded code, skipping.")
                }
            }
        }

        savedStateHandle.get<String>("state")?.let { state ->
            // The state contains the bank id, which we will look up and add to the viewState
            val bankId = state.substringAfter("openid id:")
            loadBank(bankId)
        }
    }
i

Ian Lake

10/28/2021, 10:50 PM
This looks like an issue with
SavedStateHandle
- when it is recreated, it unconditionally adds all of the default arguments (i.e., those passed to your destination), then adds each value from the saved state
Which means if you instead use
state.put("processed", true)
, that would come back after process death, but your
remove
gets 'undone' as it was by that default value
We should probably change that to only add the default values if the restoredState == null so that your
remove
call "sticks"
Do you mind filing a bug against Lifecycle for that
SavedStateHandle
issue? https://issuetracker.google.com/issues/new?component=413132&amp;template=1096619
1
And using the addition of a flag rather than removal as a workaround
n

Neal Sanche

10/29/2021, 3:11 PM
Sure, will do. Thanks for the explanation of how this is working, very helpful.
Made https://issuetracker.google.com/issues/204506306 with excerpts from this thread. I'll try the workaround suggested now.
👍 1
The suggested workaround, adding a flag to the
SavedStateHandle
worked, and allowed me to remove my other workaround. Thanks again Ian.
6 Views