wake-up-neo.com

Android-Lebenszyklusbibliothek ViewModel mit Dolch 2

Ich habe eine ViewModel-Klasse, genau wie die im Abschnitt Connecting ViewModel und Repository des Architecture-Guide definierte. Wenn ich meine App starte, erhalte ich eine Laufzeitausnahme. Weiß jemand, wie man das umgehen kann? Sollte ich das ViewModel nicht injizieren? Gibt es eine Möglichkeit, der ViewModelProvider mitzuteilen, dass das Modell mit Dagger erstellt werden soll?

public class DispatchActivityModel extends ViewModel {

    private final API api;

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

Verursacht durch: Java.lang.InstantiationException: Java.lang.Class hat keinen Null-Argument-Konstruktor bei Java.lang.Class.newInstance (Native Method) bei Android.Arch.lifecycle.ViewModelProvider $ NewInstanceFactory.create (ViewModelProvider.Java:143) bei Android.Arch.lifecycle.ViewModelProviders $ DefaultFactory.create (ViewModelProviders.Java:143) unter Android.Arch.lifecycle.ViewModelProvider.get (ViewModelProvider.Java:128) unter Android.Arch.lifecycle.ViewModelProvider.get (ViewModelProvider.Java:96) at com.example.base.BaseActivity.onCreate (BaseActivity.Java:65) at com.example.dispatch.DispatchActivity.onCreate (DispatchActivity.Java:53) bei Android.app.Activity.performCreate (Activity.Java:6682) bei Android.app.Instrumentation.callActivityOnCreate (Instrumentation.Java:1118) at Android.app.ActivityThread.performLaunchActivity (ActivityThread.Java:2619) bei Android.app.ActivityThread.handleLaunchActivity (ActivityThread.Java:2727) unter Android.app.ActivityThread.-wrap12 (ActivityThread.Java) at Android.app.ActivityThread $ H.handleMessage (ActivityThread.Java:1478) bei Android.os.Handler.dispatchMessage (Handler.Java:102) unter Android.os.Looper.loop (Looper.Java:154) at Android.app.ActivityThread.main (ActivityThread.Java:6121)

45
TheHebrewHammer

Sie müssen Ihren eigenen ViewModelProvider.Factory implementieren. Es gibt eine von Google erstellte Beispiel-App, die zeigt, wie Dagger 2 mit ViewModels verbunden wird. VERKNÜPFUNG . Sie brauchen diese 5 Dinge:

In ViewModel:

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

Anmerkung definieren:

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

In ViewModelModule:

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

In Fragment:

@Inject
ViewModelProvider.Factory viewModelFactory;

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

Fabrik:

@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);
        }
    }
}
74
Robert Wysocki

Heute habe ich gelernt, wie man es vermeiden muss, für meine ViewModel-Klassen Fabriken zu schreiben:

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: Wie von @Calin in den Kommentaren dargelegt, verwenden wir Daggers Lazy im obigen Code-Snippet, nicht Kotlins. 

Anstatt ViewModel zu injizieren, können Sie ein generisches ViewModelFactory in Ihre Aktivitäten und Fragmente einfügen und eine Instanz von ViewModel erhalten:

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)
        ...
    }

    ...
}

Ich habe AndroidInjection.inject(this) wie bei der dagger-Android-Bibliothek verwendet, aber Sie können Ihre Aktivität einfügen oder auf die von Ihnen bevorzugte Weise fragmentieren. Jetzt müssen Sie nur noch sicherstellen, dass Sie Ihre ViewModel aus einem Modul bereitstellen:

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

Oder die Annotation @Inject auf ihren Konstruktor anwenden:

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

Ich glaube, es gibt eine zweite Option, wenn Sie die in Roberts Antwort erwähnte Fabrik nicht verwenden möchten. Es ist nicht unbedingt eine bessere Lösung, aber es ist immer gut, die Optionen zu kennen.

