J'essaie d'implémenter le modèle MVVM dans mon application Android. J'ai lu que ViewModels ne devrait contenir aucun code spécifique à Android (pour faciliter les tests), mais il me faut utiliser le contexte pour différentes tâches (obtenir des ressources à partir de xml, initialiser les préférences, etc.). Quelle est la meilleure façon de procéder? J'ai vu que AndroidViewModel
renvoie au contexte de l'application, mais contient du code spécifique à Android. Je ne suis donc pas sûr que ce soit dans le ViewModel. Ceux-ci sont également liés aux événements du cycle de vie de l'activité, mais comme j'utilise un poignard pour gérer l'étendue des composants, je ne suis pas sûr de l'impact que cela pourrait avoir sur cela. Je suis nouveau sur le modèle MVVM et Dagger alors toute aide est appréciée!
Ce que j'ai fini par faire au lieu d'avoir un contexte directement dans le ViewModel, j'ai créé des classes de fournisseur, telles que ResourceProvider, qui me donneraient les ressources dont j'ai besoin, et ces classes de fournisseur ont été injectées dans mon ViewModel.
Vous pouvez utiliser un contexte Application
fourni par le AndroidViewModel
, vous devez étendre AndroidViewModel
qui est simplement un ViewModel
qui inclut une référence Application
.
Ce n'est pas que ViewModels ne contienne pas de code spécifique à Android pour faciliter les tests, car c'est l'abstraction qui en facilite les tests.
La raison pour laquelle ViewModels ne devrait pas contenir d'instance de Context ni quoi que ce soit de type Vues ou autres objets conservant un contexte, c'est parce que son cycle de vie est distinct de celui d'Activités et de Fragments.
Ce que je veux dire par là, c'est que vous modifiez la rotation de votre application. Cela provoque la destruction de votre activité et de votre fragment, ainsi il se recrée. ViewModel est censé persister pendant cet état. Il y a donc un risque de crash et d'autres exceptions si le View ou le contexte de l'activité détruite est toujours en cours.
Pour ce qui est de savoir comment faire ce que vous voulez, MVVM et ViewModel fonctionnent très bien avec le composant Liaison de données de JetPack. Pour la plupart des choses pour lesquelles vous stockez une chaîne, un int, ou autre, vous pouvez utiliser la liaison de données pour que les vues l'affiche directement, sans qu'il soit nécessaire de stocker la valeur dans ViewModel.
Mais si vous ne voulez pas que la liaison de données soit établie, vous pouvez toujours transmettre le contexte à l'intérieur du constructeur ou des méthodes pour accéder aux ressources. Ne gardez pas une instance de ce contexte dans votre ViewModel.
Pour Android Modèle de vue des composants d'architecture,
Ce n'est pas une bonne pratique de passer votre contexte d'activité au ViewModel de l'activité en tant que fuite de mémoire.
Par conséquent, pour obtenir le contexte dans votre ViewModel, la classe ViewModel doit étendre la classe Android View Model. De cette façon, vous pouvez obtenir le contexte comme indiqué dans l'exemple de code ci-dessous.
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
vous pouvez accéder au contexte de l'application à partir de getApplication().getApplicationContext()
à partir du ViewModel. C’est ce dont vous avez besoin pour accéder aux ressources, préférences, etc.
contient une référence au contexte de l'application, mais contenant le code spécifique Android
Bonne nouvelle, vous pouvez utiliser Mockito.mock(Context.class)
et faire en sorte que le contexte renvoie tout ce que vous voulez lors des tests!
Utilisez donc simplement ViewModel
comme vous le feriez normalement et donnez-le ApplicationContext via ViewModelProviders.Factory comme vous le feriez normalement.
Le MVVM est une bonne architecture et c’est assurément l’avenir du développement de Android, mais quelques éléments restent écologiques. Prenons par exemple la communication de couche dans une architecture MVVM. J'ai vu différents développeurs (développeurs très connus) utiliser LiveData pour communiquer les différentes couches de différentes manières. Certains d'entre eux utilisent LiveData pour communiquer le ViewModel avec l'interface utilisateur, mais ils utilisent ensuite des interfaces de rappel pour communiquer avec les référentiels ou ont Interactors/UseCases et utilisent LiveData pour communiquer avec eux. Le point ici est que tout n'est pas défini à 100% encore.
Cela étant dit, mon approche à votre problème spécifique consiste à disposer d'un contexte d'application disponible via DI pour pouvoir l'utiliser dans mes ViewModels afin d'obtenir des éléments tels que String à partir de mon strings.xml.
Si je traite du chargement d'images, j'essaie de passer par les objets View à partir des méthodes de l'adaptateur de liaison de données et d'utiliser le contexte de la vue pour charger les images. Pourquoi? certaines technologies (par exemple, Glide) peuvent rencontrer des problèmes si vous utilisez le contexte de l'application pour charger des images.
TL; DR: Injectez le contexte de l'application via Dagger dans vos ViewModels et utilisez-le pour charger les ressources. Si vous devez charger des images, transmettez l'instance View aux arguments des méthodes de liaison de données et utilisez ce contexte View.
J'espère que ça aide!
Vous ne devez pas utiliser d'objets liés Android dans votre ViewModel, car le motif de l'utilisation d'un ViewModel est de séparer le code Java et le code Android afin de pouvoir le tester. votre logique métier séparément et vous aurez une couche distincte de Android composants et de votre logique métier et de vos données. Vous ne devriez pas avoir de contexte dans votre ViewModel car cela pourrait entraîner des pannes.
Réponse courte - Ne fais pas ça
Pourquoi?
Il défait tout le but des modèles de vue
Presque tout ce que vous pouvez faire dans le modèle d'affichage peut être fait dans activity/fragment en utilisant des instances LiveData et diverses autres approches recommandées.
Comme d'autres l'ont mentionné, il y a AndroidViewModel
laquelle vous pouvez dériver pour obtenir l'application Context
, mais d'après ce que je comprends dans les commentaires, vous essayez de manipuler @drawable
s depuis votre ViewModel
qui va presque certainement à l’encontre du but de faire tout le processus MVVM.
Globalement, la nécessité d'avoir une Context
dans votre ViewModel
suggère presque universellement que vous devriez envisager de repenser la façon dont vous divisez la logique entre votre View
s et ViewModels
.
Par exemple. au lieu d'avoir le ViewModel
résoudre les dessinables et de les alimenter vers l'activité/le fragment, envisagez de faire en sorte que le fragment/l'activité jongle avec les données dessinables en fonction des données possédées par le ViewModel
. Par exemple, si vous avez une sorte d'indicateur d'activation/désactivation, c'est la ViewModel
qui doit conserver l'état (probablement booléen), mais c'est à View
de sélectionner le dessin approprié en conséquence.
Si vous avez besoin de Context
pour certains composants/services non directement liés à la vue (par exemple, les requêtes d’arrière-plan) au constructeur de ViewModel
(manuellement/par injection) - de cette manière, aucune dépendance explicite sur Context
et, par conséquent, se moque facilement lors des tests (il suffit de transmettre des composants/services fictifs au constructeur ou de les fournir au harnais par injection de choix, aucun besoin de Context
réelle)
Je l'ai créé de cette façon:
@Module
public class ContextModule {
@Singleton
@Provides
@Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
Et puis je viens d'ajouter dans AppComponent le ContextModule.class:
@Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
Et puis j'ai injecté le contexte dans mon ViewModel:
@Inject
@Named("AppContext")
Context context;