androidkotlin-multiplatformkoin

Compose Multiplatform Koin, No definition error


I keep getting NO DEFINITION error in KMM compose project. I use Decompose, Ktorfit and koin.

No definition found for type 'com.marketmlm.modern.features.splash.networking.repo.SplashRepo'. Check your Modules configuration and add missing type and/or qualifier!

Here is the Implementation:

in common:Main:

    fun doInitKoinApplication(
    platformModules: List<Module> = listOf(),
): KoinApplication {
    val koinApp = koinApplication {
        modules(
            listOf(
                module { includes(platformModules) },
                module { networkingModule(enableNetworkLogs = false) },
                permissionModule,
            )
        )
        createEagerInstances()
    }
    return startKoin(koinApp)
}

fun networkingModule(enableNetworkLogs: Boolean) = module {
    single { createJson() }
    single { createHttpClient(get(), get(), enableNetworkLogs = enableNetworkLogs) }
    single { CoroutineScope(Dispatchers.Default + SupervisorJob()) }

    categoriesModule()
    splashModule()
}

fun createJson() = Json { isLenient = true; ignoreUnknownKeys = true }

fun createHttpClient(
    httpClientEngine: HttpClientEngine,
    json: Json,
    enableNetworkLogs: Boolean
): Ktorfit {
    val client = HttpClient(httpClientEngine) {
        defaultRequest { url("https://pokeapi.co/api/v2/") }
        install(ContentNegotiation) { json(json) }
        if (enableNetworkLogs) {
            install(Logging) {
                logger = Logger.DEFAULT
                level = LogLevel.INFO
            }
        }
    }
    return Ktorfit.Builder().httpClient(client).build()
}

val LocalKoinApplication = compositionLocalOf {
    doInitKoinApplication()
}

in androidApp:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        WindowCompat.setDecorFitsSystemWindows(window, false)
        doInitKoinApplication(
            listOf(
                module {
                    single<Context> { applicationContext }
                    single<Activity> { this@MainActivity }
                }
            )
        )
        val root = MarketMLMRootImpl(
            componentContext = defaultComponentContext(),
            stringProvider = StringsProvider(this),
        )
        setContent {
            CoreApp(root = root)
        }
    }
}

this is the CoreApp function:

@Composable
fun CoreApp(root: MarketMLMRoot) {

    val windowsInsets = rememberWindowInsetsController()

    LaunchedEffect(Unit) {
        windowsInsets?.setIsNavigationBarsVisible(false)
        windowsInsets?.setIsStatusBarsVisible(false)
        windowsInsets?.setSystemBarsBehavior(SystemBarsBehavior.Immersive)
    }

    val stack by root.backstack.subscribeAsState()



    Column(
        modifier = Modifier.fillMaxSize()
            .background(MaterialTheme.colorScheme.surface),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Children(
            stack = stack,
            modifier = Modifier.weight(1f),
        ) { childCreated ->
            when (val child = childCreated.instance) {
                is MarketMLMRoot.MainDestinationChild.BottomNavHolder -> RootBottomNavScreen(
                    child.component
                )

                is MarketMLMRoot.MainDestinationChild.Splash -> SplashScreen(
                    component = child.component,
                    onSplashFinished = {
                        child.component.onSplashTimeFinish()
                    })

                else -> {}
            }
        }

    }

}

and this is the module definition:

fun Module.splashModule() {
    singleOf(::injectSplashService)
    singleOf(::SplashRepo)
}

private fun injectSplashService(ktorfit: Ktorfit): SplashService = ktorfit.create()

and this the ViewModel:

class SplashViewModel : InstanceKeeper.Instance, KoinComponent {
    private val viewModelScope = CoroutineScope(Dispatchers.Unconfined)
    private val _uiState = MutableStateFlow<SplashUIState>(SplashUIState.Waiting)
    val uiState: StateFlow<SplashUIState> = _uiState

    init {
        checkOnboardingState()
    }

    private fun checkOnboardingState() {
        viewModelScope.launch {
            delay(3000)
            _uiState.update {
                SplashUIState.Success
            }
        }
    }

    override fun onDestroy() {
        viewModelScope.cancel()
    }

    private val splashRepo :SplashRepo by inject() // ERROR LINE

    private val _appInfoState = MutableStateFlow<Loadable<AppInfoModel>>(Loadable.Loading)
    val appInfoState = _uiState.asStateFlow()

    fun getAppInfo() = viewModelScope.launch {
        splashRepo.getAppInfo()
            .onSuccess { _appInfoState.value = Loadable.Success(it) }
            .onFailure { _appInfoState.value = Loadable.Error(it) }
    }
}

What did I do wrong?


Solution

  • OK. So I ended up using my own DI system. To begin with, we need a Ktorfit creator fun:

    fun createKtorfit(): Ktorfit {
        return ktorfit {
    
            baseUrl("${baseUrl}${api}")
    
            httpClient(HttpClient {
    
                // Set kotlin serialization as the json converter
                install(ContentNegotiation) {
                    Json
                    register(
                        ContentType.Any, KotlinxSerializationConverter(
                            Json {
                                prettyPrint = true
                                isLenient = true
                                ignoreUnknownKeys = true
                            }
                        )
                    )
                }
    
                // Show the http log info
                install(Logging) {
                    logger = object : Logger {
                        override fun log(message: String) {
                            println("$LOG_HTTP $message")
                        }
                    }
                    level = LogLevel.ALL
                }
    
                
                }
            }
            )
        }
    }
    

    In my networking system I have a repo and a service interface class:

    interface CategorizeService {
        @POST("get/categories")
        @Headers("Content-Type: application/json")
        suspend fun getCategories(@Body getCategoriesInputs: GetCategoriesInputs): CategoriesModel
    }
    
    
    class CategoriesRepo(private val categorizeService: CategorizeService) {
            suspend fun getCategories(id: String = "", categoriesType: CategoriesType) = runCatching {
                categorizeService.getCategories(
                    GetCategoriesInputs(id = id, categoryType = categoriesType)
                )
            }
        }
    
    

    For the Di I created a object class and I Inject the service to the repo:

    object DiCategorize{
        fun categorizeRepo(): CategoriesRepo {
            return CategoriesRepo(injectCategoriesService(createKtorfit()))
        }
    }
    
    private fun injectCategoriesService(ktorfit: Ktorfit): CategorizeService = ktorfit.create()
    

    Then I simply use the object class to create an instance of my repo:

    class CategorizeViewModel: InstanceKeeper.Instance, KoinComponent {
        private val viewModelScope = CoroutineScope(Dispatchers.Unconfined)
    
        private val categorizeRepo:CategoriesRepo = DiCategorize.categorizeRepo()
    }
    

    I hope it will be useful for you (;