https://kotlinlang.org logo
#android
Title
# android
m

mnawrot

11/13/2017, 12:37 PM
hello there im trying to create generic
BaseViewModelFactory<VM>
class to provide new architecture component's view models. i wanted to call
isAssignable(...)
function before i create actual viewmodel, to check if everything is fine, but in order to do that, the viewmodel type
VM
have to be reified and i dont know how to call inline reified function from overriden
ViewModelProviders.Factory create()
function currently i got something like this :
Copy code
abstract class BaseViewModelFactory<out VM : Any>(private val viewModelClass: KClass<VM>) : ViewModelProvider.Factory {

    @Suppress("unchecked_cast")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        require(modelClass.isAssignableFrom(viewModelClass.java)) {
            "Could not instantiate ${javaClass.simpleName} class"
        }
        return createViewModel() as T
    }

    abstract protected fun createViewModel(): VM
}
is there a way to avoid passing viewModelClass in constructor and somehow use reified type parameter here?
m

mnawrot

11/13/2017, 1:56 PM
not really 😞 but thanks
y

yperess

11/13/2017, 8:08 PM
Is the goal here to use this with dependency injection?
What you're doing here doesn't really make a lot of sense, you're creating a factory that can only create one type of view model. Generally what you'd want to do is more like:
Copy code
@Singleton
class MetaverseViewModelFactory @Inject constructor(
        creators: Map<KClass<out ViewModel>, Provider<ViewModel>>
): ViewModelProvider.Factory {

    private val creators: Map<Class<out ViewModel>, Provider<ViewModel>> =
            creators.mapKeys { it.key.java }

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        Log.d("ViewModelFactory", "create($modelClass)")
        var creator = creators[modelClass]
        if (creator == null) {
            creators.entries.forEach { entry ->
                if (modelClass.isAssignableFrom(entry.key)) {
                    creator = entry.value
                    return@forEach
                }
            }
        }
        if (creator == null) {
            throw IllegalArgumentException("Unknown model class $modelClass")
        }
        try {
            return creator!!.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
Now you'll need to create a module for your view models:
Copy code
@Module
abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    abstract fun bindMainViewModel(mainViewModel: MainViewModel): ViewModel

    @Binds
    abstract fun bindViewModelFactory(factory: MetaverseViewModelFactory): ViewModelProvider.Factory
}

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
r

rkeazor

11/14/2017, 9:52 AM
The best solution here is to create a abstract inline refied function that returns the actual class. So that the child classes can ovverride it so it returns there class type
m

mnawrot

11/15/2017, 8:47 AM
there is no way to create abstract inline function, only private/final can be inlined
@yperess this makes much more sense... ill drop the idea of creating factory for every vm. thanks man
y

yperess

11/15/2017, 12:01 PM
You're welcome