如何防止函数在Composable内调用,仅允许在非Composable中使用?
避免在Composable函数内直接调用
alert方法的方案 针对你遇到的问题,这里提供两种可行的解决思路,从编译期和运行期分别拦截错误调用,同时附带更友好的封装建议:
一、自定义@NotComposable注解+Lint编译期检查
通过自定义注解配合Android Lint规则,能在编译阶段直接拦截在Composable内的错误调用,从源头避免问题:
- 定义
@NotComposable注解
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) annotation class NotComposable
- 为
alert方法添加注解
@NotComposable fun Activity.alert(dialogData: AlertDialogData) { AlertDialogComposeView(this).show(dialogData) }
- 编写自定义Lint规则
创建Lint检测器,扫描所有@Composable函数,一旦发现直接调用带@NotComposable注解的方法,就抛出编译错误:
class NotComposableUsageDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames(): List<String> = listOf("alert") override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { method.getAnnotation(NotComposable::class.java) ?: return val parentFunction = node.findParentFunction() parentFunction?.getAnnotation(Composable::class.java) ?: return context.report( issue = ISSUE_NOT_COMPOSABLE_USAGE, location = context.getCallLocation(node, false, false), message = "禁止在Composable函数内直接调用@NotComposable方法,请用LaunchedEffect包裹后调用" ) } companion object { val ISSUE_NOT_COMPOSABLE_USAGE = Issue.create( id = "NotComposableUsage", briefDescription = "Composable内调用了@NotComposable方法", explanation = "带@NotComposable注解的方法直接在Composable内执行会引发重组重复调用问题", category = Category.CORRECTNESS, priority = 6, severity = Severity.ERROR, implementation = Implementation(NotComposableUsageDetector::class.java, Scope.JAVA_FILE_SCOPE) ) } }
将这个Lint规则集成到项目中后,只要开发者在Composable内直接调用alert,编译时就会报错,强制规范调用方式。
二、运行时检测是否处于Composable上下文
如果需要运行时兜底检查,可以通过Compose的currentComposer判断当前是否处于Composable执行流程:
fun Activity.alert(dialogData: AlertDialogData) { // 检测当前是否在Composable上下文内 val inComposable = runBlocking(Dispatchers.Main) { var result = false androidx.compose.ui.platform.ComposeView(this@alert).apply { setContent { result = currentComposer !== Composer.Empty } compose() } result } if (inComposable) { throw IllegalStateException("禁止在Composable函数内直接调用alert,请使用LaunchedEffect包裹") } AlertDialogComposeView(this).show(dialogData) }
这种方式是运行时判断,会有轻微性能开销,适合作为编译期检查的补充兜底方案。
更友好的封装建议
其实最省心的方式是直接封装一个Composable版本的弹窗方法,内部自动处理LaunchedEffect逻辑,让调用方无需关心底层细节:
@Composable fun AlertDialog(dialogData: AlertDialogData, onDismiss: () -> Unit = {}) { val activity = LocalContext.current as Activity LaunchedEffect(key1 = dialogData) { activity.alert(dialogData) onDismiss() } }
这样开发者在Composable内直接调用这个封装后的AlertDialog即可,完全不用担心重复调用问题,同时也避免了错误调用的可能。
内容的提问来源于stack exchange,提问作者pycxu
相关产品推荐
相关产品推荐

