Compose Multiplatform Koin, No definition error

147 views Asked by At

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?

1

There are 1 answers

0
Shadman Adman On BEST ANSWER

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 (;