Thread
#compose
    Neal Sanche

    Neal Sanche

    10 months ago
    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 months ago
    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?
    Neal Sanche

    Neal Sanche

    10 months ago
    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:
    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 months ago
    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
    And using the addition of a flag rather than removal as a workaround
    Neal Sanche

    Neal Sanche

    10 months ago
    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.
    The suggested workaround, adding a flag to the
    SavedStateHandle
    worked, and allowed me to remove my other workaround. Thanks again Ian.