Библиотека жизненного цикла 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, который генерируется.
отказ от ответственности: это моя библиотека, но я считаю, что она также полезна автору этого вопроса и всем, кто хочет достичь того же.