Jetpack Compose 1.2.0中withStyle调用stringResource报错的简洁解决办法
问题场景
此前大量使用withStyle构建带格式的注解字符串,例如要显示:
OPTION A or OPTION B
编写的代码如下:
val optionAorB = buildAnnotatedString { withStyle(MaterialTheme.typography.h2.toSpanStyle()) { append(stringResource(R.string.option_a)) } withStyle(MaterialTheme.typography.body2.toSpanStyle()) { append(stringResource(R.string.or)) } withStyle(MaterialTheme.typography.h2.toSpanStyle()) { append(stringResource(R.string.option_b)) } }
升级到Jetpack Compose 1.2.0版本后,编译器抛出错误:@Composable invocations can only happen from the context of a @Composable function
已知将stringResource移至withStyle的lambda代码块外可解决问题,但大量修改会导致代码混乱,需要更简洁的方案。
解决方案:自定义带Composable上下文的withStyle扩展函数
创建一个AnnotatedString.Builder的扩展函数,让其lambda参数支持@Composable上下文,这样就能在内部继续调用stringResource:
@Composable fun AnnotatedString.Builder.withComposableStyle( style: SpanStyle, content: @Composable AnnotatedString.Builder.() -> Unit ) { val styledString = buildAnnotatedString(content) pushStyle(style) append(styledString) pop() }
使用方式
只需将原有代码中的withStyle替换为withComposableStyle即可:
val optionAorB = buildAnnotatedString { withComposableStyle(MaterialTheme.typography.h2.toSpanStyle()) { append(stringResource(R.string.option_a)) } withComposableStyle(MaterialTheme.typography.body2.toSpanStyle()) { append(stringResource(R.string.or)) } withComposableStyle(MaterialTheme.typography.h2.toSpanStyle()) { append(stringResource(R.string.option_b)) } }
原理说明
Jetpack Compose 1.2.0之后,官方的AnnotatedString.Builder.withStyle的lambda参数不再标记为@Composable,而stringResource是必须在@Composable上下文中调用的函数,因此原有写法触发编译错误。
自定义的withComposableStyle通过内部调用Composable版本的buildAnnotatedString,让传入的contentlambda拥有@Composable上下文,从而允许调用stringResource,最后将生成的带格式字符串追加到当前Builder中并应用指定样式。
这种方式只需全局替换函数名(可通过IDE的批量替换功能完成),无需逐个迁移stringResource,大幅减少代码修改量。
内容的提问来源于stack exchange,提问作者HBB20

