Tlaster / PreCompose

Compose Multiplatform Navigation && State Management
https://tlaster.github.io/PreCompose/
MIT License
853 stars 49 forks source link

how to inject ViewModel (PreCompose) in Composable in compose Multiplatform? #33

Closed ahmadhassan5 closed 1 year ago

Tlaster commented 1 year ago

Since the Compose compiler does not work well with inline composable for the Kotlin Native platform, you can use non-inline version of viewModel like this:

val viewModel = viewModel(HomeViewModel::class) {
   HomeViewModel()
}
ahmadhassan5 commented 1 year ago

Thanks for the reply.

I'm using same approach but how can I inject 'noteDataSource' in this case (for example, I'm using Koin)

val viewModel = viewModel(NoteListViewModel::class) { NoteListViewModel(noteDataSource = ?) }

Tlaster commented 1 year ago

You can use the get() function in koin to get the parameter you want like this:

val noteDataSource: NoteDataSource = get()
val viewModel = viewModel(NoteListViewModel::class) {
    NoteListViewModel(noteDataSource = noteDataSource)
}

or you can check out here as an example

ahmadhassan5 commented 1 year ago

Thank you for the assistance. I think, you mentioned another file mistakenly. Anyway, I used the extension function from ComposeExt.

One more thing, I placed logs inside scene composables, they were triggered multiple times on single event. I don’t, it’s expected behaviour. Isn’t it?

image
Tlaster commented 1 year ago

Since PreCompose uses AnimatedContent for navigation animation, at some point there will be two screens displayed at the same time, as long as you're not doing heavy stuff in Compose, this is fine.

Tlaster commented 1 year ago

Close as inactive.

mahramane commented 1 year ago

Hi I have the same problem How can I define preCompose ViewModel by Koin?

It is normally as follows :

module {
    single { UserRepository(client = get()) }
    viewModel { UserViewModel(userRepository = get()) }
}

and for use : private val viewModel: UserViewModel by viewModel()

But how can I use it for your library?

Tlaster commented 1 year ago

Hi I have the same problem How can I define preCompose ViewModel by Koin?

It is normally as follows :

module {
    single { UserRepository(client = get()) }
    viewModel { UserViewModel(userRepository = get()) }
}

and for use : private val viewModel: UserViewModel by viewModel()

But how can I use it for your library?

You can check out here as an example, simply define a factory and it should work as expected.

mahramane commented 1 year ago

You can check out here as an example, simply define a factory and it should work as expected.

thank you for your assistance. I think this project use old version of koin and the codes of this file that you sent has some errors. I use koin 3.4.0

mahramane commented 1 year ago
inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>
): Pair<Module, InstanceFactory<T>> {
    return factory(qualifier, definition)
}

@KoinReflectAPI
inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null
): Pair<Module, InstanceFactory<T>> {
    return factory(qualifier) { newInstance(it) }
}

I deleted the second function and changed the first function to the following code, it was fixed.

inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>
): KoinDefinition<T> {
    return factory(qualifier, definition)
}
ahmadhassan5 commented 1 year ago

I did using this extension function. Check this repository!

https://github.com/ahmadhassan5/MyNote/blob/with_precompose/shared/src/commonMain/kotlin/com/ahmadhassan/mynote/utils/Xs.kt

sleeyax commented 1 year ago
inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>
): Pair<Module, InstanceFactory<T>> {
    return factory(qualifier, definition)
}

@KoinReflectAPI
inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null
): Pair<Module, InstanceFactory<T>> {
    return factory(qualifier) { newInstance(it) }
}

I deleted the second function and changed the first function to the following code, it was fixed.

inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    noinline definition: Definition<T>
): KoinDefinition<T> {
    return factory(qualifier, definition)
}

@mahramane

Your fix seems to work in the module definition, but I still can't acquire the viewmodel like private val viewModel: UserViewModel by viewModel() in the Activity:

Type mismatch.
    Required: UserViewModel
    Found: ViewModel

How exactly did you manage to get that working? For context, I'm trying to share viewmodels between a regular android app and a compose app. Thank you in advance.

mahramane commented 1 year ago

You must use the following code in compose:

val androidModule = module {
    single { MyService() }
}

@Composable
fun App() {
    val myService = koinInject<MyService>()
}

@Composable
fun App(myService: MyService = koinInject()) {
}
sleeyax commented 1 year ago

Yeah your suggestion is correct but I think my use case is just wrong. I am trying to find a no-boilerplate way to share viewmodels between a regular android app and a compose app but that's probably not what precompose is intended for. Thanks for your help though, I appreciate it.

ahmadhassan5 commented 1 year ago

@sleeyax Check this repository, this is Compose Multiplatform app with shared ViewModel. Koin as DI.

sleeyax commented 1 year ago

How are you guys using Koin's get exactly?

For example the following suggestion doesn't work for me within a composable:

val noteDataSource: NoteDataSource = get()
val viewModel = viewModel(NoteListViewModel::class) {
    NoteListViewModel(noteDataSource = noteDataSource)
}

What does work is the following:

val noteDataSource = koinInject<NoteDataSource>()
val viewModel = viewModel(NoteListViewModel::class) {
    NoteListViewModel(noteDataSource = noteDataSource)
}

I can only seem to import koinInject here, not get. Is this normal? It appears to achieve the same result either way but I'm not sure why this is the case.

ahmadhassan5 commented 1 year ago

I have defined the extension function. Check here

sleeyax commented 1 year ago

I have defined the extension function. Check here

Awesome! Thanks a lot for your time to reply, this answers my question perfectly.