Livedata имеет разные значения в разных действиях

#android #kotlin #mvvm

#Android #kotlin #mvvm

Вопрос:

Я использую Android MVVM в своем приложении kotlin. У меня есть UserViewModel класс, в котором есть все пользовательские данные, необходимые приложению для работы.

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

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

currentUser Данные извлекаются из firestore при запуске и становятся доступными для всех действий. Я хочу получить доступ к currentPlan объекту внутри User . Я делаю это:

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

Это прекрасно работает у меня HomeFragment внутри меня MainActivity . У HomeFragment есть кнопка сообщения, которая приводит пользователя к ChatActivty .

Проблема в том, что когда я выполняю один и тот же вызов функции в ChatActivity , я получаю эту ошибку:

 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
  

Почему userVM.value становится нулевым в ChatActivity . Что я делаю не так?

Комментарии:

1. У вас есть другой экземпляр ViewModel в вашем ChatActivity. ViewModels не являются одиночными

2. Вау! Как мне это исправить? Я объявляю их следующим образом: private val userVM: UserViewModel by viewModels() @tyczj

3. Короче говоря, вы ожидаете, что ViewModel будет одноэлементным, когда это не так, они создаются для каждого действия. Вы можете создать какой-то класс «UserService», который действует как одноэлементный, и вы передаете этот класс в свою модель представления. Возможно, вы захотите изучить использование Dependency Injection

4. Используйте несколько фрагментов в одном действии вместо нескольких действий. Используйте by activityViewModels() во фрагменте, и разные фрагменты получат один и тот же экземпляр.

Ответ №1:

Некоторая теория

Существует несколько способов создания экземпляра ViewModel класса (возможно, я пропустил некоторые):

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

Общим является то, что вы всегда неявно или явно используете владельца хранилища моделей просмотра. Это либо действие, либо фрагмент. Модели просмотра существуют до тех пор, пока существует хранилище моделей просмотра. Это ограничено жизненным циклом действия / фрагмента. При уничтожении фрагмента все модели представлений, созданные этим фрагментом, также будут уничтожены (при наличии циклических ссылок). То же самое касается действия.

Именно так создаются модели общего просмотра. Фрагменты могут повторно использовать одну и ту же модель представления, если она принадлежит действию. Вот почему у нас есть две функции отложенной загрузки: by activityViewModels() и by viewModels() . Первый в основном используется для моделей общего просмотра.

Как устранить проблему?

Либо не используйте модель представления и создайте одноэлементный object , либо используйте одноэлементную модель представления (настоятельно не рекомендую).

Если ваше приложение использует некоторые данные в течение своего жизненного цикла, и вы уверены, что эти данные следует хранить и получать к ним доступ из одного места — singleton поможет.

Это похоже на подключение к базе данных. Обычно существует только одно открытое соединение или оно открывается и закрывается по запросу, но я не видел последней реализации с базами данных, обычно с файлами.

Я предлагаю использовать object s от Kotlin:

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

Ответ №2:

Обновлено: Создайте класс UserRepository и введите singleton UserRepository (вручную или Dagger, Koin, ..)

Комментарии:

1. Это не поможет при использовании в разных действиях.

2. Обновленный ответ.

3. Это действительно вариант!