В качестве примера я использую пример Google - GithubBrowserSample (я использую версию Java, ее можно найти здесь )
Чего хотите достичь:
- Начало действия с дополнительным намерением
- Добавление дополнительного в конструктор viewModel
Модуль моей активности (ведь я хочу передать намерение):
@Module
public abstract class AddEditTaskModule {
@Provides
@Nullable
static String provideTaskId(AddEditTaskActivity activity) {
return activity.getIntent().getStringExtra(AddEditTaskActivity.EDIT_TASK_ID);
}
@ContributesAndroidInjector
abstract AddEditTaskFragment contributeAddEditTaskFragment();
@Binds
@IntoMap
@ViewModelScope(AddEditTaskViewModel.class)
abstract ViewModel bindAddEditTaskViewModel(AddEditTaskViewModel viewModel);
}
моя фабрика:
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
Timber.d("@Inject constructor");
this.creators = creators;
}
@SuppressWarnings("unchecked")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
Timber.e(e);
throw new RuntimeException(e);
}
}
}
в примере Google - фабрика аннотирована как @Singleton
В моем случае - я не могу использовать его как одиночный, потому что мне нужно связатьviewModel в модуле активности - передать оттуда intentExtra.
ТАК, это работает, но каждый раз, когда я пытаюсь получить viewModel с помощью
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
AddEditTaskViewModel mViewModel =
ViewModelProviders.of(this, viewModelFactory).get(AddEditTaskViewModel.class);
}
, создается новая фабрика.
Это кажется мне очень неправильным.Что я делаю не так?
Редактировать:
Решение №1:
Создать собственную фабрику для этого вида ViewModels
public class AddEditTaskViewModel extends ViewModel {
...
public static class Factory extends ViewModelProvider.NewInstanceFactory{
@NonNull
private final Application application;
@Nullable
private final String taskId;
@Inject
public Factory(@NonNull Application application, @Nullable String taskId){
Timber.d("Constructor");
this.application = application;
this.taskId = taskId;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new AddEditTaskViewModel(application, taskId);
}
}
}
ActivtyModule:
@Module
public abstract class AddEditTaskModule {
@Provides
@Nullable
static String provideTaskId(AddEditTaskActivity activity) {
return activity.getIntent().getStringExtra(AddEditTaskActivity.EDIT_TASK_ID);
}
@ContributesAndroidInjector
abstract AddEditTaskFragment contributeAddEditTaskFragment();
@Provides
static AddEditTaskViewModel.Factory bindAddEditTaskViewModelFactory(Application application, @Nullable String id){
return new AddEditTaskViewModel.Factory(application, id);
}
}
сейчас, используйте в нашей деятельности и фрагменты в этой деятельности:
@Inject
AddEditTaskViewModel.Factory viewModelFactory;
AddEditTaskViewModel mViewModel =
ViewModelProviders.of(this, viewModelFactory).get(AddEditTaskViewModel.class);
Это также работает.Проблема в том, что в этом случае мне нужно скопировать конструктор ViewModel 3 (!!) раза: 1. в модуле кинжала, 2. на фабрике, 3. в viewModel.
И я не могу просто использовать:
@Binds
abstract AddEditTaskViewModel.Factory bindAddEditTaskViewModelFactory(AddEditTaskViewModel.Factory factory);
потому что - круговая зависимость
Это решение мне кажется плохим.
Решение № 2:
Используйте два ViewModelFactory.
Первый глобальный, помеченный @Singleton
.Ведьма будет использоваться для ViewModel без случаев, таких как «добавить дополнительные намерения в ViewModel».
Второй - справка ViewModelFactory, но без аннотации @Singleton
.Должен использоваться в модуле активности с тегом @Named
следующим образом:
@Binds
@Named(AddEditTaskViewModel.TAG)
abstract ViewModelProvider.Factory bindAddEditTaskViewModelFactory(ViewModelFactoryTarget factory);
в активности / фрагменте:
@Inject
@Named(AddEditTaskViewModel.TAG)
ViewModelProvider.Factory viewModelFactory;
Хорошо, это кажется лучшим решением, чем любое из вышеперечисленного, кроме нескольких«но»:
- У меня есть два идентичных класса ViewModelFactory, которые отличаются только аннотацией
@Singleton
. - Использование
@Named
не очень элегантно, как для меня.