I need to ask permission for contacts and when application starts I'm asking,in ViewModel part I need to call method which requires permission. I need to check permission is granted by user or not and then call, but for checking permission I need to have access Activity. while in my ViewModel I don't have a reference to Activity and don't want to have, How I can overcome, the problem?
-
3"so in ViewModel part I need to call method which requires permission" -- IMHO, that is an architecture bug. If a `ViewModel` is working with anything much more complicated than a `Bitmap`, then your `ViewModel` has the wrong responsibilities. – CommonsWare Jun 14 '17 at 17:13
-
@CommonsWare In ViewModel I call method getContacts() or any other method for what I need to have permission . If there will be check that permission is not granted the method will not be called. I don't know where to organize check part, as in ViewModel I don't want to have reference to Activity. – I.S Jun 14 '17 at 17:45
-
"In ViewModel I call method getContacts() or any other method for what I need to have permission" -- IMHO, something outside of the `ViewModel` should be calling `setContacts()` on the `ViewModel`. A `ViewModel` should be little more than a POJO. – CommonsWare Jun 14 '17 at 17:48
-
@CommonsWare I'm talking about this ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel.html as I see it's allowed to call methods here – I.S Jun 14 '17 at 18:02
-
1:: shrug :: I disagree with the samples shown there, precisely for the sort of issue that you are raising here. Some sort of presenter or controller -- something with access to the activity -- is responsible for getting the protected data. Ideally, this whole UI is not started before requesting the permission, which would eliminate this problem entirely. – CommonsWare Jun 14 '17 at 18:05
-
1I agree with @A.A.I.A. in that the permissions should NOT need the activity. I'm trying to do a query of the MediaStore, and it needs READ_EXTERNAL-STORAGE. Querying MediaStore for info about songs is a domain level data query from a system service, and is even below the ViewModel layer IMO. I want a domain-level query to do that work, and that level shouldn't even know an activity exists let alone have access to it. I'm using RxKotlin, so want the results of this query to be Observable to the ViewModel then ultimately to Fragment/UI layer. – Jim Leask Jun 06 '18 at 22:38
-
@CommonsWare We are moving to a modular design where the Fragment/ViewModel/Domain are self contained, and only assembled into a layout by the top activity. The activity doesn't, and IMO shouldn't, know what the data is for these components, and shouldn't even know about the need for permissions so we can't block the UI from coming up at that layer. Lower parts of the UI may need to adjust if the permission isn't granted, but that isn't the job of the top activity. There must be a better way to do this that preserves isolation. – Jim Leask Jun 06 '18 at 22:58
-
@JimLeask: "The activity doesn't, and IMO shouldn't, know what the data is for these components" -- fragments can request runtime permissions. – CommonsWare Jun 06 '18 at 23:36
3 Answers
I just ran into this problem, and I decided to use make use of LiveData
instead.
Core concept:
ViewModel has a LiveData on what permission request needs to be made
ViewModel has a method (essentially callback) that returns if permission is granted or not
SomeViewModel.kt
:
class SomeViewModel : ViewModel() {
val permissionRequest = MutableLiveData<String>()
fun onPermissionResult(permission: String, granted: Boolean) {
TODO("whatever you need to do")
}
}
FragmentOrActivity.kt
class FragmentOrActivity : FragmentOrActivity() {
private viewModel: SomeViewModel by lazy {
ViewModelProviders.of(this).get(SomeViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
......
viewModel.permissionRequest.observe(this, Observer { permission ->
TODO("ask for permission, and then call viewModel.onPermissionResult aftwewards")
})
......
}
}

- 1,587
- 3
- 20
- 33
-
How do you trigger the asking of permissions and the change of "permissionRequest" object? – Starwave Sep 18 '21 at 14:34
I have reworked the solution. The PermissionRequester
object is everything you need to request permissions from any point where you have at least an application context. It uses its helper PermissionRequestActivity
to accomplish this job.
@Parcelize
class PermissionResult(val permission: String, val state: State) : Parcelable
enum class State { GRANTED, DENIED_TEMPORARILY, DENIED_PERMANENTLY }
typealias Cancellable = () -> Unit
private const val PERMISSIONS_ARGUMENT_KEY = "PERMISSIONS_ARGUMENT_KEY"
private const val REQUEST_CODE_ARGUMENT_KEY = "REQUEST_CODE_ARGUMENT_KEY"
object PermissionRequester {
private val callbackMap = ConcurrentHashMap<Int, (List<PermissionResult>) -> Unit>(1)
private var requestCode = 256
get() {
requestCode = field--
return if (field < 0) 255 else field
}
fun requestPermissions(context: Context, vararg permissions: String, callback: (List<PermissionResult>) -> Unit): Cancellable {
val intent = Intent(context, PermissionRequestActivity::class.java)
.putExtra(PERMISSIONS_ARGUMENT_KEY, permissions)
.putExtra(REQUEST_CODE_ARGUMENT_KEY, requestCode)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
callbackMap[requestCode] = callback
return { callbackMap.remove(requestCode) }
}
internal fun onPermissionResult(responses: List<PermissionResult>, requestCode: Int) {
callbackMap[requestCode]?.invoke(responses)
callbackMap.remove(requestCode)
}
}
class PermissionRequestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
requestPermissions()
}
}
private fun requestPermissions() {
val permissions = intent?.getStringArrayExtra(PERMISSIONS_ARGUMENT_KEY) ?: arrayOf()
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
when {
permissions.isNotEmpty() && requestCode != -1 -> ActivityCompat.requestPermissions(this, permissions, requestCode)
else -> finishWithResult()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionResults = grantResults.zip(permissions).map { (grantResult, permission) ->
val state = when {
grantResult == PackageManager.PERMISSION_GRANTED -> State.GRANTED
ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> State.DENIED_TEMPORARILY
else -> State.DENIED_PERMANENTLY
}
PermissionResult(permission, state)
}
finishWithResult(permissionResults)
}
private fun finishWithResult(permissionResult: List<PermissionResult> = listOf()) {
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
PermissionRequester.onPermissionResult(permissionResult, requestCode)
finish()
}
}
Usage:
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val cancelRequest: Cancellable = requestPermission()
private fun requestPermission(): Cancellable {
return PermissionRequester.requestPermissions(getApplication(), "android.permission.SEND_SMS") {
if (it.firstOrNull()?.state == State.GRANTED) {
Toast.makeText(getApplication(), "GRANTED", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(getApplication(), "DENIED", Toast.LENGTH_LONG).show()
}
}
}
override fun onCleared() {
super.onCleared()
cancelRequest()
}
}

- 4,468
- 1
- 30
- 38
-
How do I implement this code in my ViewModel, if my permission request requires displaying a dialog for the permission rationale? – LCZ May 13 '20 at 07:03
-
This is by far the best solution! You just forgot to mention you need to register the `PermissionRequestActivity` on `AppManifest`. – Machado Oct 06 '20 at 21:37
-
I did something like this:
create an abstract class that extends AndroidViewModel which gives you access to the application context:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Now, create your view model by extending the BaseViewModel class and you will have access to the application context
class AdminViewModel(application: Application) : BaseViewModel(application) {
.....
}
Now you always have access to a Context that you can use to get access to resources.

- 1,126
- 15
- 31