アーキテクチャガイド のViewModelとrepositoryの接続セクションで定義されているようなViewModelクラスがあります。アプリを実行すると、ランタイム例外が発生します。誰もこれを回避する方法を知っていますか? ViewModelを注入すべきではありませんか? ViewModelProvider
にDaggerを使用してモデルを作成するように指示する方法はありますか?
public class DispatchActivityModel extends ViewModel {
private final API api;
@Inject
public DispatchActivityModel(API api) {
this.api = api;
}
}
原因:Java.lang.InstantiationException:Java.lang.Classには、Android.Arch.lifecycle.ViewModelProvider $ NewInstanceFactory.create(ViewModelProvider.Java:143)のJava.lang.Class.newInstance(Native Method)にゼロ引数コンストラクターがありません。 Android.Arch.lifecycle.ViewModelProviders $ DefaultFactory.create(ViewModelProviders.Java:143)Android.Arch.lifecycle.ViewModelProvider.get(ViewModelProvider.Java:128)Android.Arch.lifecycle.ViewModelProvider.get(ViewModelProvider.Java)Provider :96)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)at Android.app.ActivityThread.handleLaunchActivity(ActivityThread.Java:2727)Android app.ActivityThread.-wrap12(ActivityThread.Java)And roid.app.ActivityThread $ 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)
独自のViewModelProvider.Factory
を実装する必要があります。 Dagger 2をViewModelsに接続する方法を示す、Googleが作成したサンプルアプリがあります。 LINK 。これらの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
}
編集:コメントで@Calinが指摘したように、上記のコードスニペットでは、KotlinではなくDaggerの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)
...
}
...
}
dagger-Android
ライブラリと同様にAndroidInjection.inject(this)
を使用しましたが、アクティビティを挿入したり、好きな方法でフラグメント化することができます。あとは、モジュールからViewModel
を指定することだけです:
@Module
object MyModule {
@JvmStatic
@Provides
fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
}
または、@Inject
注釈をコンストラクタに適用します。
class MyViewModel @Inject constructor(
someDependency: SomeDependency
) : ViewModel() {
...
}
ロバートの答えに記載されている工場を使用したくない場合、2番目のオプションがあると思います。必ずしもより良い解決策ではありませんが、オプションを知ることは常に良いことです。
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);
}
乾杯、ピョートル
質問で明らかでないかもしれないのは、ViewModelProviderから取得したデフォルトのFactory
ViewModelProvider.of(LifecycleOwner lo)
lifecycleOwnerパラメーターのみを持つメソッドは、引数なしのデフォルトコンストラクターを持つViewModelのみをインスタンス化できます。
コンストラクタにparam: 'api'があります:
public DispatchActivityModel(API api) {
そのためには、ファクトリを作成して、自身の作成方法を伝えることができるようにする必要があります。グーグルからのサンプルコードは、受け入れられた答えで述べられているように、ダガー設定とファクトリーコードを提供します。
実装が変更された場合、すべての参照も変更する必要があるため、DIは依存関係でnew()演算子の使用を避けるために作成されました。 ViewModel実装は、ViewProvider.of()。get()ですでに静的ファクトリーパターンを賢明に使用します。これにより、引数なしのコンストラクターの場合、その注入が不要になります。そのため、ファクトリを記述する必要がない場合、もちろんファクトリをインジェクトする必要はありません。
この質問につまずいた人のために、第三の選択肢を提供したいと思います。 Dagger ViewModelライブラリ を使用すると、ViewModelでDagger2のような方法で注入でき、オプションでViewModelのスコープを指定できます。
ボイラープレートの多くを削除し、注釈を使用して宣言的な方法でViewModelを注入することもできます。
@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;
また、完全に依存関係を注入したViewModelを生成できるモジュールをセットアップするために少量のコードが必要です。その後、呼び出すのと同じくらい簡単です。
void injectFragment(Fragment fragment, ViewModelFactory factory) {
ViewModelInejectors.inject(frag, viewModelFactory);
}
生成されるViewModelInjectorsクラス。
免責事項:それは私のライブラリですが、この質問の著者や同じことを達成したい人にも役立つと思います。
ビューでDispatchActivityModel
のインスタンスを取得するために使用されるデフォルトのViewModelファクトリは、想定される空のコンストラクターを使用してViewModelを構築します。
カスタムViewModel.Factory
を作成して回避することもできますが、API
クラスを提供する場合は、依存関係グラフを完成させる必要があります。
私は、この一般的な問題をより簡単に解決し、マルチバインディングやファクトリーボイラープレートを必要とせず、実行時にViewModel
をさらにパラメータ化する機能を提供する小さなライブラリを作成しました。 https:// github .com/radutopor/ViewModelFactory
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
public DispatchActivityModel(@Provided API api) {
this.api = api;
}
}
ビューで:
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create())
.get(UserViewModel.class)
}
}
前述のように、ViewModel
インスタンスにもランタイムパラメーターを簡単に追加することもできます。
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
private final int dispatchId;
public DispatchActivityModel(@Provided API api, int dispatchId) {
this.api = api;
this.dispatchId = dispatchId;
}
}
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
final int dispatchId = getIntent().getIntExtra("DISPATCH_ID", -1);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create(dispatchId))
.get(UserViewModel.class)
}
}