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

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

public class DispatchActivityModel extends ViewModel {

    private final API api;

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

Вызывается: java.lang.InstantiationException: java.lang.Class не имеет конструктора нулевых аргументов в java.lang.Class.newInstance (собственный метод) в android.arch.lifecycle.ViewModelProvider $ NewInstanceFactory.create(ViewModelProvider.java:143) в android.arch.lifecycle.ViewModelProviders $ DefaultFactory.create(ViewModelProviders.java:143) в android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128) в android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java: 96) at com.example.base.BaseActivity.onCreate(BaseActivity.java:65) в com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53) в android.app.Activity.performCreate(Activity.java: 6682) на android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118) на android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) на android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) на android. app.ActivityThread. -wrap12 (ActivityThread.java) в android.app.ActivityTh прочитайте $ H.handleMessage(ActivityThread.java:1478) в android.os.Handler.dispatchMessage(Handler.java:102) на android.os.Looper.loop(Looper.java:154) в android.app.ActivityThread.main(ActivityThread.java:6121)

Ответ 1

Вам необходимо реализовать свой собственный 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);
        }
    }
}

Ответ 2

Сегодня я научился избегать писать фабрики для своих классов 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 Lazy в фрагменте кода выше, а не Kotlin's.

Вместо того, чтобы вводить 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 только убедиться, что вы предоставили свою ViewModel из модуля:

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

Или применив аннотацию @Inject к своему конструктору:

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

Ответ 3

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

Вы можете оставить свой 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.

У вас есть параметр: api в вашем конструкторе:

public DispatchActivityModel(API api) {

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

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

Ответ 5

Я хотел бы предоставить третий вариант для тех, кто наткнулся на этот вопрос. Библиотека Dagger ViewModel позволит вам вводить Dagger2-подобным способом с помощью ViewModels, опционально задавая область ViewModel.

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

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

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

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

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: это моя библиотека, но я считаю, что это также полезно автору этого вопроса, и всем, кто хочет достичь того же.