Android dagger2 вводит намерение в viewModel, используя Factory - PullRequest
0 голосов
/ 25 февраля 2019

В качестве примера я использую пример Google - GithubBrowserSample (я использую версию Java, ее можно найти здесь )

Чего хотите достичь:

  1. Начало действия с дополнительным намерением
  2. Добавление дополнительного в конструктор 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;

Хорошо, это кажется лучшим решением, чем любое из вышеперечисленного, кроме нескольких«но»:

  1. У меня есть два идентичных класса ViewModelFactory, которые отличаются только аннотацией @Singleton.
  2. Использование @Named не очень элегантно, как для меня.

1 Ответ

0 голосов
/ 26 февраля 2019

Отключено, это проще, чем я думал.

Для распространенных моделей представления:

удалить @Singleton аннотацию из ViewModelFactory.

Добавьте @Singleton к ViewModelModule

@Binds
@Singleton
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);

Для ViewModels, где нам нужно использовать Intent Extra injection:

ActivityModule:

@Provides
@Nullable
static String provideTaskId(AddEditTaskActivity activity) {
    return activity.getIntent().getStringExtra(AddEditTaskActivity.EDIT_TASK_ID);
}

@Binds
@Named(AddEditTaskViewModel.TAG)
abstract ViewModelProvider.Factory bindAddEditTaskViewModelFactory(ViewModelFactory factory);

Чем использовать в деятельности / фрагмент:

@Inject
@Named(AddEditTaskViewModel.TAG)
ViewModelProvider.Factory viewModelFactory;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...