带有 Dagger 2 的 ViewModelProviders,无法掌握概念
ViewModelProviders with Dagger 2, not able to grasp the concept
我有这样的改装服务
public interface BrandsService {
@GET("listBrand")
Call<List<Brand>> getBrands();
}
然后我有一个存储库来从这样的 api 中获取数据
public class BrandsRepository {
public static final String TAG = "BrandsRepository";
MutableLiveData<List<Brand>> mutableLiveData;
Retrofit retrofit;
@Inject
public BrandsRepository(Retrofit retrofit) {
this.retrofit = retrofit;
}
public LiveData<List<Brand>> getListOfBrands() {
// Retrofit retrofit = ApiManager.getAdapter();
final BrandsService brandsService = retrofit.create(BrandsService.class);
Log.d(TAG, "getListOfBrands: 00000000000 "+retrofit);
mutableLiveData = new MutableLiveData<>();
Call<List<Brand>> retrofitCall = brandsService.getBrands();
retrofitCall.enqueue(new Callback<List<Brand>>() {
@Override
public void onResponse(Call<List<Brand>> call, Response<List<Brand>> response) {
mutableLiveData.setValue(response.body());
}
@Override
public void onFailure(Call<List<Brand>> call, Throwable t) {
t.printStackTrace();
}
});
return mutableLiveData;
}
}
我正在通过注入 Retrofit 来使用 Dagger2 的构造函数注入。
然后我有一个像这样的 ViewModel
public class BrandsViewModel extends ViewModel{
BrandsRepository brandsRepository;
LiveData<List<Brand>> brandsLiveData;
@Inject
public BrandsViewModel(BrandsRepository brandsRepository) {
this.brandsRepository = brandsRepository;
}
public void callService(){
brandsLiveData = brandsRepository.getListOfBrands();
}
public LiveData<List<Brand>> getBrandsLiveData() {
return brandsLiveData;
}
}
要在 BrandsRepository 中注入 Retrofit,我必须像那样注入 BrandsRepository。
然后我有这样的 MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Inject
BrandsViewModel brandsViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MainApplication)getApplication()).getNetComponent().inject(this);
// BrandsViewModel brandsViewModel = ViewModelProviders.of(this).get(BrandsViewModel.class);
brandsViewModel.callService();
LiveData<List<Brand>> brandsLiveData = brandsViewModel.getBrandsLiveData();
brandsLiveData.observe(this, new Observer<List<Brand>>() {
@Override
public void onChanged(@Nullable List<Brand> brands) {
Log.d(TAG, "onCreate: "+brands.get(0).getName());
}
});
}
}
BrandsViewModel 是使用 Dagger2 而不是 ViewModelProviders 注入的。这工作正常但是当我尝试通过取消注释来使用 ViewModelProviders 时,dagger 给我一个明显的错误。获取 ViewModel 的正确方法是使用 ViewModelProviders,但我将如何在注入这样的改造时实现这一点。
答案基于android-architecture-components。
您可以在 Dagger 中使用 Map 多重绑定。
首先,像这样声明映射键。
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
二、制作地图
@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
}
接下来,创建工厂文件来处理使用键选择 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)
}
}
}
最后,在您的 activity 或片段中注入工厂。
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)
}
通过这种方式,您可以在 ViewModel 中注入存储库。
class SearchViewModel @Inject constructor(repoRepository: RepoRepository) : ViewModel() {
}
编辑:重要说明。要使用 Jetpack ViewModel,您不需要 map-multibinding。继续阅读。
答案可以比 Mumi 的方法更简单,即在组件上公开 ViewModel:
@Singleton
@Component(modules={...})
public interface SingletonComponent {
BrandsViewModel brandsViewModel();
}
现在您可以在 ViewModelFactory 内部的组件上访问此方法:
// @Inject
BrandsViewModel brandsViewModel;
...
brandsViewModel = new ViewModelProvider(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);
所有这些都可以用 Kotlin 简化和隐藏:
inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
ViewModelProvider(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)
}
现在可以让你做
brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }
现在 BrandsViewModel
可以从 Dagger 接收参数:
class BrandsViewModel @Inject constructor(
private val appContext: Context,
/* other deps */
): ViewModel() {
...
}
尽管如果 Provider<BrandsViewModel>
从 Dagger 中暴露出来,意图可能会更清晰
interface SingletonComponent {
fun brandsViewModel(): Provider<BrandsViewModel>
}
brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }
有了来自 android-ktx
的一些额外技巧,您甚至可以做到
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
crossinline creator: () -> T
): Lazy<T> {
return createViewModelLazy(T::class, storeProducer = {
viewModelStore
}, factoryProducer = {
object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(
modelClass: Class<T>
): T = creator.invoke() as T
}
})
}
然后
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by fragmentViewModels {
singletonComponent.brandsViewModelFactory().get()
}
其中brandsViewModelFactory()
是
fun brandsViewModelFactory(): Provider<BrandsViewModel>
我有这样的改装服务
public interface BrandsService {
@GET("listBrand")
Call<List<Brand>> getBrands();
}
然后我有一个存储库来从这样的 api 中获取数据
public class BrandsRepository {
public static final String TAG = "BrandsRepository";
MutableLiveData<List<Brand>> mutableLiveData;
Retrofit retrofit;
@Inject
public BrandsRepository(Retrofit retrofit) {
this.retrofit = retrofit;
}
public LiveData<List<Brand>> getListOfBrands() {
// Retrofit retrofit = ApiManager.getAdapter();
final BrandsService brandsService = retrofit.create(BrandsService.class);
Log.d(TAG, "getListOfBrands: 00000000000 "+retrofit);
mutableLiveData = new MutableLiveData<>();
Call<List<Brand>> retrofitCall = brandsService.getBrands();
retrofitCall.enqueue(new Callback<List<Brand>>() {
@Override
public void onResponse(Call<List<Brand>> call, Response<List<Brand>> response) {
mutableLiveData.setValue(response.body());
}
@Override
public void onFailure(Call<List<Brand>> call, Throwable t) {
t.printStackTrace();
}
});
return mutableLiveData;
}
}
我正在通过注入 Retrofit 来使用 Dagger2 的构造函数注入。 然后我有一个像这样的 ViewModel
public class BrandsViewModel extends ViewModel{
BrandsRepository brandsRepository;
LiveData<List<Brand>> brandsLiveData;
@Inject
public BrandsViewModel(BrandsRepository brandsRepository) {
this.brandsRepository = brandsRepository;
}
public void callService(){
brandsLiveData = brandsRepository.getListOfBrands();
}
public LiveData<List<Brand>> getBrandsLiveData() {
return brandsLiveData;
}
}
要在 BrandsRepository 中注入 Retrofit,我必须像那样注入 BrandsRepository。 然后我有这样的 MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Inject
BrandsViewModel brandsViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MainApplication)getApplication()).getNetComponent().inject(this);
// BrandsViewModel brandsViewModel = ViewModelProviders.of(this).get(BrandsViewModel.class);
brandsViewModel.callService();
LiveData<List<Brand>> brandsLiveData = brandsViewModel.getBrandsLiveData();
brandsLiveData.observe(this, new Observer<List<Brand>>() {
@Override
public void onChanged(@Nullable List<Brand> brands) {
Log.d(TAG, "onCreate: "+brands.get(0).getName());
}
});
}
}
BrandsViewModel 是使用 Dagger2 而不是 ViewModelProviders 注入的。这工作正常但是当我尝试通过取消注释来使用 ViewModelProviders 时,dagger 给我一个明显的错误。获取 ViewModel 的正确方法是使用 ViewModelProviders,但我将如何在注入这样的改造时实现这一点。
答案基于android-architecture-components。
您可以在 Dagger 中使用 Map 多重绑定。
首先,像这样声明映射键。
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
二、制作地图
@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
}
接下来,创建工厂文件来处理使用键选择 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)
}
}
}
最后,在您的 activity 或片段中注入工厂。
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)
}
通过这种方式,您可以在 ViewModel 中注入存储库。
class SearchViewModel @Inject constructor(repoRepository: RepoRepository) : ViewModel() {
}
编辑:重要说明。要使用 Jetpack ViewModel,您不需要 map-multibinding。继续阅读。
答案可以比 Mumi 的方法更简单,即在组件上公开 ViewModel:
@Singleton
@Component(modules={...})
public interface SingletonComponent {
BrandsViewModel brandsViewModel();
}
现在您可以在 ViewModelFactory 内部的组件上访问此方法:
// @Inject
BrandsViewModel brandsViewModel;
...
brandsViewModel = new ViewModelProvider(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);
所有这些都可以用 Kotlin 简化和隐藏:
inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
ViewModelProvider(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)
}
现在可以让你做
brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }
现在 BrandsViewModel
可以从 Dagger 接收参数:
class BrandsViewModel @Inject constructor(
private val appContext: Context,
/* other deps */
): ViewModel() {
...
}
尽管如果 Provider<BrandsViewModel>
从 Dagger 中暴露出来,意图可能会更清晰
interface SingletonComponent {
fun brandsViewModel(): Provider<BrandsViewModel>
}
brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }
有了来自 android-ktx
的一些额外技巧,您甚至可以做到
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
crossinline creator: () -> T
): Lazy<T> {
return createViewModelLazy(T::class, storeProducer = {
viewModelStore
}, factoryProducer = {
object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(
modelClass: Class<T>
): T = creator.invoke() as T
}
})
}
然后
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by fragmentViewModels {
singletonComponent.brandsViewModelFactory().get()
}
其中brandsViewModelFactory()
是
fun brandsViewModelFactory(): Provider<BrandsViewModel>