InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
8.76k stars 695 forks source link

Support androidx viewmodel for kotlin multiplatform #1826

Open Vivecstel opened 3 months ago

Vivecstel commented 3 months ago

https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.0-alpha03 added view model support for kotlin multiplatform. Ideally koin should support this also. Thanks

fluttertutorialin commented 3 months ago

Can you explain how to add viewmodel library in kmm

frankois1234 commented 2 months ago

Hello, some reference here

A full demo how to use viewmodel here :

It should be OK that koin move the viewmodel support to KMP.

ghost commented 2 months ago

The is the official repo used in google codelabe https://github.com/MatkovIvan/nav_cupcake

AbdelrahmanEsam commented 2 months ago

@frankois1234 it doesn't work with koin .... iam getting this error

Uncaught Kotlin exception: org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory:'viewModel class Name']' at 0 shared

also I am creating this viewModel by factory or single ..... I think koin should support the viewModel{} at the common main

Leedwon commented 2 months ago

@AbdelrahmanEsam

Inspired by https://github.com/MatkovIvan/nav_cupcake/blob/master/composeApp/src/commonMain/kotlin/com/matkovivan/nav_cupcake/ViewModels.kt which uses androidx viewmodel. I'm using the following code to make it work with Koin:

ViewModels.kt in commonMain:

internal expect fun <VM : ViewModel> viewModel(
    modelClass: KClass<VM>,
    factory: ViewModelProvider.Factory = rememberViewModelFactory()
): VM

// Can't be private as it causes Exception during IR lowering
@Composable
internal fun rememberViewModelFactory(): ViewModelProvider.Factory {
    val koin = getKoin()
    return remember {
        viewModelFactory {
            initializer { MyViewModel(myDependency = koin.get()) }
            ... // more VM initializers
        }
    }
}

ViewModels.android.kt in androidMain:

@Composable
internal actual fun <VM : ViewModel> viewModel(
    modelClass: KClass<VM>,
    factory: ViewModelProvider.Factory
): VM = androidx.lifecycle.viewmodel.compose.viewModel(modelClass.java, factory = factory)

ViewModels.ios.kt in iosMain:

@Composable
internal actual fun <VM : ViewModel> viewModel(
    modelClass: KClass<VM>,
    factory: ViewModelProvider.Factory
): VM = androidx.lifecycle.viewmodel.compose.viewModel(modelClass, factory = factory)

And wiring it in the Composables:

@Composable
fun MyScreen(
    myViewModel: MyViewModel = viewModel(MyViewModel::class)
) {

}

I doesn't guarantee that it's the most correct approach, but it works and can be a workaround until Koin officially suports it.

AbdelrahmanEsam commented 2 months ago

@Leedwon the problem is on the ios side not the android one .... android is working perfectly

class DIHelper : KoinComponent { private val notesViewModel: NotesViewModel by inject() private val noteDetailsViewModel: NoteDetailsViewModel by inject()

fun getNotesViewModel(): NotesViewModel = notesViewModel
fun getNoteDetailsViewModel(): NoteDetailsViewModel = noteDetailsViewModel

}

this is my DIHelper in the iosMain to access the viewModels in the ios app

Leedwon commented 2 months ago

@AbdelrahmanEsam I see, I'm using Compose Multiplatform, so for me the setup above does the job on both platforms. But if you are having the setup with separate UIs for iOS and Android, then I think you can take a look at joreilly FantasyPremierLeague, he's using VMs with Koin. I haven't checked the whole thing, but this PR might be helpful.

AbdelrahmanEsam commented 2 months ago

@Leedwon unfortunately his viewModels doesn't have any other injected dependencies (usecases /repositories) so he just take object from it at the view in ios side

frankois1234 commented 2 months ago

The ViewModel in the common Main is more for the Compose Multiplatform than SwiftUI.

Nothing will change for iOS UIKit/SwiftUI.

The best example is https://github.com/joreilly/FantasyPremierLeague.

Also, the usage of Koin viewmodel in the commonMain must be also for Koin annotation.

AbdelrahmanEsam commented 2 months ago

@frankois1234 that really doesn't work .... I am using the helper in the ios main module and for the android side everything working perfectly .... but when I am trying to run the ios app koin can't inject the viewModel with the following error

org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory:

frankois1234 commented 2 months ago

This issue is for demanding the implementation of koin ViewModel support in kmp commonMain project, not how to use it.

As the import org.koin.androidx.viewmodel.dsl.viewModel is not available in commonMain project but the import of androidx.lifecycle.ViewModel is now possible with the latest androidx.lifecycle:lifecycle-viewmodel dependancies

Vivecstel commented 2 months ago

This issue is for demanding the implementation of koin ViewModel support in kmp commonMain project, not how to use it.

As the import org.koin.androidx.viewmodel.dsl.viewModel is not available in commonMain project but the import of androidx.lifecycle.ViewModel is now possible with the latest androidx.lifecycle:lifecycle-viewmodel dependancies

exactly

AbdelrahmanEsam commented 2 months ago

