Ich versuche, MVVM-Muster in meiner Android-App zu implementieren. Ich habe gelesen, dass ViewModels keinen androidspezifischen Code enthalten sollten (um das Testen zu vereinfachen), ich muss jedoch den Kontext für verschiedene Dinge verwenden (Ressourcen aus XML abrufen, Einstellungen initialisieren usw.). Was ist der beste Weg, dies zu tun? Ich habe gesehen, dass dies AndroidViewModel
einen Verweis auf den Anwendungskontext enthält, der jedoch androidspezifischen Code enthält, sodass ich nicht sicher bin, ob dies im ViewModel enthalten sein sollte. Auch diese hängen mit den Ereignissen im Aktivitätslebenszyklus zusammen, aber ich verwende Dolch, um den Umfang der Komponenten zu verwalten, sodass ich nicht sicher bin, wie sich dies darauf auswirken würde. Ich bin neu im MVVM-Muster und im Dolch, daher ist jede Hilfe willkommen!
quelle
AndroidViewModel
aber esCannot create instance exception
bekommt, können Sie sich auf meine Antwort stackoverflow.com/a/62626408/1055241Antworten:
Sie können einen verwenden
Application
Kontext, der durch die vorgesehen istAndroidViewModel
, sollten Sie erweiternAndroidViewModel
die einfach eine ist ,ViewModel
die eine beinhaltetApplication
Referenz.quelle
Für Android-Architekturkomponenten Modell anzeigen,
Es ist keine gute Vorgehensweise, Ihren Aktivitätskontext an das ViewModel der Aktivität zu übergeben, da es sich um einen Speicherverlust handelt.
Um den Kontext in Ihrem ViewModel zu erhalten, sollte die ViewModel-Klasse daher die Android View Model- Klasse erweitern. Auf diese Weise können Sie den Kontext wie im folgenden Beispielcode gezeigt abrufen.
class ActivityViewModel(application: Application) : AndroidViewModel(application) { private val context = getApplication<Application>().applicationContext //... ViewModel methods }
quelle
Es ist nicht so, dass ViewModels keinen Android-spezifischen Code enthalten sollten, um das Testen zu vereinfachen, da es die Abstraktion ist, die das Testen erleichtert.
Der Grund, warum ViewModels keine Instanz von Context oder Ähnlichem wie Views oder anderen Objekten enthalten sollten, die an einem Context festhalten, liegt darin, dass es einen anderen Lebenszyklus als Aktivitäten und Fragmente hat.
Damit meine ich, dass Sie eine Rotationsänderung an Ihrer App vornehmen. Dies führt dazu, dass sich Ihre Aktivität und Ihr Fragment selbst zerstören und sich selbst neu erstellen. ViewModel soll in diesem Zustand bestehen bleiben, sodass Abstürze und andere Ausnahmen auftreten können, wenn noch eine Ansicht oder ein Kontext für die zerstörte Aktivität vorhanden ist.
MVVM und ViewModel funktionieren sehr gut mit der Datenbindungskomponente von JetPack. Für die meisten Dinge, für die Sie normalerweise einen String, ein Int usw. speichern, können Sie die Datenbindung verwenden, damit die Ansichten sie direkt anzeigen, sodass der Wert nicht in ViewModel gespeichert werden muss.
Wenn Sie jedoch keine Datenbindung wünschen, können Sie den Kontext dennoch im Konstruktor oder in den Methoden übergeben, um auf die Ressourcen zuzugreifen. Halten Sie nur keine Instanz dieses Kontexts in Ihrem ViewModel.
quelle
Kurze Antwort - Tu das nicht
Warum ?
Es macht den gesamten Zweck von Ansichtsmodellen zunichte
Fast alles, was Sie im Ansichtsmodell tun können, kann in Aktivität / Fragment mithilfe von LiveData-Instanzen und verschiedenen anderen empfohlenen Ansätzen ausgeführt werden.
quelle
Was ich letztendlich getan habe, anstatt einen Kontext direkt im ViewModel zu haben, habe ich Anbieterklassen wie ResourceProvider erstellt, die mir die benötigten Ressourcen zur Verfügung stellen, und diese Anbieterklassen wurden in mein ViewModel eingefügt
quelle
getDrawableRes(@DrawableRes int id)
innerhalb der ResourceProvider-Klasse hinzufügenTL; DR: Fügen Sie den Kontext der Anwendung über Dagger in Ihre ViewModels ein und verwenden Sie ihn zum Laden der Ressourcen. Wenn Sie Bilder laden müssen, übergeben Sie die View-Instanz über Argumente aus den Datenbindungsmethoden und verwenden Sie diesen View-Kontext.
Die MVVM ist eine gute Architektur und es ist definitiv die Zukunft der Android-Entwicklung, aber es gibt ein paar Dinge, die noch grün sind. Nehmen wir zum Beispiel die Layer-Kommunikation in einer MVVM-Architektur. Ich habe gesehen, dass verschiedene Entwickler (sehr bekannte Entwickler) LiveData verwenden, um die verschiedenen Layer auf unterschiedliche Weise zu kommunizieren. Einige von ihnen verwenden LiveData, um das ViewModel mit der Benutzeroberfläche zu kommunizieren, aber dann verwenden sie Rückrufschnittstellen, um mit den Repositorys zu kommunizieren, oder sie haben Interactors / UseCases und sie verwenden LiveData, um mit ihnen zu kommunizieren. Punkt hier ist, dass noch nicht alles zu 100% definiert ist .
Abgesehen davon besteht mein Ansatz bei Ihrem spezifischen Problem darin, den Kontext einer Anwendung über DI verfügbar zu machen, um ihn in meinen ViewModels zu verwenden, um Dinge wie String aus meiner Datei strings.xml abzurufen
Wenn ich mich mit dem Laden von Bildern beschäftige, versuche ich, die View-Objekte aus den Methoden des Datenbindungsadapters zu durchlaufen und den Kontext der Ansicht zum Laden der Bilder zu verwenden. Warum? da bei einigen Technologien (z. B. Glide) Probleme auftreten können, wenn Sie den Kontext der Anwendung zum Laden von Bildern verwenden.
Ich hoffe es hilft!
quelle
Wie andere bereits erwähnt haben, gibt es etwas, von
AndroidViewModel
dem Sie ableiten können, um die App zu erhalten,Context
aber von dem, was ich in den Kommentaren erfahre, versuchen Sie,@drawable
s aus Ihrem Inneren heraus zu manipulierenViewModel
was den Zweck von MVVM zunichte macht.Im Allgemeinen legt die Notwendigkeit, ein
Context
in IhremViewModel
fast universellen zu haben, nahe, dass Sie überlegen sollten, wie Sie die Logik zwischen IhremView
s und aufteilenViewModels
.Anstatt
ViewModel
Drawables aufzulösen und sie der Aktivität / dem Fragment zuzuführen, sollten Sie in Betracht ziehen, dass das Fragment / die Aktivität die Drawables auf der Grundlage der Daten jongliert, über die das verfügtViewModel
. Angenommen, Sie müssen verschiedene Drawables in einer Ansicht für den Ein / Aus-Status anzeigen - es ist dasViewModel
, das den (wahrscheinlich booleschen) Status enthalten sollte, aber es istView
Aufgabe des Drawables, das Drawable entsprechend auszuwählen.Mit DataBinding geht das ganz einfach :
<ImageView ... app:src="@{viewModel.isOn ? @drawable/switch_on : @drawable/switch_off}" />
Wenn Sie mehr Status und Drawables haben, können Sie einen benutzerdefinierten BindingAdapter schreiben , der beispielsweise einen
Enum
Wert in übersetzt, um unhandliche Logik in der Layoutdatei zu vermeidenR.drawable.*
(z. B. Kartenanzüge)Oder Sie benötigen die
Context
Komponente für eine Komponente, die Sie in Ihrem verwendenViewModel
. Erstellen Sie dann die Komponente außerhalb vonViewModel
und übergeben Sie sie. Sie können DI oder Singletons verwenden oder dieContext
abhängige Komponente direkt vor dem Initialisieren vonViewModel
inFragment
/ erstellenActivity
.Warum sich die Mühe machen:
Context
ist eine Android-spezifische Sache, und abhängig von denen inViewModel
s ist eine schlechte Praxis: Sie stehen Unit-Tests im Wege. Auf der anderen Seite haben Sie Ihre eigenen Komponenten- / Serviceschnittstellen vollständig unter Ihrer Kontrolle, sodass Sie sie zum Testen leicht verspotten können.quelle
Gute Nachrichten, Sie können
Mockito.mock(Context.class)
den Kontext verwenden und dafür sorgen, dass er in Tests zurückgibt, was Sie wollen!Verwenden
ViewModel
Sie also einfach a wie gewohnt und geben Sie ihm den ApplicationContext über ViewModelProviders.Factory wie gewohnt.quelle
Sie können über
getApplication().getApplicationContext()
das ViewModel auf den Anwendungskontext zugreifen . Dies ist, was Sie benötigen, um auf Ressourcen, Einstellungen usw. zuzugreifen.quelle
ViewModel
Klasse hat diegetApplication
Methode nicht.AndroidViewModel
tutSie sollten keine Android-bezogenen Objekte in Ihrem ViewModel verwenden, da das Motiv für die Verwendung eines ViewModel darin besteht, den Java-Code und den Android-Code zu trennen, damit Sie Ihre Geschäftslogik separat testen können und eine separate Schicht von Android-Komponenten und Ihre Geschäftslogik haben und Daten, Sie sollten keinen Kontext in Ihrem ViewModel haben, da dies zu Abstürzen führen kann
quelle
Ich hatte Probleme,
SharedPreferences
dieViewModel
Klasse zu benutzen , also nahm ich den Rat aus den obigen Antworten und machte die folgendenAndroidViewModel
. Jetzt sieht alles gut ausFür die
AndroidViewModel
import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.preference.PreferenceManager; public class HomeViewModel extends AndroidViewModel { private MutableLiveData<String> some_string; public HomeViewModel(Application application) { super(application); some_string = new MutableLiveData<>(); Context context = getApplication().getApplicationContext(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); some_string.setValue("<your value here>")); } }
Und in der
Fragment
import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; public class HomeFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View root = inflater.inflate(R.layout.fragment_home, container, false); HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class); homeViewModel.getAddress().observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(@Nullable String address) { } }); return root; } }
quelle
Ich habe es so erstellt:
Und dann habe ich in AppComponent die ContextModule.class hinzugefügt:
@Component( modules = { ... ContextModule.class } ) public interface AppComponent extends AndroidInjector<BaseApplication> { ..... }
Und dann habe ich den Kontext in mein ViewModel eingefügt:
quelle
Verwenden Sie das folgende Muster:
quelle