InsertKoinIO / koin

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

Cannot resolve viewModel injection with delegate style declaration #56

Closed silviorp closed 6 years ago

silviorp commented 6 years ago

I'm trying to follow the documentation using delegate style declaration of ViewModels but I cannot resolve the import in Android Studio.

screen shot 2018-02-16 at 18 05 14

This is my code: screen shot 2018-02-16 at 18 07 08

What I'm doing wrong? Any advice is appreciated.

fredy-mederos commented 6 years ago

Your code looks good to me. Are you shure you are compiling koin-android-architecture 0.8.2 ??

// Koin for Android Architecture Components
compile "org.koin:koin-android-architecture:0.8.2"
caleb-allen commented 6 years ago

Are you importing correctly?

import org.koin.android.architecture.ext.viewModel
caleb-allen commented 6 years ago

Maybe try declaring the type in the value?

val myViewModel : MyViewModel by viewModel()
silviorp commented 6 years ago

The problem was the version I was using, 0.8.0. But now that I was able to import, there's another problem:

screen shot 2018-02-16 at 21 56 32

Do you guys know how should I proceed? I really like to get the viewModel instance inside my BaseActivity to handle snackbar, toast and other generic observers from my ViewModel.

caleb-allen commented 6 years ago

You don't need the angle brackets for the viewmodel I believe

caleb-allen commented 6 years ago

Scratch that I misunderstood that your activity was generic

caleb-allen commented 6 years ago

What about

val myViewModel : MyViewModel<T> by viewModel()
arnaudgiuliani commented 6 years ago

Hello,

we can't use generic type T with infered Kotlin parameter :/

Best thing is to add a method to allow such declaration: val myViewModel : MyViewModel<T> by viewModel(T::class) or something like that.

silviorp commented 6 years ago

What I trying to do is receive the type of View Model as parameter and inject a new instance of this type. There's no MyViewModel. The ViewModel types would be received in a generic way. The problem is because it's an inline function with reified type, it's not allowed to receive the type like we have here. It would be amazing to be able to use like this: val myViewModel : T by viewModel(T::class) or val myViewModel: T by viewModel()

I'm going to try to strip the injection source code and implement a ViewModelFactory to initialize it inside the BaseActivity but this would not benefit from the rest of the library features

silviorp commented 6 years ago

After 2 days I had no success trying to make this work. I'm having a hard time working with generics in kotlin, something I could do in Java very easily. I don't want to try another libraries for dependency injection but since I'm in the beginning of a new project in the company I work for, if I can't solve this quickly I'll be forced to try another libs or maybe, write this in Java.

caleb-allen commented 6 years ago

Does

 val myViewModel : T by viewModel()

Not work?

arnaudgiuliani commented 6 years ago

Yes Ok. Sorry there was a mistake in my answer. You need to write:

val model: T by viewModel(T::class) or val model: T by lazy {getViewModel(T::class)}

Let me write a patch 👍

arnaudgiuliani commented 6 years ago

fix is available in current alpha: 0.9.0-alpha-11

arnaudgiuliani commented 6 years ago

API has been updated to have val model: T by viewModel(<KClass<T>>) and avoid reified types this way.

Your base activity must be like:

class Base Activity<T : ViewModel>(clazz : KClass<T>){
    val model : T by viewModel(clazz)
}

We can't pass reified parameters from class.

silviorp commented 6 years ago

It worked! Thanks very much!!! I'll try make an extension or something like that to avoid having to pass the class as a parameter but for now, that's working wonderfully!!

arnaudgiuliani commented 6 years ago

Cool :) I close the issue.

jakoss commented 6 years ago

@shrpereira Did you manage to avoid having to pass the class as a parameter?

arnaudgiuliani commented 6 years ago

@shrpereira You will be able to get viewModel by class with viewModelByClass() and getViewModelByClass. This is the final version of the API fir release 0.9.0

fathallah92 commented 6 years ago

@shrpereira Have you added an extension to not pass class as a parameter to the activity ?

silviorp commented 6 years ago

@fathallah92 No. I've used the following approach:

But I'm not coding on this project for some time (it's in production, though), so, probably there are better ways to do it.

loalexzzzz commented 5 years ago

@arnaudgiuliani Are there any elegant approach that not pass class as a parameter to the activity?

ductranit commented 5 years ago

@arnaudgiuliani Are there any elegant approach that not pass class as a parameter to the activity?

Hope this isn't too late. You can try this:


open class BaseFragment<M : ViewModel> : Fragment() {
    val viewModel: M by lazy { getViewModel(viewModelClass()) }

    @Suppress("UNCHECKED_CAST")
    private fun viewModelClass(): KClass<M> {
        // dirty hack to get generic type https://stackoverflow.com/a/1901275/719212
        return ((javaClass.genericSuperclass as ParameterizedType)
            .actualTypeArguments[0] as Class<M>).kotlin
    }
}
nEdAy commented 5 years ago

@arnaudgiuliani Are there any elegant approach that not pass class as a parameter to the activity?

Hope this isn't too late. You can try this:


open class BaseFragment<M : ViewModel> : Fragment() {
    val viewModel: M by lazy { getViewModel(viewModelClass()) }

    @Suppress("UNCHECKED_CAST")
    private fun viewModelClass(): KClass<M> {
        // dirty hack to get generic type https://stackoverflow.com/a/1901275/719212
        return ((javaClass.genericSuperclass as ParameterizedType)
            .actualTypeArguments[0] as Class<M>).kotlin
    }
}

There is a problem when using databinding at the same time.

rasoulmiri commented 4 years ago

@nEdAy if use ViewDataBinding use need change 0 to 1 because ViewModel is 2 in paramaeters

open class BaseFragment<B : ViewDataBinding, M : ViewModel> : Fragment() {
    val viewModel: M by lazy { getViewModel(viewModelClass()) }

    @Suppress("UNCHECKED_CAST")
    private fun viewModelClass(): KClass<M> {
        // dirty hack to get generic type https://stackoverflow.com/a/1901275/719212
        return ((javaClass.genericSuperclass as ParameterizedType)
            .actualTypeArguments[1] as Class<M>).kotlin
    }
}
Adamshick012 commented 4 years ago

Is there an example of how this is implemented on a example Fragment? I am getting a No definition found error when I use the above example. The only way I have gotten it to work was going around the lazy and doing a loadKoinModules(module{single{ExampleViewModel()}}) inside the ExampleFragment which kind of defeats the purpose.

loalexzzzz commented 4 years ago

@arnaudgiuliani Are there any elegant approach that not pass class as a parameter to the activity?

Hope this isn't too late. You can try this:


open class BaseFragment<M : ViewModel> : Fragment() {
    val viewModel: M by lazy { getViewModel(viewModelClass()) }

    @Suppress("UNCHECKED_CAST")
    private fun viewModelClass(): KClass<M> {
        // dirty hack to get generic type https://stackoverflow.com/a/1901275/719212
        return ((javaClass.genericSuperclass as ParameterizedType)
            .actualTypeArguments[0] as Class<M>).kotlin
    }
}

I have figured out by myself, quite similar with your approach , thank you for answer, happy coding :D

tomek27 commented 4 years ago

I did it like this

abstract class BaseFragment<VM: BaseViewModel> : Fragment() {

    val viewModel: VM by lazy { getViewModel(clazz) }

    abstract var clazz: KClass<VM>

}