@frankois1234 I didn't said that I am using it with koin viewModel ..... actually I am creating it with factory or single in koin .... but koin failed to give me my instance

AbdelrahmanEsam commented 2 months ago

@frankois1234 this happen in the ios side only for some reason I really don't know .... in the android side everything working perfectly

frankois1234 commented 2 months ago

@AbdelrahmanEsam Please make another issue, it's not the target of the current one

hoc081098 commented 2 months ago

https://github.com/hoc081098/kmp-viewmodel/blob/master/viewmodel-koin-compose/src/commonMain/kotlin/com/hoc081098/kmp/viewmodel/koin/compose/koinKmpViewModel.kt

I did it before AndroidX. Hope it is useful

marcellogalhardo commented 2 months ago

@arnaudgiuliani, now that JetBrains has published their artifact (which includes JS and WASM support), we should be able to add official ViewModel KMP support to Koin.

Note that the new artifact is still experimental, so we might want to wait for it to reach a more stable stage, but we can start exploring it and reporting any challenge we may find to JB.

Please see: https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.6.10-beta01

PMARZV commented 2 months ago

Experimental support for koin viewmodel multiplatform would be helpful!

arnaudgiuliani commented 2 months ago

Great, let's add it for one of next milestone quickly 💪 thanks @marcellogalhardo

frankois1234 commented 2 months ago

@arnaudgiuliani koin annotations will have the support?

arnaudgiuliani commented 2 months ago

it should be possible yes 👍

ronjunevaldoz commented 1 month ago

Following this!

arnaudgiuliani commented 1 month ago

Work in progress: https://github.com/InsertKoinIO/koin/pull/1875

arnaudgiuliani commented 1 month ago

Release in 3.6.0-Beta4 🎉

ColtonIdle commented 1 month ago

❤️

Sent from Proton Mail Android

-------- Original Message -------- On 5/17/24 12:22 PM, Arnaud Giuliani wrote:

Release in 3.6.0-Beta4 🎉

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you are subscribed to this thread.Message ID: @.***>

agabeyalioglu commented 1 month ago

Hello @arnaudgiuliani i tried 3.6.0-Beta4

factory {
    ListViewModel(get(), get(named(IODispatcher)))
}

I was creating my viewmodel like this previously. Now if i use viewModelOf(::ListViewModel) how can i specify the dispatcher parameter to use named(IODispatcher)?

DavidGrygar commented 1 month ago

Hi, can I get and use viewModelOf in commonMain if we use SwiftUI on iOS side? What import/imports is needed for this? I have 3.6.0-Beta4 version.

arnaudgiuliani commented 4 weeks ago

Hello @arnaudgiuliani i tried 3.6.0-Beta4

factory {
    ListViewModel(get(), get(named(IODispatcher)))
}

I was creating my viewmodel like this previously. Now if i use viewModelOf(::ListViewModel) how can i specify the dispatcher parameter to use named(IODispatcher)?

you need manual dsl then to describe your qualifier and use named.

arnaudgiuliani commented 4 weeks ago

Hi, can I get and use viewModelOf in commonMain if we use SwiftUI on iOS side? What import/imports is needed for this? I have 3.6.0-Beta4 version.

For compose MP for now. Still need a bit of work for Swift integration

frankois944 commented 4 weeks ago

Hi, can I get and use viewModelOf in commonMain if we use SwiftUI on iOS side? What import/imports is needed for this? I have 3.6.0-Beta4 version.

@DavidGrygar It's not that simple, take a look at https://github.com/joreilly/FantasyPremierLeague and https://github.com/joreilly/FantasyPremierLeague/issues/231.

You can use a Android ViewModel almost like a SwiftUI ViewModel but you need to manage the lifecycle yourself.

frankois944 commented 2 weeks ago

@DavidGrygar @ColtonIdle For the iOS developer hear, I made a playground with a lot of interesting things with Koin and MVVM

https://github.com/frankois944/kmp-mvvm-exploration

romanatexn commented 6 days ago

Not sure if this is the best place to ask, but related: I'm trying 3.6.0-Beta4 and everything looks great, but is it ok to instantiate a kotlin multiplatform viewmodel on iOS using a factory{} inside an Observable object and manually cancel it's scope on deinit of the Observable object?

It seems to be working fine, but I'm afraid of the viewModel comments: "You should never manually create a [ViewModel] outside of a [ViewModelProvider.Factory]"

Maybe I'm missing something important?

Context: I want to use the same ViewModel inside Compose using koinViewModel, and iOS SwiftUI using factory

frankois944 commented 6 days ago

@romanatexn The official Kotlin ViewModel is made to be working on an Android/Compose not a SwiftUI/iOS environment. I think the usage on swift language is kind of experimental as we need to fill the holes. So, I guess, this rule doesn't apply for Swift.

The most important thing is the respect of the lifecycle of the viewmodel (init()/onCleared()). The kotlin ViewModel on SwiftUI/iOS target must be used like a SwiftUI ViewModel not like an Android/Compose ViewModel, they can't be used the same way.