ViewModelProviders with Dagger 2, not able to grasp the concept

拥有回忆 提交于 2019-11-29 11:07:47

The answer can be simpler than Mumi's approach, which is that you expose the ViewModel on your component:

@Singleton
@Component(modules={...})
public interface SingletonComponent {
    BrandsViewModel brandsViewModel();
}

And now you can access this method on the component inside the ViewModelFactory:

// @Inject
BrandsViewModel brandsViewModel;

...
brandsViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() {
    @Override
    public <T extends ViewModel> create(Class<T> modelClazz) {
        if(modelClazz == BrandsViewModel.class) {
            return singletonComponent.brandsViewModel();
        }
        throw new IllegalArgumentException("Unexpected class: [" + modelClazz + "]");
    }).get(BrandsViewModel.class);

All this can be simplified and hidden with Kotlin:

inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
    ViewModelProviders.of(this, object: ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if(modelClass == clazz) {
                @Suppress("UNCHECKED_CAST")
                return factory() as T
            }
            throw IllegalArgumentException("Unexpected argument: $modelClass")
        }
    }).get(clazz)
}

which now lets you do

brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }

Where now BrandsViewModel can receive its parameters from Dagger:

class BrandsViewModel @Inject constructor(
    private val appContext: Context,
    /* other deps */
): ViewModel() {
    ...
}

The answer is based on android-architecture-components.

You can use Map multibinding in Dagger.

First, to declare map key like this.

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

Second, to create map.

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel::class)
    abstract fun bindUserViewModel(userViewModel: UserViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(SearchViewModel::class)
    abstract fun bindSearchViewModel(searchViewModel: SearchViewModel): ViewModel
}

Next, to create the factory file to handle the map which uses key to choose ViewModel.

@Singleton
class GithubViewModelFactory @Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

Finally, to inject factory in your activity or fragment.

class SearchFragment : Fragment(), Injectable {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        searchViewModel = ViewModelProviders.of(this, viewModelFactory)
            .get(SearchViewModel::class.java)
}

By this way, you can inject repository in your ViewModel.

class SearchViewModel @Inject constructor(repoRepository: RepoRepository) : ViewModel() {
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!