Skip to content
This repository has been archived by the owner on May 9, 2023. It is now read-only.

New ActivityResultContracts.RequestPermission() sample #7

Closed
Canato opened this issue Sep 14, 2020 · 6 comments · Fixed by #8
Closed

New ActivityResultContracts.RequestPermission() sample #7

Canato opened this issue Sep 14, 2020 · 6 comments · Fixed by #8

Comments

@Canato
Copy link
Contributor

Canato commented Sep 14, 2020

Issue

Android 11

With the new Android 11, we have a change on permissions behaviour, adding:

Luckily, Android already give a method(RequestPermission) that deal with all this cases.

Developer Android Permission Guide

On the permission guide Request App Permission where this project is linked (end of the page) we can see the usage of ActivityResultContracts.RequestPermission() and ActivityResultCaller.registerForActivityResult.

What can make the developer confused because registerForActivityResult still on alpha for appCompat library.

Solution

Based on this, would be beneficial for the community to have a sample code for it. But since the library still not release this should be another sub-project until the appCompat release 1.3 and this code became the default one.

@kovrizhin
Copy link

kovrizhin commented Feb 8, 2021

Hello, also will be quite interesting to see, how to pass parameters from launch to callback. Because in early version it was easy to do with closures, but now it is not possible to do, because you have to register your callback when you create activity or fragments.

Should we build Custom Contract, and where store values?

Thanks.

@Canato
Copy link
Contributor Author

Canato commented Feb 9, 2021

@kovrizhin could you take a look in the PR?
#8

Would this solve your question?

If not, can you provide some code sample from the way you said in early version so I can help to find the latest way of doing it?

@kovrizhin
Copy link

kovrizhin commented Feb 9, 2021

@Canato Thanks a lot for quick response. I checked #8 and looks like I don't find answer for now. I understood a bit that it is wrong how I want to use and how I use it, this is why it was deprecated and not allowed, but it was quite useful. I will try to write code:

Previously it was possible to write code like this, for example we have a RecycleView and in onBindViewHolder we create a closure, and provide item like this:

        override fun onBindViewHolder(holder: EmptyViewHolder, position: Int) {
            val item = dataset[position]
            holder.downloadButton.setOnClickListener {
                registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
                    if (isGranted) GlobalScope.launch(Dispatchers.IO) {
                        clientService.download(item, position) // passing item and position as parameter, this is what I mean by context
                    } else {
                        notificationServie.notifyUser(item)
                    }
                }.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            }
        }

but now I need to check permission firstly and if I have it I can continue if not I need to ask it, when I request it loose context I don't have ability to continue action with parameters after user grant me permission, the user have click again action.

    val registerForActivityResult = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
               clientService.download(???, ???) // how should I retrieve here parameters 
        } else {
              notificationServie.notifyUser(???, ???) // how should I retrieve here parameters
        }
    }

        override fun onBindViewHolder(holder: EmptyViewHolder, position: Int) {
            val item = dataset[position]
            holder.downloadButton.setOnClickListener {
                if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                    clientService.download(item, position) // passing item and position as parameter, this is what I mean by context
                } else {
                    registerForActivityResult.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
            }
        }

And I can't find how to do it in correct way, because creating custom contract will also not solve it, where is no place to store custom date inside to contract.

Of course there are two possible hacks :) But I don't like it. You can store item into instance of your class or in the custom contract when do createIntent, but this I don't like..

@Canato
Copy link
Contributor Author

Canato commented Feb 9, 2021

I see your point.

Indeed you could create a wrapper with custom contract, but for your case I agree is not the best solution, my personal suggestion (be clear I'm not part of the google team)

Easy fix(little hack and little unsafe): make a latestPosition parameter in this class so you save the state and get the item later.

 private val latestPosition: Int = 0
    val registerForActivityResult = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            clientService.download(dataset[latestPosition], latestPosition) // how should I retrieve here parameters
        } else {
            notificationServie.notifyUser(???, ???) // how should I retrieve here parameters
        }
    }

    override fun onBindViewHolder(holder: EmptyViewHolder, position: Int) {
        val item = dataset[position]
        holder.downloadButton.setOnClickListener {
            if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                clientService.download(item, position) // passing item and position as parameter, this is what I mean by context
            } else {
                latestPosition = position
                registerForActivityResult.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            }
        }
    }

Fix I would do: On clickListener would active a listener fun onDowloadClicked(item, position) this will pass the logic check to your presenter/viewModel/Controller depending on your architecture, that would check if you have the permission and start the download when provide.
This will be the right place to hold a instance/cache the value and later access the service/repository.
And if you are worry about the safety of the value, ideally immutable, the solution would be to create a Stream/cue, like a Coroutines Channel to keep the value safe.

This second solution bring more work cause you would need to bring everything in another place following your architecture. But this will make your code safer and reliable on the long term.

@kovrizhin
Copy link

@Canato Yeah, thanks a lot for. Yep, I do the first approach right now and trying to find good solution, how to correct handle it. The second of course is better, but it is required a lot of staff just for small things, and if you have quite simple application it is a big pain :)

But thanks a lot. :)

troy1993-tcs referenced this issue in Jiangxuewu/permissions-samples May 11, 2021
Change-Id: I11cae937a03e05b85f43e0d78a20a50e0d255d21
@IamMuhammadHasib
Copy link

@SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
if (requestCode == myResquestCode) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLastLocation()
} else {
requestPermission()
}
}
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants