diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index d30aa8d..9614d56 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -85,6 +85,9 @@ kotlin { implementation("com.arkivanov.decompose:decompose:2.2.2-compose-experimental") implementation("com.arkivanov.decompose:extensions-compose-jetbrains:2.2.2-compose-experimental") //decompose step1 + + //koin step1 + implementation("io.insert-koin:koin-core:3.5.3") } desktopMain.dependencies { implementation(compose.desktop.currentOs) @@ -92,6 +95,10 @@ kotlin { androidMain.dependencies { implementation(libs.ktor.client.android) + + //koin step2 + implementation("io.insert-koin:koin-android:3.5.3") + } iosMain.dependencies { diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 92127a9..83d101a 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -4,6 +4,7 @@ { defaultComponentContext() } + } + + init { + loadKoinModules(modules) + } + + private val rootComponent: RootComponent by inject() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -28,9 +45,7 @@ class MainActivity : ComponentActivity() { CompositionLocalProvider( LocalImageLoader provides remember { generateImageLoader() }, ) { - val homeViewModel = HomeViewModel() - val root = DefaultRootComponent(defaultComponentContext(), homeViewModel) - RootContent(root, modifier = Modifier) + RootContent(rootComponent, modifier = Modifier) } } } diff --git a/composeApp/src/androidMain/kotlin/com/codingambitions/kmpapp2/MainApplication.kt b/composeApp/src/androidMain/kotlin/com/codingambitions/kmpapp2/MainApplication.kt new file mode 100644 index 0000000..8be3b19 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/codingambitions/kmpapp2/MainApplication.kt @@ -0,0 +1,17 @@ +package com.codingambitions.kmpapp2 + +import android.app.Application +import di.initKoin +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger + +class MainApplication: Application() { + override fun onCreate() { + super.onCreate() + + initKoin { + androidContext(this@MainApplication) + androidLogger() + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/HomeRepository.kt b/composeApp/src/commonMain/kotlin/HomeRepository.kt index 0bddeb2..4246b5b 100644 --- a/composeApp/src/commonMain/kotlin/HomeRepository.kt +++ b/composeApp/src/commonMain/kotlin/HomeRepository.kt @@ -1,10 +1,13 @@ import apiClient.httpClient import data.Product +import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import kotlinx.coroutines.flow.flow -class HomeRepository { +class HomeRepository( + private val httpClient: HttpClient +) { suspend fun getProductsApi(): List { val response = httpClient.get("https://fakestoreapi.com/products") diff --git a/composeApp/src/commonMain/kotlin/HomeViewModel.kt b/composeApp/src/commonMain/kotlin/HomeViewModel.kt index d359541..e87ef75 100644 --- a/composeApp/src/commonMain/kotlin/HomeViewModel.kt +++ b/composeApp/src/commonMain/kotlin/HomeViewModel.kt @@ -5,13 +5,13 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class HomeViewModel : ViewModel() { +class HomeViewModel( + private val homeRepository: HomeRepository +) : ViewModel() { private val _products = MutableStateFlow>(listOf()) val products = _products.asStateFlow() - private val homeRepository = HomeRepository() - init { viewModelScope.launch { homeRepository.getProducts().collect { products -> diff --git a/composeApp/src/commonMain/kotlin/di/CommonModule.kt b/composeApp/src/commonMain/kotlin/di/CommonModule.kt new file mode 100644 index 0000000..b7325ce --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/CommonModule.kt @@ -0,0 +1,26 @@ +package di + +import HomeRepository +import HomeViewModel +import org.koin.dsl.module +import root.DefaultRootComponent +import root.RootComponent + +fun commonModule() = networkModule() + module { + + single { + HomeRepository(get()) + } + + single { + HomeViewModel(get()) + } + + single { + DefaultRootComponent( + componentContext = get(), + homeViewModel = get() + ) + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/Koin.kt b/composeApp/src/commonMain/kotlin/di/Koin.kt new file mode 100644 index 0000000..0b5bd7f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/Koin.kt @@ -0,0 +1,13 @@ +package di + +import org.koin.core.context.startKoin +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration + +fun initKoin( + additionalModules: List = emptyList(), + appDeclaration: KoinAppDeclaration = {} +) = startKoin { + appDeclaration() + modules(additionalModules + commonModule() + platformModule()) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/NetworkModule.kt b/composeApp/src/commonMain/kotlin/di/NetworkModule.kt new file mode 100644 index 0000000..fdd0de2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/NetworkModule.kt @@ -0,0 +1,20 @@ +package di + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import org.koin.dsl.module + +fun networkModule() = module { + single { + HttpClient { + install(ContentNegotiation) { + json(Json { + prettyPrint = true + ignoreUnknownKeys = true + }) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/PlatformModule.kt b/composeApp/src/commonMain/kotlin/di/PlatformModule.kt new file mode 100644 index 0000000..5c5cdd6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/PlatformModule.kt @@ -0,0 +1,5 @@ +package di + +import org.koin.dsl.module + +fun platformModule() = module {} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/di/KoinJvm.kt b/composeApp/src/desktopMain/kotlin/di/KoinJvm.kt new file mode 100644 index 0000000..30738ae --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/di/KoinJvm.kt @@ -0,0 +1,12 @@ +package di + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import org.koin.dsl.module + +val jvmModule = module { + single { LifecycleRegistry() } + single { DefaultComponentContext(lifecycle = get()) } +} +fun startKoinJvm() = initKoin(additionalModules = listOf(jvmModule)) \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/main.kt index 6b1666e..6b5916f 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/main.kt @@ -8,20 +8,20 @@ import com.arkivanov.essenty.lifecycle.LifecycleRegistry import com.seiko.imageloader.ImageLoader import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.defaultImageResultMemoryCache +import di.startKoinJvm import okio.Path.Companion.toOkioPath import root.DefaultRootComponent +import root.RootComponent import root.RootContent import java.io.File + +val koin = startKoinJvm().koin + fun main() = application { Window(onCloseRequest = ::exitApplication, title = "KmpApp2") { - val homeViewModel = HomeViewModel() - val root = - DefaultRootComponent( - componentContext = DefaultComponentContext(LifecycleRegistry()), - homeViewModel - ) - RootContent(root, modifier = Modifier) + val rootComponent = koin.get() + RootContent(rootComponent, modifier = Modifier) } } diff --git a/composeApp/src/iosMain/kotlin/di/DependencyInjection.kt b/composeApp/src/iosMain/kotlin/di/DependencyInjection.kt new file mode 100644 index 0000000..bce7163 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/di/DependencyInjection.kt @@ -0,0 +1,21 @@ +package di + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import org.koin.core.Koin +import org.koin.dsl.module +import root.RootComponent + +val iosModule = module { + single { LifecycleRegistry() } + single { DefaultComponentContext(lifecycle = get()) } +} + +fun initKoinIOS() = initKoin(additionalModules = listOf(iosModule)) + +val Koin.rootComponent: RootComponent + get() = get() + +val Koin.lifecycleRegistry: LifecycleRegistry + get() = get() \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/di/KoinJs.kt b/composeApp/src/jsMain/kotlin/di/KoinJs.kt new file mode 100644 index 0000000..2e9efd3 --- /dev/null +++ b/composeApp/src/jsMain/kotlin/di/KoinJs.kt @@ -0,0 +1,13 @@ +package di + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import org.koin.dsl.module + + +val jsModule = module { + single { LifecycleRegistry() } + single { DefaultComponentContext(lifecycle = get()) } +} +fun startKoinJs() = initKoin(additionalModules = listOf(jsModule)) \ No newline at end of file diff --git a/composeApp/src/jsMain/kotlin/main.kt b/composeApp/src/jsMain/kotlin/main.kt index 2aa9cf5..c60db33 100644 --- a/composeApp/src/jsMain/kotlin/main.kt +++ b/composeApp/src/jsMain/kotlin/main.kt @@ -1,21 +1,21 @@ -import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.window.CanvasBasedWindow -import androidx.compose.ui.window.Window -import com.arkivanov.decompose.DefaultComponentContext -import com.arkivanov.essenty.lifecycle.LifecycleRegistry import com.seiko.imageloader.ImageLoader import com.seiko.imageloader.LocalImageLoader import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.defaultImageResultMemoryCache +import di.startKoinJs import okio.FileSystem import org.jetbrains.skiko.wasm.onWasmReady -import root.DefaultRootComponent +import root.RootComponent import root.RootContent + +val koin = startKoinJs().koin + @OptIn(ExperimentalComposeUiApi::class) fun main() { @@ -24,12 +24,7 @@ fun main() { CompositionLocalProvider( LocalImageLoader provides remember { generateImageLoader() }, ) { - val homeViewModel = HomeViewModel() - val root = - DefaultRootComponent( - componentContext = DefaultComponentContext(LifecycleRegistry()), - homeViewModel - ) + val root = koin.get() RootContent(root, modifier = Modifier) } } diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 66c59c0..0ce98d6 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 108200342B56FD090037B781 /* Koin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108200332B56FD090037B781 /* Koin.swift */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; /* End PBXBuildFile section */ @@ -16,8 +17,9 @@ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 108200332B56FD090037B781 /* Koin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Koin.swift; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = .app; path = KmpApp2.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; @@ -65,6 +67,7 @@ 7555FF8C242A565B00829871 /* Info.plist */, 2152FB032600AC8F00CF470E /* iOSApp.swift */, 058557D7273AAEEB004C7B11 /* Preview Content */, + 108200332B56FD090037B781 /* Koin.swift */, ); path = iosApp; sourceTree = ""; @@ -168,6 +171,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 108200342B56FD090037B781 /* Koin.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, 7555FF83242A565900829871 /* ContentView.swift in Sources */, ); diff --git a/iosApp/iosApp/Koin.swift b/iosApp/iosApp/Koin.swift new file mode 100644 index 0000000..165c2f4 --- /dev/null +++ b/iosApp/iosApp/Koin.swift @@ -0,0 +1,22 @@ +// +// Koin.swift +// iosApp +// +// Created by Sunil Kumar on 16/01/24. +// Copyright © 2024 orgName. All rights reserved. +// + +import Foundation +import ComposeApp + + +private var _koin: Koin_coreKoin? = nil +var koin: Koin_coreKoin { + return _koin! +} + + +func startKoin() { + let koinApplication = DependencyInjectionKt.doInitKoinIOS() + _koin = koinApplication.koin +} diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index 1d1b1f5..af22635 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -8,7 +8,7 @@ struct iOSApp: App { var appDelegate: AppDelegate var rootHolder: RootHolder { - appDelegate.rootHolder + appDelegate.getRootHolder() } var body: some Scene { @@ -40,15 +40,8 @@ class RootHolder: ObservableObject { let root: RootComponent init() { - lifecycle = LifecycleRegistryKt.LifecycleRegistry() - lifecycle.subscribe(callbacks: LifecycleCallbacksImpl()) - let homeViewModel = HomeViewModel() - - root = DefaultRootComponent( - componentContext: DefaultComponentContext(lifecycle: lifecycle), - homeViewModel: homeViewModel - ) - + lifecycle = koin.lifecycleRegistry + root = koin.rootComponent LifecycleRegistryExtKt.create(lifecycle) } @@ -59,5 +52,20 @@ class RootHolder: ObservableObject { } class AppDelegate: NSObject, UIApplicationDelegate { - let rootHolder: RootHolder = RootHolder() + var rootHolder: RootHolder? = nil + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + + startKoin() + rootHolder = RootHolder() + + return true + } + + func getRootHolder() -> RootHolder { + if(rootHolder == nil) { + rootHolder = RootHolder() + } + return rootHolder! + } }