Sie können Ihr viewModel mit dem Standardkonstruktor belassen und Ihre Abhängigkeiten wie bei Aktivitäten oder anderen von system ..__ erstellten Elementen einfügen. Beispiel:

ViewModel:

public class ExampleViewModel extends ViewModel {

@Inject
ExampleDependency exampleDependency;

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

Komponente:

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

void inject(ExampleViewModel exampleViewModel);

}

Modul:

@Module
public abstract class ExampleModule {

@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);

}

Prost, Piotr

Was in der Frage möglicherweise nicht offensichtlich ist, ist, dass das ViewModel nicht auf diese Weise injiziert werden kann, da die ViewModelProvider-Standard-Factory die von der 

ViewModelProvider.of(LifecycleOwner lo) 

eine Methode mit nur dem Parameter LifecycleOwner kann nur ein ViewModel mit einem Standardkonstruktor ohne Argument instanziieren.

Sie haben einen Parameter: 'api' in Ihrem Konstruktor: 

public DispatchActivityModel(API api) {

Dazu müssen Sie eine Factory erstellen, damit Sie ihr sagen können, wie sie sich selbst erstellen soll. Der Beispielcode von Google enthält die Dagger-Konfiguration und den Factory-Code, wie in der akzeptierten Antwort angegeben. 

DI wurde erstellt, um die Verwendung des new () - Operators für Abhängigkeiten zu vermeiden, da bei Änderungen der Implementierungen auch jede Referenz geändert werden müsste. Die ViewModel-Implementierung verwendet ein statisches Factory-Muster bereits mit ViewProvider.of (). Get (), wodurch die Injektion im Fall eines no-arg-Konstruktors nicht erforderlich ist. Wenn Sie also keine Fabrik schreiben müssen, brauchen Sie natürlich keine Fabrik zu spritzen.

4
Droid Teahouse

Ich möchte eine dritte Option für alle bereitstellen, die über diese Frage stolpern. Mit der Dagger ViewModel-Bibliothek können Sie Dagger2-ähnlich injizieren, wobei ViewModels optional den Bereich von ViewModel angibt. 

Es entfernt einen Großteil der Boilerplate und ermöglicht die deklarative Injektion der ViewModels mithilfe einer Annotation:

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

Es ist auch ein wenig Code erforderlich, um ein Modul einzurichten, aus dem ViewModels mit vollständiger Abhängigkeit erstellt werden können. Danach ist es so einfach wie das Aufrufen von:

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

In der ViewModelInjectors-Klasse, die generiert wird.

HAFTUNGSAUSSCHLUSS: Es ist meine Bibliothek, aber ich glaube, es ist auch für den Verfasser dieser Frage und jeden anderen, der das gleiche erreichen möchte, von Nutzen.

3
onepointsixtwo

Die standardmäßige ViewModel-Factory, die zum Abrufen einer Instanz Ihrer DispatchActivityModel in Ihrer Ansicht verwendet wird, erstellt die ViewModels unter Verwendung von leeren Konstruktoren.

Sie können Ihren benutzerdefinierten ViewModel.Factory schreiben, um dieses Problem zu umgehen. Sie müssen jedoch den Abhängigkeitsgraphen selbst ausfüllen, wenn Sie Ihre API-Klasse bereitstellen möchten.

Ich habe eine kleine Bibliothek geschrieben, die die Überwindung dieses allgemeinen Problems einfacher und sauberer machen sollte. Es sind keine Multibindings oder Factory Boilerplates erforderlich, und die ViewModel kann zur Laufzeit weiter parametriert werden: https://github.com/ radutopor/ViewModelFactory

@ViewModelFactory
public class DispatchActivityModel extends ViewModel {

    private final API api;

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

In der Ansicht:

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)
    }
}

Wie bereits erwähnt, können Sie Laufzeitparameter auch leicht zu Ihren ViewModel-Instanzen hinzufügen:

@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