Как проверить разрешение предоставлено в ViewModel?

Мне нужно запросить разрешение для контактов, и когда приложение запускается, я спрашиваю, в части ViewModel мне нужно вызвать метод, который требует разрешения. Мне нужно проверить разрешение, предоставленное пользователем или нет, а затем позвонить, но для проверки разрешения мне нужно иметь доступ к Activity. в то время как в моей ViewModel у меня нет ссылки на Activity и я не хочу иметь, как я могу решить проблему?


person I.S    schedule 14.06.2017    source источник
comment
поэтому в части ViewModel мне нужно вызвать метод, который требует разрешения - ИМХО, это ошибка архитектуры. Если ViewModel работает с чем-то более сложным, чем Bitmap, то у вашего ViewModel неправильные обязанности.   -  person CommonsWare    schedule 14.06.2017
comment
@CommonsWare В ViewModel я вызываю метод getContacts() или любой другой метод, для которого мне нужно иметь разрешение. Если будет проверка, что разрешение не предоставлено, метод не будет вызываться. Я не знаю, где организовать контрольную часть, так как во ViewModel я не хочу иметь ссылку на Activity.   -  person I.S    schedule 14.06.2017
comment
В ViewModel я вызываю метод getContacts() или любой другой метод, для которого мне нужно иметь разрешение - ИМХО, что-то за пределами ViewModel должно вызывать setContacts() в ViewModel. ViewModel должен быть немного больше, чем POJO.   -  person CommonsWare    schedule 14.06.2017
comment
@CommonsWare Я говорю об этой ViewModel developer.android.com/topic/libraries /architecture/ насколько я вижу здесь разрешено вызывать методы   -  person I.S    schedule 14.06.2017
comment
:: пожимает плечами :: Я не согласен с образцами, показанными там, именно из-за проблемы, которую вы здесь поднимаете. Какой-то презентатор или контроллер — что-то с доступом к активности — отвечает за получение защищенных данных. В идеале весь этот пользовательский интерфейс не запускается до запроса разрешения, что полностью устранило бы эту проблему.   -  person CommonsWare    schedule 14.06.2017
comment
Я согласен с @A.A.I.A. в том, что разрешения НЕ должны нуждаться в активности. Я пытаюсь сделать запрос к MediaStore, и ему требуется READ_EXTERNAL-STORAGE. Запрос MediaStore для получения информации о песнях — это запрос данных на уровне домена из системной службы, и он находится даже ниже уровня ViewModel IMO. Я хочу, чтобы запрос на уровне домена выполнял эту работу, и этот уровень не должен даже знать о существовании действия, не говоря уже о доступе к нему. Я использую RxKotlin, поэтому хочу, чтобы результаты этого запроса были наблюдаемыми для ViewModel, а затем, в конечном итоге, для слоя Fragment/UI.   -  person Jim Leask    schedule 07.06.2018
comment
@CommonsWare Мы переходим к модульной конструкции, в которой Fragment/ViewModel/Domain являются автономными и собираются в макет только с помощью верхней активности. Активность не знает, и IMO не должна знать, что представляют собой данные для этих компонентов, и даже не должна знать о необходимости разрешений, поэтому мы не можем заблокировать появление пользовательского интерфейса на этом уровне. Нижние части пользовательского интерфейса могут нуждаться в настройке, если разрешение не предоставлено, но это не задача верхнего действия. Должен быть лучший способ сделать это, сохраняющий изоляцию.   -  person Jim Leask    schedule 07.06.2018
comment
@JimLeask: активность не знает, и IMO не должна знать, что представляют собой данные для этих компонентов - фрагменты могут запрашивать разрешения во время выполнения.   -  person CommonsWare    schedule 07.06.2018


Ответы (3)


Я только что столкнулся с этой проблемой и решил вместо этого использовать LiveData.

Основная концепция:

  • ViewModel имеет LiveData о том, какой запрос разрешения необходимо сделать

  • ViewModel имеет метод (по сути, обратный вызов), который возвращается, если разрешение предоставлено или нет.

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")
        })
        ......
    }
}
person Louis Tsai    schedule 25.05.2018

Я переработал решение. Объект PermissionRequester — это все, что вам нужно для запроса разрешений из любой точки, где у вас есть хотя бы контекст приложения. Для выполнения этой работы он использует своего помощника PermissionRequestActivity.

@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()
    }
}

Использование:

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()
    }
}
person user1185087    schedule 20.09.2017
comment
Как мне реализовать этот код в моей ViewModel, если мой запрос на разрешение требует отображения диалогового окна для обоснования разрешения? - person LCZ; 13.05.2020
comment
Это пока лучшее решение! Вы просто забыли упомянуть, что вам нужно зарегистрировать PermissionRequestActivity на AppManifest. - person Machado; 07.10.2020

Я сделал что-то вроде этого:

создайте абстрактный класс, расширяющий AndroidViewModel, который дает вам доступ к контексту приложения:

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()
    }
}

Теперь создайте свою модель представления, расширив класс BaseViewModel, и у вас будет доступ к контексту приложения.

class AdminViewModel(application: Application) : BaseViewModel(application) {
    .....
}

Теперь у вас всегда есть доступ к контексту, который вы можете использовать для получения доступа к ресурсам.

person Jo Momma    schedule 24.03.2020