【问题标题】:How to check permission is granted in ViewModel?如何检查 ViewModel 中授予的权限?
【发布时间】:2017-11-16 23:09:08
【问题描述】:

我需要为联系人请求权限,当应用程序启动时我在询问,在 ViewModel 部分我需要调用需要权限的方法。我需要检查权限是否由用户授予然后调用,但是为了检查权限我需要访问活动。而在我的 ViewModel 中,我没有对 Activity 的引用并且不想有,我该如何克服这个问题?

【问题讨论】:

  • “所以在 ViewModel 部分我需要调用需要权限的方法”——恕我直言,这是一个架构错误。如果ViewModel 正在处理比Bitmap 复杂得多的东西,那么您的ViewModel 的职责就错了。
  • @CommonsWare 在 ViewModel 中,我调用方法 getContacts() 或任何其他我需要获得权限的方法。如果将检查未授予权限,则不会调用该方法。我不知道在哪里组织检查部分,因为在 ViewModel 中我不想引用 Activity。
  • “在 ViewModel 中,我调用方法 getContacts() 或任何其他我需要获得许可的方法”——恕我直言,ViewModel 之外的东西应该在 @987654326 上调用 setContacts() @。 ViewModel 应该只是一个 POJO。
  • :: shrug :: 我不同意那里显示的示例,这正是您在这里提出的问题。某种演示者或控制器——可以访问活动的东西——负责获取受保护的数据。理想情况下,在请求权限之前不启动整个 UI,这样可以完全消除这个问题。
  • 我同意@A.A.I.A.因为权限不应该需要活动。我正在尝试查询 MediaStore,它需要 READ_EXTERNAL-STORAGE。查询 MediaStore 以获取有关歌曲的信息是来自系统服务的域级数据查询,甚至在 ViewModel 层 IMO 之下。我想要一个域级别的查询来完成这项工作,并且该级别甚至不应该知道活动存在,更不用说访问它了。我正在使用 RxKotlin,所以希望这个查询的结果对 ViewModel 是可观察的,然后最终对 Fragment/UI 层是可观察的。

标签: android android-activity permissions android-mvvm


【解决方案1】:

我已经修改了解决方案。 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()
    }
}

【讨论】:

  • 如果我的权限请求需要显示权限理由对话框,我该如何在 ViewModel 中实现此代码?
  • 这是迄今为止最好的解决方案!您只是忘了提及您需要在AppManifest 上注册PermissionRequestActivity
【解决方案2】:

我刚遇到这个问题,我决定改用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")
        })
        ......
    }
}

【讨论】:

  • 如何触发权限请求和“permissionRequest”对象的变化?
【解决方案3】:

我做了这样的事情:

创建一个扩展 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) {
    .....
}

现在您始终可以访问可用于访问资源的 Context。

【讨论】:

    猜你喜欢
    • 2015-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    • 2021-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多