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