You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Compose与Android 13环境下结合MVI+Orbit架构请求推送通知权限的最佳实现方案问询

Compose与Android 13环境下结合MVI+Orbit架构请求推送通知权限的最佳实现方案问询

嘿,针对你在Compose+Android13环境下结合MVI+Orbit架构实现推送权限请求的问题,我整理了一套清晰的实现思路,帮你理顺各个环节的逻辑:

一、先搞定权限状态的持久化与状态管理

要解决「避免重复弹窗」的核心需求,我们得把权限状态牢牢管控起来——既要存在ViewModel的State里供UI实时感知,最好还要做本地持久化(比如用DataStore或者SharedPreferences),确保APP重启后也能记住用户的选择。

  • 首先给你的ViewModel State类加个权限状态字段,用枚举来明确不同状态:
enum class PushPermissionStatus {
    UNKNOWN, // 还未检查过权限
    GRANTED, // 已授权
    DENIED_PERMANENTLY, // 永久拒绝(用户点了「不再询问」)
    DENIED_TEMPORARILY // 临时拒绝(可再次发起请求)
}

// 你的ViewModel State类
data class MyState(
    val pushPermissionStatus: PushPermissionStatus = PushPermissionStatus.UNKNOWN,
    // 其他状态字段...
)
  • 每次APP启动或者进入需要权限的页面时,先执行权限检查,更新到ViewModel的State里。检查逻辑要兼顾Android13+的特殊权限:
fun checkPushPermissionStatus(context: Context) = intent {
    val status = when {
        NotificationManagerCompat.from(context).areNotificationsEnabled() -> PushPermissionStatus.GRANTED
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_DENIED &&
                !ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, Manifest.permission.POST_NOTIFICATIONS) -> PushPermissionStatus.DENIED_PERMANENTLY
        else -> PushPermissionStatus.DENIED_TEMPORARILY
    }
    reduce { state.copy(pushPermissionStatus = status) }
}

二、自定义弹窗的触发逻辑

只有当权限状态是UNKNOWN或者DENIED_TEMPORARILY时,才显示引导弹窗。这个判断直接在Compose UI层根据ViewModel的State来做:

val state by viewModel.collectAsState()
val context = LocalContext.current

// 根据状态判断是否显示弹窗
if (state.pushPermissionStatus in listOf(PushPermissionStatus.UNKNOWN, PushPermissionStatus.DENIED_TEMPORARILY)) {
    AlertDialog(
        onDismissRequest = { viewModel.onEvent(PushPermissionEvent.DismissDialog) },
        title = { Text("开启推送通知") },
        text = { Text("开启后可以及时收到重要消息提醒哦") },
        confirmButton = {
            Button(onClick = { viewModel.onEvent(PushPermissionEvent.OpenSettings) }) {
                Text("去设置")
            }
        },
        dismissButton = {
            Button(onClick = { viewModel.onEvent(PushPermissionEvent.DismissDialog) }) {
                Text("取消")
            }
        }
    )
}

三、Orbit架构下的SideEffect处理(跳转设置+返回后检查)

点击「去设置」后,通过SideEffect跳转系统设置页面,返回后要立即重新检查权限状态,这部分可以这么实现:

1. ViewModel里的事件与SideEffect分发

class MyViewModel: ContainerHost<MyState, MySideEffect>, ViewModel() {
    override val container = container<MyState, MySideEffect>(MyState())

    fun onEvent(event: PushPermissionEvent) {
        when(event) {
            PushPermissionEvent.OpenSettings -> {
                postSideEffect(MySideEffect.OpenNotificationSettings)
            }
            PushPermissionEvent.DismissDialog -> {
                // 可以标记用户暂时不想开启,后续再找时机触发
                reduce { state.copy(pushPermissionStatus = PushPermissionStatus.DENIED_TEMPORARILY) }
            }
            // 其他事件...
        }
    }
}

// 定义SideEffect
sealed class MySideEffect {
    object OpenNotificationSettings : MySideEffect()
    object PushPermissionGranted : MySideEffect()
    // 其他SideEffect...
}

2. UI层处理跳转与返回后的权限检查

rememberLauncherForActivityResult来监听设置页面的返回,回来后立刻触发权限检查:

viewModel.collectSideEffect { sideEffect ->
    when(sideEffect) {
        MySideEffect.OpenNotificationSettings -> {
            val settingsIntent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
                putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
            }
            val settingsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                // 返回后立即更新权限状态
                viewModel.checkPushPermissionStatus(context)
            }
            settingsLauncher.launch(settingsIntent)
        }
        MySideEffect.PushPermissionGranted -> {
            // 权限授予后执行你的后续操作,比如注册推送Token等
            registerPushToken()
        }
        // 其他SideEffect处理...
    }
}

四、优化权限请求的时机

如果用户还未做过权限选择(状态为UNKNOWN),可以先直接发起系统权限请求,而不是先弹自定义弹窗;只有当用户临时拒绝后,再用自定义弹窗引导去设置:

// ViewModel里添加请求权限的方法
fun requestPushPermission() = intent {
    postSideEffect(MySideEffect.RequestPushPermission)
}

// UI层处理权限请求的SideEffect
val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
    val status = if (isGranted) PushPermissionStatus.GRANTED else PushPermissionStatus.DENIED_TEMPORARILY
    viewModel.reduce { state.copy(pushPermissionStatus = status) }
    if (isGranted) {
        viewModel.postSideEffect(MySideEffect.PushPermissionGranted)
    }
}

viewModel.collectSideEffect { sideEffect ->
    when(sideEffect) {
        MySideEffect.RequestPushPermission -> {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
            } else {
                // Android13以下通知权限默认开启,直接标记为已授权
                viewModel.reduce { state.copy(pushPermissionStatus = PushPermissionStatus.GRANTED) }
                viewModel.postSideEffect(MySideEffect.PushPermissionGranted)
            }
        }
        // 其他SideEffect...
    }
}

这样整套流程就闭环了:APP启动检查权限→未授权则发起系统请求→请求被拒则弹自定义引导弹窗→用户跳转设置后返回更新状态→权限授予则执行后续操作,同时所有状态都由ViewModel管控,不会出现重复弹窗的问题。

备注:内容来源于stack exchange,提问作者Сергей Беляков

火山引擎 最新活动