Livedata ha valori diversi in diverse attività

Aug 25 2020

Sto usando Android MVVM nella mia app kotlin. Ho una UserViewModelclasse che contiene tutti i dati relativi all'utente richiesti dall'app per funzionare.

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

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

Il currentUserviene recuperato da firestore al momento del lancio e viene reso disponibile a tutte le attività. Voglio accedere currentPlanall'oggetto all'interno User. Lo faccio:

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

Questo funziona bene nel mio HomeFragmentdentro il mio MainActivity. Il HomeFragmentha un pulsante di messaggio che prende utente ChatActivty.

Il problema è che quando eseguo la stessa chiamata di funzione ChatActivityottengo questo errore:

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

Perché userVM.valuediventa nullo in ChatActivity. Che cosa sto facendo di sbagliato?

Risposte

1 JeneaVranceanu Aug 24 2020 at 22:59

Qualche teoria

Esistono diversi modi per creare un'istanza di una ViewModelclasse (forse ne ho persi alcuni):

// #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)

Ciò che è comune è che si utilizza sempre, in modo implicito o esplicito , il proprietario dell'archivio modelli di visualizzazione . È un'attività o un frammento. Visualizza i modelli dal vivo finché è attivo il negozio del modello di visualizzazione. È vincolato dal ciclo di vita Activity / Fragment. Quando Fragment viene distrutto, verranno distrutti anche tutti i modelli di vista creati da quel frammento (se sono presenti riferimenti circolari). Lo stesso vale per l'attività.

Questo è anche il modo in cui vengono creati i modelli di visualizzazione condivisa . I frammenti possono riutilizzare lo stesso modello di visualizzazione purché sia ​​di proprietà dell'attività . Questo è il motivo per cui abbiamo due funzioni di caricamento lento: by activityViewModels()e by viewModels(). Il primo utilizzato principalmente per i modelli di visualizzazione condivisa.

Come risolvere il problema?

Non utilizzare un modello di visualizzazione e creare un singleton objecto utilizzare un modello di visualizzazione singleton (altamente sconsigliato).

Se la tua applicazione condivide alcuni dati durante il suo ciclo di vita e sei sicuro che questi dati dovrebbero essere archiviati e accessibili da un unico posto, singleton ti aiuterà.

È come avere una connessione a un database. Di solito, c'è solo una connessione aperta o viene aperta e chiusa su richiesta, ma non ho visto quest'ultima implementazione con i database, di solito con i file.

Suggerisco di usare objects di Kotlin :

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

Aggiornato: crea classe UserRepositorye inietta singleton UserRepository(manualmente o Dagger, Koin, ..)