skip to Main Content
Android Studio Chipmunk 2021.2.1; 
Compose Version = '1.1.1'; 
Gradle  Version 7.4.2; 
Kotlin 1.6.10;

Up to one point, everything was working. Then this error appeared and the preview stopped working when I try to call "LocalContext.current" and make "context.applicationContext as Application" both in this project and in another one. Where it used to work with "LocalContext.current"

Tried on different versions of Compose, kotlin, gradle.

Render problem

java.lang.ClassCastException: class
com.android.layoutlib.bridge.android.BridgeContext cannot be cast to
class android.app.Application
(com.android.layoutlib.bridge.android.BridgeContext and
android.app.Application are in unnamed module of loader
com.intellij.ide.plugins.cl.PluginClassLoader @3a848149)   at
com.client.personalfinance.screens.ComposableSingletons$AccountScreenKt$lambda-2$1.invoke(AccountScreen.kt:136)
  at
com.client.personalfinance.screens.ComposableSingletons$AccountScreenKt$lambda-2$1.invoke(AccountScreen.kt:133)
  at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
  at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
  at
androidx.compose.material.MaterialTheme_androidKt.PlatformMaterialTheme(MaterialTheme.android.kt:23)
  at
androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:82)
  at
androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:81)
  at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
  at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
  at
androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
  at 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)
 }

5

Answers


  1. This is a wrong line
    viewModel(factory = MainVeiwModelFactory(context.applicationContext as Application)

    You can see your "as" is incorrect.
    Try to create an empty ViewModel without context.
    It solves the problem with a preview

    Login or Signup to reply.
  2. I found the best way to get a Preview to work when you need to access something that’s Android lifecycle-specific, e.g. Application, Activity, FragmentManager, ViewModel, etc, is to create an implementation of that interface that does nothing.

    An example using FragmentManager:

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

    Preview function:

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

    Now your Preview function will render.

    You can do the same thing with ViewModel – just make your ViewModel extend an interface.

    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)
    }
    

    In your case, I would suggest doing the steps for ViewModel outlined above, and not messing around with LocalContext whatsoever in your preview.

    Login or Signup to reply.
  3. You can’t render a preview composable if you try to inject a ViewModel.

    There is plenty of way to avoid this problem. But for me, the easiest and clearest way to do this is simply to NOT RENDER a composable who have a viewModel.

    To do that you can simply extract the content of your MyView composable to another composable called MyViewContent.

    The MyView composable will declare all states that the MyViewContent will need (he will be statefull) and the MyViewContent composable will have those values as parameter (he will be stateless).

    This way you ensure to respect the state hoisting pattern because you have the major part of your UI that will be stateless thanks to MyViewContent

    Login or Signup to reply.
  4. As I understand

    Let’s imagine that your screen is divided into 2 sections, which have 2 subsections, and so on.

    First you do this

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

    But it’s impossible to render preview. So you can do like this (Pass simple stable types as parameters "top-down") if you want render whole screen

    @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
    

    And get a nightmare due to the need to pass a huge number of parameters, some of which are not needed by this composable, but are needed by the nested

    So you can pass as parameters everything that can be created in the preview (not only primitive types), but organize it like this

    @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
    

    As you can see, viewmodel is located only in statefull MainScreen, and you can render MainContent and other composable segments

    Login or Signup to reply.
  5. If you need preview of the function, that uses context you need to use safe cast. Here’s how I do it when inject httpClient to Coil.

    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()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search