androidmvvmandroid-jetpack-composekoin

Rendering Issue in Jetpack Compose - Android


I am currently working with Jetpack compose, I have Koin for Dependency Injection in my Project. When I inject viewModel into @Composable function, and I observe some data from the Viewmodel, But the issue here is I can't preview my UI, It says render problem.

java.lang.IllegalStateException: KoinApplication has not been started
at org.koin.core.context.GlobalContext.get(GlobalContext.kt:36)
at org.koin.compose.KoinApplicationKt.getKoinContext(KoinApplication.kt:52)
at org.koin.compose.KoinApplicationKt.access$getKoinContext(KoinApplication.kt:1)
at org.koin.compose.KoinApplicationKt.getKoinScope(KoinApplication.kt:86)

It throws the above exception in issue panel in Preview screen.

As it says KoinApplication not started, But I have already Initialize my Koin in Application class and defined it in Manifest.

    class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidLogger()
            androidContext(this@MainApplication)
            modules(ApiModule)
            modules(appModule)
        }
    }
}
    <application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.ComposeRetrofit"
    android:name=".MainApplication"
    tools:targetApi="31">

And this is my compose code,

@Preview
@Composable
fun ViewForMainScreen(){
    MainScreen()
}

@Composable
fun getApplianceViewModel(): ApplianceViewModel {
    return koinViewModel()
}

@Composable
fun getAddressViewModel(): AddressViewModel {
    return koinViewModel()
}

@Composable
fun MainScreen(){
    val addressViewModel = getAddressViewModel()
    val address by addressViewModel.addressApiResponse.observeAsState(initial = null)

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(
            onClick = { addressViewModel.callAddressApi() },
            modifier = Modifier
                .fillMaxWidth()
                .height(40.dp)
                .padding(start = 40.dp, end = 40.dp)
        ) {
            Text(text = address?.mailBox ?: "Country")
        }
        Spacer(modifier = Modifier.height(20.dp))
        Text(
            text = address?.fullAddress ?: "Full Address",
            modifier = Modifier.padding(start = 40.dp, end = 40.dp)
        )
    }
}

What is the issue here! Any way to solve it?

Thanks For your answers in Advance!


Solution

  • Android Studio doesn't use your Application class to render previews. Therefore, dependency injection doesn't work.

    The suggested workaround for this is, to create another composable which takes the parameters from the view model.

    For example, if you have a screen called ShoppingCartScreen in which you inject a view model, create another screen with the name ShoppingCartScreenContent. ShoppingCartScreen then extracts all the data from the view model and passes it as parameters into ShoppingCartScreenContent.

    Alternatively both composables can have the same name. They will just differ by their parameter types.

    In your case, you can refactor it like this:

    @Composable
    fun MainScreen(){
        val addressViewModel = getAddressViewModel()
        val address by addressViewModel.addressApiResponse.observeAsState(initial = null)
        val onClick = remember { addressViewModel.callAddressApi() }
        MainScreen(address, onClick)
    }
    
    @Composable
    fun MainScreen(address: Address, onClick: () -> Unit){
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Button(
                onClick = onClick,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(40.dp)
                    .padding(start = 40.dp, end = 40.dp)
            ) {
                Text(text = address?.mailBox ?: "Country")
            }
            Spacer(modifier = Modifier.height(20.dp))
            Text(
                text = address?.fullAddress ?: "Full Address",
                modifier = Modifier.padding(start = 40.dp, end = 40.dp)
            )
        }
    }
    
    @Preview
    @Composable
    fun MainScreenPreview(){
        MainScreen(
            address = Address(), // Create some dummy data here
            onClick = {}
        )
    }