Livedata a différentes valeurs dans différentes activités

Aug 25 2020

J'utilise Android MVVM dans mon application kotlin. J'ai une UserViewModelclasse qui contient toutes les données relatives à l'utilisateur dont l'application a besoin pour fonctionner.

class UserViewModel(application: Application) : AndroidViewModel(application) {

    val currentUid = MutableLiveData<String>()
    var currentUser = MutableLiveData<User>()
    // More code here...
}

Le currentUserest récupéré au firestore au lancement et mis à la disposition de toutes les activités. Je veux accéder à l' currentPlanobjet à l'intérieur User. Je fais ça:

someFunc(userViewModel.value!!.currentPlan!!)

Cela fonctionne bien dans mon HomeFragmentintérieur MainActivity. Le HomeFragmenta un bouton de message qui amène l'utilisateur à ChatActivty.

Le problème est que lorsque j'exécute le même appel de fonction, ChatActivityj'obtiens cette erreur:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.**, PID: 30420
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.makeshaadi/com.makeshaadi.ChatActivity}: kotlin.KotlinNullPointerException
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:201) at android.app.ActivityThread.main(ActivityThread.java:6810) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
     Caused by: kotlin.KotlinNullPointerException
        at com.makeshaadi.ChatActivity.onCreate(ChatActivity.kt:52)
        at android.app.Activity.performCreate(Activity.java:7224)
        at android.app.Activity.performCreate(Activity.java:7213)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:201)  at android.app.ActivityThread.main(ActivityThread.java:6810)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873) 
I/Process: Sending signal. PID: 30420 SIG: 9

Pourquoi userVM.valuedevient nul dans ChatActivity. Qu'est-ce que je fais mal?

Réponses

1 JeneaVranceanu Aug 24 2020 at 22:59

Une théorie

Il existe plusieurs façons de créer une instance d'une ViewModelclasse (peut-être en ai-je manqué):

// #1 lazy loaded class level variable. 
// Activity serves as view model store
val vm: UserViewModel by activityViewModels()

// #2 lazy loaded class level variable. 
// Activity or Fragment serves as a view model store owner.
// Depends on from which class it is used.
val vm: UserViewModel by viewModels()

// #3 A variable defined in a function scope. 
// `this` is either Activity or a Fragment. The same as #2 but not lazy.
val vm = ViewModelProvider(this).get(UserViewModel::class.java)

// #4 The same as the third option but with factory parameter. 
// Used in cases when view model constructor accepts custom arguments.
// Factory takes care of passing correct parameters.
val vm = ViewModelProvider(this, factory).get(UserViewModel::class.java)

Ce qui est courant, c'est que vous utilisez toujours implicitement ou explicitement le propriétaire du magasin de modèles de vue . C'est soit une activité, soit un fragment. Les modèles de vue vivent aussi longtemps que le magasin de modèles de vues dure. Il est limité par le cycle de vie de l'activité / du fragment. Lorsque le fragment est détruit, tous les modèles de vue créés par ce fragment seront également détruits (s'il existe des références circulaires). Il en va de même pour l'activité.

C'est également ainsi que les modèles de vue partagée sont créés. Les fragments peuvent réutiliser le même modèle de vue tant qu'il appartient à l'activité . C'est pourquoi nous avons deux fonctions de chargement différé: by activityViewModels()et by viewModels(). Le premier est principalement utilisé pour les modèles de vue partagée.

Comment résoudre le problème?

N'utilisez pas de modèle de vue et créez un singleton objectou utilisez un modèle de vue singleton (fortement déconseillé).

Si votre application partage certaines données au cours de son cycle de vie et que vous êtes sûr que ces données doivent être stockées et accessibles à partir d'un seul endroit, singleton vous aidera.

C'est comme avoir une connexion à une base de données. Habituellement, il n'y a qu'une seule connexion ouverte ou elle est ouverte et fermée sur demande, mais je n'ai pas vu la dernière implémentation avec des bases de données, généralement avec des fichiers.

Je suggère d'utiliser les objects de Kotlin :

object MySingleton {
    val currentUid = MutableLiveData<String>()
    var currentUser = MutableLiveData<User>()
    // More code here...
}
PetrusNguyễnTháiHọc Aug 24 2020 at 22:41

Mise à jour: créer une classe UserRepositoryet injecter singleton UserRepository(manuellement, ou Dagger, Koin, ..)