web-dev-qa-db-ja.com

短剣2を使用したAndroidライフサイクルライブラリViewModel

アーキテクチャガイド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)

47
TheHebrewHammer

独自の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);
        }
    }
}
83
Robert Wysocki

今日、私は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() {
    ...
}
24
argenkiwi

ロバートの答えに記載されている工場を使用したくない場合、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()ですでに静的ファクトリーパターンを賢明に使用します。これにより、引数なしのコンストラクターの場合、その注入が不要になります。そのため、ファクトリを記述する必要がない場合、もちろんファクトリをインジェクトする必要はありません。

4
Droid Teahouse

この質問につまずいた人のために、第三の選択肢を提供したいと思います。 Dagger ViewModelライブラリ を使用すると、ViewModelでDagger2のような方法で注入でき、オプションでViewModelのスコープを指定できます。

ボイラープレートの多くを削除し、注釈を使用して宣言的な方法でViewModelを注入することもできます。

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

また、完全に依存関係を注入したViewModelを生成できるモジュールをセットアップするために少量のコードが必要です。その後、呼び出すのと同じくらい簡単です。

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

生成されるViewModelInjectorsクラス。

免責事項:それは私のライブラリですが、この質問の著者や同じことを達成したい人にも役立つと思います。

3
onepointsixtwo

ビューで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)
    }
}
0
Radu Topor