199 votes

Comment obtenir le contexte dans le ViewModel MVVM d'Android ?

J'essaie de mettre en œuvre le modèle MVVM dans mon application Android. J'ai lu que les ViewModels ne devraient pas contenir de code spécifique à Android (pour faciliter les tests), mais j'ai besoin d'utiliser le contexte pour diverses choses (obtenir des ressources à partir de xml, initialiser les préférences, etc). Quelle est la meilleure façon de faire cela ? J'ai vu que AndroidViewModel contient une référence au contexte de l'application, mais comme il contient du code spécifique à Android, je ne suis pas sûr qu'il doive se trouver dans le ViewModel. Ces éléments sont également liés aux événements du cycle de vie de l'activité, mais j'utilise Dagger pour gérer la portée des composants, donc je ne suis pas sûr que cela ait une incidence. Je suis novice en matière de modèle MVVM et de Dagger, alors toute aide est la bienvenue !

0 votes

Juste au cas où quelqu'un essaierait d'utiliser AndroidViewModel mais en obtenant Cannot create instance exception alors vous pouvez vous référer à ma réponse stackoverflow.com/a/62626408/1055241

1 votes

Vous ne devriez pas utiliser le contexte dans un ViewModel, créez plutôt un UseCase pour obtenir le contexte de cette manière.

2 votes

@RubenCaster avez-vous un exemple ou un lien GitHub pour cela ?

131voto

DeeJay Points 543

Pour les composants de l'architecture Android Modèle de vue,

Ce n'est pas une bonne pratique de passer votre contexte d'activité au ViewModel de l'activité car cela entraîne une fuite de mémoire.

Par conséquent, pour obtenir le contexte dans votre ViewModel, la classe ViewModel devrait étendre la classe Modèle de vue Android La classe. 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 

}

9 votes

Pourquoi ne pas utiliser directement le paramètre d'application et un ViewModel normal ? Je ne vois pas l'intérêt de "getApplication<Application>()". Cela ne fait qu'ajouter du texte passe-partout.

2 votes

Pourquoi serait-ce une fuite de mémoire ?

15 votes

Oh je vois, parce qu'une activité sera détruite plus souvent que son modèle de vue (par exemple, lorsque l'écran tourne). Malheureusement, la mémoire ne sera pas libérée par le garbage collection car le modèle de vue a toujours une référence à celle-ci.

122voto

Jay Points 499

Vous pouvez utiliser un Application qui est fourni par le AndroidViewModel vous devez étendre AndroidViewModel qui est simplement un ViewModel qui comprend un Application référence.

7 votes

Mais est-ce une bonne pratique d'utiliser AndroidViewModel ? À quoi dois-je faire attention pour éviter les fuites de mémoire ou les incohérences si je l'utilise ?

77voto

Jackey Points 2221

Ce n'est pas que les ViewModels ne doivent pas contenir de code spécifique à Android pour faciliter les tests, puisque c'est l'abstraction qui facilite les tests.

La raison pour laquelle les ViewModels ne doivent pas contenir une instance de Context ou quoi que ce soit d'autre comme des Views ou d'autres objets qui s'accrochent à un Context est qu'ils ont un cycle de vie distinct de celui des Activities et des Fragments.

Ce que je veux dire par là, c'est que, disons que vous faites un changement de rotation sur votre application. Cela entraîne la destruction de votre activité et de votre fragment, qui se recrée alors. Le ViewModel est censé persister pendant cet état, il y a donc des chances que des crashs et d'autres exceptions se produisent s'il tient toujours une vue ou un contexte de l'activité détruite.

Quant à la façon de faire ce que vous voulez faire, MVVM et ViewModel fonctionnent très bien avec le composant Databinding de JetPack. Pour la plupart des choses que vous stockez typiquement un String, int, ou etc pour, vous pouvez utiliser Databinding pour faire les vues l'afficher directement, donc pas besoin de stocker la valeur dans ViewModel.

Mais si vous ne voulez pas de liaison de données, vous pouvez toujours passer le contexte dans le constructeur ou les méthodes pour accéder aux ressources. Mais ne gardez pas une instance de ce contexte dans votre ViewModel.

1 votes

J'ai cru comprendre que l'inclusion de code spécifique à Android nécessitait l'exécution de tests d'instrumentation, ce qui est beaucoup plus lent que les tests JUnit ordinaires. J'utilise actuellement le Databinding pour les méthodes de clic, mais je ne vois pas comment il pourrait aider à obtenir des ressources à partir de xml ou pour les préférences. Je viens de réaliser que pour les préférences, j'aurais également besoin d'un contexte à l'intérieur de mon modèle. Ce que je fais actuellement, c'est que Dagger injecte le contexte de l'application (le module de contexte l'obtient à partir d'une méthode statique dans la classe de l'application).

0 votes

@VincentWilliams Oui, l'utilisation d'un ViewModel permet d'abstraire votre code des composants de l'interface utilisateur, ce qui facilite les tests. Mais, ce que je veux dire, c'est que la raison principale de ne pas inclure de Context, Views ou autres n'est pas pour des raisons de test, mais à cause du cycle de vie du ViewModel qui peut vous aider à éviter les crashs et autres erreurs. Quant au databinding, il peut vous aider avec les ressources parce que la plupart du temps, vous avez besoin d'accéder aux ressources dans le code parce que vous avez besoin d'appliquer cette chaîne, couleur, dimen dans votre mise en page, ce que le databinding peut faire directement.

0 votes

Oh ok, je vois ce que vous voulez dire mais le databinding ne m'aidera pas dans ce cas puisque j'ai besoin d'accéder à des chaînes pour les utiliser dans le modèle (elles pourraient être placées dans une classe de constantes au lieu de xml, je suppose) et aussi pour initialiser les SharedPreferences.

29voto

Vincent Williams Points 1229

Ce que j'ai fini par faire, c'est qu'au lieu d'avoir un contexte directement dans le ViewModel, j'ai créé des classes de fournisseurs telles que ResourceProvider qui me donneraient les ressources dont j'ai besoin, et j'ai injecté ces classes de fournisseurs dans mon ViewModel.

1 votes

J'utilise ResourcesProvider avec Dagger dans AppModule. Est-ce une bonne approche pour obtenir le contexte de ResourcesProvider ou AndroidViewModel est mieux pour obtenir le contexte des ressources ?

0 votes

@Vincent : Comment utiliser resourceProvider pour obtenir Drawable dans ViewModel ?

0 votes

@Vegeta Vous ajouteriez une méthode telle que getDrawableRes(@DrawableRes int id) dans la classe ResourceProvider

25voto

humble_wolf Points 345

Réponse courte - Ne faites pas ça

Pourquoi ?

Cela va à l'encontre de l'objectif des modèles de vue

Presque tout ce que vous pouvez faire dans le modèle de vue peut être fait dans l'activité/fragment en utilisant des instances LiveData et diverses autres approches recommandées.

74 votes

Pourquoi la classe AndroidViewModel existe-t-elle ?

2 votes

@AlexBerdnikov Le but de MVVM est d'isoler la vue (Activité/Fragment) du ViewModel encore plus que MVVM. Ainsi, il sera plus facile à tester.

5 votes

@free_style Merci pour cette clarification, mais la question reste posée : si nous ne devons pas conserver le contexte dans le ViewModel, pourquoi la classe AndroidViewModel existe-t-elle ? Son but est de fournir le contexte de l'application, n'est-ce pas ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X