Jetpack撰写预览渲染问题时,铸造LocalContext.current


Android Studio Chipmunk 2021.2.1; 
Compose Version = '1.1.1'; 
Gradle  Version 7.4.2; 
Kotlin 1.6.10;

在某一点上,一切都在工作。然后这个错误出现了,当我尝试调用localcontext .current时,预览停止了工作。创造"语境"。applicationContext作为Application"无论是在这个项目中还是在另一个项目中。它曾经使用"LocalContext.current">

尝试不同版本的Compose, kotlin, gradle。

渲染问题

. lang。ClassCastException:类bridgecontext不能强制转换为类android.app.Application(com.android.layoutlib.bridge.android。BridgeContext和android.app.Application在loader的未命名模块中pluginclassloader @3a848149) at调用com.client.personalfinance.screens.ComposableSingletons AccountScreenKtλ2美元1.美元(AccountScreen.kt: 136)在调用com.client.personalfinance.screens.ComposableSingletons AccountScreenKtλ2美元1.美元(AccountScreen.kt: 133)在androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambda.jvm.kt: 107)在androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambda.jvm.kt: 34)在androidx.compose.material.MaterialTheme_androidKt.PlatformMaterialTheme (MaterialTheme.android.kt: 23)在androidx.compose.material.MaterialThemeKt MaterialTheme 1美元1.美元调用(MaterialTheme.kt: 82)在androidx.compose.material.MaterialThemeKt MaterialTheme 1美元1.美元调用(MaterialTheme.kt: 81)在androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambda.jvm.kt: 107)在androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambda.jvm.kt: 34)在androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider (CompositionLocal.kt: 228)在androidx.compose.material.TextKt.ProvideTextStyle (Text.kt: 265

@Preview(showBackground = true) 
@Composable fun PrevAccountScreen() {
val context  = LocalContext.current
val mViewModel: MainViewModel =
viewModel(factory = MainVeiwModelFactory(context.applicationContext as Application))
AccountScreen(navController = rememberNavController(), viewModel = mViewModel)
}

我发现当你需要访问特定于Android生命周期的东西时,比如Application, Activity, FragmentManager, ViewModel等,让预览工作的最好方法是创建一个不做任何事情的接口实现。

使用FragmentManager的一个例子:

@Composable
@OptIn(ExperimentalAnimationApi::class)
fun MyFragmentView(
fragmentManager: FragmentManager
) {
Button(modifier = Modifier.align(Alignment.End),
onClick = {         
MyDialogFragment().show(fragmentManager, "MyDialogTag")
}
) {
Text(text = "Open Dialog")
}
}

预览功能:

object PreviewFragmentManager: FragmentManager()
@Preview
@Composable
fun MyFragmentViewPreview() {
MyFragmentView(
fragmentManager = PreviewFragmentManager
)
}

现在你的预览功能将呈现。

你可以用ViewModel做同样的事情——只需要让你的ViewModel扩展一个接口。

import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.StateFlow
interface MyViewModel {
val state: StateFlow<SomeState>
fun doSomething(input: String)
}
class MyViewModelImpl: MyViewModel, ViewModel() {
// implement interface's required values/functions
}
object PreviewViewModel: MyViewModel() 
@Composable
fun MyView(viewModel: MyViewModel = viewModel<MyViewModelImpl>()) {
// UI building goes here
}
@Composable
@Preview
fun MyViewPreview() {
MyView(viewModel = PreviewViewModel)
}

在你的情况下,我建议做上面概述的ViewModel的步骤,不要在你的预览中混淆LocalContext。

如果试图注入ViewModel,则无法呈现可组合预览。

有很多方法可以避免这个问题。但对我来说,最简单、最清晰的方法就是不渲染具有viewModel的可组合对象。

要做到这一点,你可以简单地提取你的MyView可组合的内容到另一个名为MyViewContent的可组合。

MyView可组合将声明MyViewContent需要的所有状态(他将是有状态的),MyViewContent可组合将把这些值作为参数(他将是无状态的)。

这种方式可以确保尊重状态提升模式,因为由于MyViewContent

,你的UI的主要部分将是无状态的。

写错了viewModel(factory = MainVeiwModelFactory(context.applicationContext as Application)

你可以看到你的">";是不正确的。尝试创建一个没有上下文的空ViewModel。它用预览

解决了这个问题。

据我所知

让我们想象一下,你的屏幕被分成两个部分,每个部分有两个子部分,以此类推。

首先做这个

@Composable
fun MainScreen(viewModel: ...){
Row {
LeftSection(viewModel)
RightSection(viewModel)
}
}
@Composable
fun LeftSection(viewModel: ...){
Row {
LeftSubSection1(viewModel)
LeftSubSection2(viewModel)
}
}
RightSection looks similar

但是不可能渲染预览。所以你可以这样做(传递简单的稳定类型作为参数"自顶向下")如果你想渲染整个屏幕

@Composable
fun MainScreen(viewModel: ...){
MainContent(viewModel.prop1, viewmodel.prop2, viewmodel.prop3, viewmodel.someAction, viewmodel.someSubSectionAction, viewmodel.someSubSectionAction2)
}
fun MainContent(prop1: Int, prop2: Int, prop3: Int, onSomeAction: ()-> Unit, onSomeSubsectionAction: ()->Unit, onSomeSubsectionAction2: ()->Unit ){
Row {
LeftSection(prop1, prop2, someAction, someSubSectionAction...)
RightSection(prop2, prop3, someAction, someSubSectionAction2...)
}
}
@Composable
fun LeftSection(prop1: Int, prop2: Int, onSomeAction: ()-> Unit, onSomeSubsectionAction: ()->Unit ){
Row {
LeftSubSection1(prop1, onSomeSubsectionAction, ...)
LeftSubSection2(prop2, onSomeSubsectionAction, ...)
}
}
RightSection looks similar

由于需要传递大量的参数,其中一些参数是这个可组合的不需要的,但是嵌套的 需要。因此,您可以将预览中可以创建的所有内容(不仅仅是基本类型)作为参数传递,但要像这样组织

@Composable
fun MainScreen(viewModel: ...) {
MainContent(
leftContent = {
LeftSection(
prop = viewModel.prop1,
onSomeAction = { (viewModel.onSomeAction()) },
subSection1 = { 
LeftSubSection1(viewModel.prop2) 
},
subSection2 = { 
LeftSubSection1(onSomeSubsectionAction = { viewModel.onSomeSubsectionAction() }) 
}
)
},
rightContent = {
RightSection(
prop = viewModel.prop2,
onSomeAction = { (viewModel.onSomeAction2()) },
subSection1 = { 
RightSubSection1(viewModel.prop2) 
},
subSection2 = { 
RightSubSection1(onSomeSubsectionAction = { viewModel.onSomeSubsectionAction2() }))
}
})
}
@Composable
private fun MainContent(
leftContent: @Composable () -> Unit,
rightContent: @Composable () -> Unit,
) {
Row {
leftContent()
rightContent()
}
}
@Composable
fun LeftSection(
prop: Int,
onSomeAction: () -> Unit,
subSection1: @Composable () -> Unit,
subSection2: @Composable () -> Unit
) {
// use prop and onSomeAction
Row {
subSection1()
subSection2()
}    
}
RightSection looks similar

您可以看到,视图模型仅位于有状态的MainScreen中,您可以呈现MainContent和其他可组合段

如果需要预览使用上下文的函数,则需要使用安全强制转换。以下是我在向Coil注入httpClient时的操作方法:

val context = LocalContext.current
val appApi = remember { (context.applicationContext as? AppApiProvider)?.provideAppApi() }
val imageLoader = remember {
ImageLoader.Builder(context)
.apply { if (appApi != null) okHttpClient(UiComponent.get(appApi).httpClient) }
...
.build()

最新更新