Livedata ha valori diversi in diverse attività
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
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...
}
Aggiornato: crea classe UserRepositorye inietta singleton UserRepository(manualmente o Dagger, Koin, ..)