<https://kotlinlang.slack.com/archives/C0BJ0GTE2/p...
# compose-android
b
I'm pretty sure we would accept this as a contribution to Accompanist if you would like to upstream it?
e
I considered that, but a lot of the undertones in the comments I read suggested that Accompanist didn't want to provide this functionality. I'm happy to contribute it up if that isn't the case.
b
I had a read through what you have written and it looks solid, so I think it should be fine
e
Aside from the functionality, there were some breaking API changes (mostly renaming and removing properties). Any thoughts on that?
b
If there is a good reason it would be ok because it's the perfect time to land them as there was only just a stable release. But if it's just a preferred name then I would probably ask you to rename them back
👍 1
e
b
Thank you! I'll check it out tomorrow
k
Hey Eliezer, thanks for the library 🙏 I've used a Accompanist in the past I had built similar wrapper, just to cover some of your use cases as well (such as, if user was already asked or not). One particular feature I found useful, but missing from both libraries are suspendable function for requesting a permission, which returns the result from the request.
Any thoughts on this maybe?
e
It's not something I have bandwidth for now, but if I have some free time I can put something together.
k
Sure, just asking your thoughts on that 😄
e
I've worked with an API like that in the past. Other than being careful with the CoroutineScope i.e. making sure it doesn't cancel on pause, it works fine.
k
I think it's useful overall, if you're working within compose context and do actions based on result of the request
e
I think it's less useful in the context of compose since you can use accompanist. It would be pretty straightforward to convert the accompanist state to a suspending call if you needed to use it in a LaunchedEffect.
k
I'm specifically talking about compose context where based on permission result you would do different things, such as navigate to different screens
e
Can you give me an example? I'm not understanding the scenario.
k
Copy code
var loading by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
fun onClick() {
    scope.launch {
        loading = true
        val result = requestPermissions()
        if(result.granted) {
            navigateToHome()
        } else {
            navigateToPermissionRationale()
        }
        loading = false
    }
}

Column() {
    Screen(onClick = onClick)
}
e
Why not use accompanist for that?
Copy code
val scope = rememberCoroutineScope()
var loading by remember { mutableStateOf(false) }
val permissionState = rememberPermissionState(...) { isGranted ->
  scope.launch {
    if(isGranted) {
      navigateToHome()
    } else {
      navigateToPermissionRationale()
    }
  }
  loading = false
}

Column {
  Screen(
    onClick = {
      loading = true
      permissionState.launchPermissionRequest()
    }
  )
}
k
For non suspendable context, yeah, but if you need to call other suspendable functions, then u need to use launch effect for that
e
I updated my message above to account for that.
👀 1
k
yeah, that would work in that case too. My point is that having a function that gets u the result make it bit easier to use in async scenarios
b
By memory, and it's been awhile, it's not possible to have a suspending version work consistently on all devices because your activity might be killed inbetween the request and the result. It's the same reason why libraries that convert startActivityForResult into a suspending function don't work
☝️ 1
i
Yes, you absolutely cannot write any suspending API around permissions or activity results and expect it to actually work - that's exactly why they are written how they are
k
just out of curiosity now - since accompanist accepts a callback function invoked when there’s a request result, couldn’t this be made into a suspendable function instead, using channels?
i
The callback function is not at the place where you request permissions - that place where you requested permissions (whether it is in an onClick or whatever) does not have any guarantee that it exists at all when the result comes in
k
hypothetically, wouldn’t that be the case for callback as well, in some weird cases? such as
Copy code
var visible by remember { mutableStateOf(true) }
  if (visible) {
    val permission = rememberPermissionState(
      android.Manifest.permission.CAMERA
    ) { granted ->
      println(granted)
    }

    Button(onClick = {
      permission.launchPermissionRequest()
      visible = false
    }) {
      Text("Request permission")
    }
  }
i
But as soon as you change visible back to true, it will get the callback
k
I see the callback is updated via
rememberUpdatedState
in
rememberLauncherForActivityResult
I got the reasoning behind it now, thank you all 🙏
c
excellent work! exactly what i was looking for!
i
Note the comment on the Accompanist pull request that indicates one of the cases where this code doesn't return the correct result (namely, in cases where the user hits the back button without confirming or denying the permission): https://github.com/google/accompanist/pull/1793#discussion_r1770720260
e
Yes, it's looking like at the end of the day there's no way to distinguish between the user cancelling the permission dialog, and the permission being permanently denied. I'm going to try to make an explicit way of handling that in
compose-permissionx
but at the end of the day the user will have to trade off between optimizing for permanent denials or cancellations.
👍 1
@Ben Trengrove [G] opened https://issuetracker.google.com/issues/375026897 in case anyone wants to follow along to see if there is a solution to the issue that @Ian Lake mentioned.