How are people handling permission flows with comp...
# compose
d
How are people handling permission flows with compose? Right now I'm creating methods on my activity like
ensureFineLocation
that handles the whole permission flow and then passing a reference to the function down through the composables. This seems like it will scale pretty badly though, both in that the activity has a lot of unrelated methods and I've got to pass a lot of free functions. Also, since
registerForActivityResult
is global state I'm not sure the right way to handle repeated calls. I'm currently using a MutableState like so
Copy code
private val fineLocationStatus = MutableState<Boolean?>(null)
    private val requestFineLocationLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            fineLocationStatus.value = isGranted
        }

   private fun ensureFineLocation(): Boolean {
       val initialCached = fineLocationStatus.value
       if (initialCached != null) {
           if (!initialCached) showFineLocationMissingError()
           return initialCached
       }

       val permission = Manifest.permission.ACCESS_FINE_LOCATION

        if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) return true

        if (shouldShowRequestPermissionRationale(permission)) {
            if (!fineLocationRationalePrompt()) {
                fineLocationDeniedMsg()
                return false
            }
        }

        requestFineLocationLauncher.launch(permission)

        val cached = fineLocationStatus.filterNotNull().first()
        if (!cached) fineLocationDeniedMsg()
        return cached
   }
This is a little racy if there are multiple calls before a value is cached. I'm also not sure the value should be cached, so I considered clearing the mutable state before returning. Then I think I'd need to protect
ensureFineLocation
with a mutex, though. I feel like there has to be a better solution. Is there a reason why
registerForActivityResult
uses a global callback instead of giving each request a different requestCode so that you can have a suspend fun like
launcher.awaitResult
?
i
As per the docs (https://developer.android.com/training/basics/intents/result#register), your app can be killed due to low memory or config changes while the permission request (which is another activity) is in progress. You can't assume that your context exists at all after you call
launch
d
Oh, I hadn't realized the full implications of that. I don't understand then how it's possible to prompt for a permission on a button press in compose at all then
i
And I think you're misunderstanding how
rememberLauncherForActivityResult
works (note: the method was renamed) - it is not a global callback at all, it automatically handles returning the result to specifically the launcher that you call
launch
on
The reason it was renamed was specifically to denote that the launcher is remembered
d
Thank you! I didn't realize there was a compose-specific activityResult api, I've been using the androidx version in my main activity and passing callbacks to control it
d
I don't understand how that can work though @Ian Lake? My understanding is that
remember
doesn't persist through config changes/process death
i
It uses
rememberSaveable
for the unique key
d
But not the callback?
Throughout my app I've been following the advice to configure not to restart on config changes and only using
remember
, not
rememberSaveable
. If I use this do I have to refactor everything to use
rememberSaveable
?
i
You still should handle process death and recreation (something you can test with the 'Don't keep activities' developer option). You'd save a considerably less amount of data than you'd remember though
d
Thanks for all the help
i
We're tracking more comprehensive docs on the
activity-compose
APIs in https://issuetracker.google.com/190615266 if you'd like to star that and get notified on when those docs become available
👍 2
m
FYI, we’re working on permissions support in Accompanist 🙂