Библиотека жизненного цикла Android ViewModel с помощью dagger 2

у меня есть класс ViewModel, как и тот, который определен в подключение ViewModel и репозитория на руководство по архитектуре. Когда я запускаю приложение, я получаю исключение во время выполнения. Кто-нибудь знает как обойти это? Не должен ли я вводить ViewModel? Есть ли способ сказать ViewModelProvider использовать Dagger для создания модели?

public class DispatchActivityModel extends ViewModel {

    private final API api;

    @Inject
    public DispatchActivityModel(API api) {
        this.api = api;
    }
}

вызвано: java.ленг.Исключение InstantiationException: java.ленг.Класс не имеет нулевого аргумента конструктор на Яве.ленг.Класс.newInstance (собственный метод) на Андроид.арка.жизненный цикл.ViewModelProvider$NewInstanceFactory.create (ViewModelProvider.java: 143) на Андроид.арка.жизненный цикл.ViewModelProviders$DefaultFactory.create (ViewModelProviders.java: 143)  на Андроид.арка.жизненный цикл.ViewModelProvider.get (ViewModelProvider.java: 128)  на Андроид.арка.жизненный цикл.ViewModelProvider.get (ViewModelProvider.Ява:96)  на com.образец.основа.BaseActivity.onCreate (BaseActivity.Ява:65)  по ком.образец.отправка.DispatchActivity.onCreate (DispatchActivity.java: 53)  на Андроид.приложение.Деятельность.performCreate (действие.java: 6682)  на Андроид.приложение.Аппаратура.callActivityOnCreate(контрольно-измерительные приборы.java: 1118)  на Андроид.приложение.ActivityThread.performLaunchActivity (ActivityThread.java: 2619) на android.приложение.ActivityThread.handleLaunchActivity (ActivityThread.java: 2727)  на андроид.приложение.ActivityThread.- wrap12 (ActivityThread.Ява)  на Андроид.приложение.ActivityThread$H. handleMessage (ActivityThread.java: 1478)  на Андроид.ОС.Обработчик.dispatchMessage(обработчика.java: 102)  на Андроид.ОС.Петлитель.петля (Looper.java: 154)  на Андроид.приложение.ActivityThread.main (ActivityThread.java: 6121)

5 ответов


вам нужно реализовать свой собственный ViewModelProvider.Factory. Существует пример приложения, созданного Google, демонстрирующий, как подключить Dagger 2 с ViewModels. ссылке. Вы нужны эти 5 вещей:

В ViewModel:

@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {

определить аннотация:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

В ViewModelModule:

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel bindUserViewModel(UserViewModel userViewModel);

Фрагмент:

@Inject
ViewModelProvider.Factory viewModelFactory;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);

Фабрика:

@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(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) {
            throw new RuntimeException(e);
        }
    }
}

сегодня я узнал способ избежать необходимости писать фабрики для моего ViewModel классы:

class ViewModelFactory<T : ViewModel> @Inject constructor(
    private val viewModel: Lazy<T>
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}

EDIT: как указал @Calin в комментариях, мы используем Dagger's Lazy в приведенном выше фрагменте кода, а не Котлина.

вместо того, чтобы вводить ViewModel, вы можете придать универсальный ViewModelFactory в ваши действия и фрагменты и получить экземпляр любого ViewModel:

class MyActivity : Activity() {

    @Inject
    internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        this.viewModel = ViewModelProviders.of(this, viewModelFactory)
            .get(MyViewModel::class.java)
        ...
    }

    ...
}

Я AndroidInjection.inject(this) С dagger-android библиотека, но вы можете ввести свою активность или фрагмент так, как вы предпочитаете. Все, что осталось, это убедиться, что вы предоставляете свой ViewModel из модуля:

@Module
object MyModule {
    @JvmStatic
    @Provides
    fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
} 

или применения @Inject аннотация к его конструктору:

class MyViewModel @Inject constructor(
    someDependency: SomeDependency
) : ViewModel() {
    ...
}

Я считаю, что есть второй вариант, если вы не хотите использовать завод, упомянутый в ответе Роберта. Это не обязательно лучшее решение, но всегда хорошо знать варианты.

вы можете оставить viewModel с конструктором по умолчанию и ввести свои зависимости так же, как и в случае действий или других элементов, созданных система. Пример:

ViewModel:

public class ExampleViewModel extends ViewModel {

@Inject
ExampleDependency exampleDependency;

public ExampleViewModel() {
    DaggerExampleComponent.builder().build().inject(this);
    }
}

компоненты:

@Component(modules = ExampleModule.class)
public interface ExampleComponent {

void inject(ExampleViewModel exampleViewModel);

}

модуль:

@Module
public abstract class ExampleModule {

@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);

}

Ура, Петр!--4-->


что может быть не очевидно в вопросе, так это то, что ViewModel не может быть введен таким образом, потому что фабрика ViewModelProvider по умолчанию, которую вы получаете от

ViewModelProvider.of(LifecycleOwner lo) 

метод только с параметром LifecycleOwner может создавать только экземпляр ViewModel, который имеет конструктор по умолчанию no-arg.

у вас есть param: 'api' в вашем конструкторе:

public DispatchActivityModel(API api) {

для этого вам нужно создать фабрику, чтобы вы могли сказать ей, как создать себя. Пример кода от google дает вам конфигурацию Кинжала и заводской код, как указано в принятом ответе.

DI был создан, чтобы избежать использования оператора new () в зависимостях, потому что, если реализации изменятся, каждая ссылка также должна будет измениться. Реализация ViewModel мудро использует статический шаблон фабрики уже с ViewProvider.из.)(get (), что делает его инъекцию ненужной в случае конструктора no-arg. Так что в этом случае вам не нужно напишите фабрику вам не нужно впрыснуть фабрику конечно.


Я хотел бы предоставить третий вариант для тех, кто наткнулся на этот вопрос. The Кинжал ViewModel библиотека позволит вам вводить Dagger2-подобный способ с ViewModels, необязательно указывая область видимости ViewModel.

Он удаляет много шаблонов, а также позволяет вводить ViewModels декларативным способом, используя аннотацию:

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

Он также требует небольшого количества кода для настройки модуля, от которого полностью зависит инжектированные ViewModels могут быть сгенерированы, и после этого это так же просто, как вызов:

void injectFragment(Fragment fragment, ViewModelFactory factory) {
    ViewModelInejectors.inject(frag, viewModelFactory);
}

в классе ViewModelInjectors, который генерируется.

отказ от ответственности: это моя библиотека, но я считаю, что она также полезна автору этого вопроса и всем, кто хочет достичь того же.