Моя ViewModel не переживает изменения конфигурации - PullRequest
0 голосов
/ 18 ноября 2018

Я испытываю Dagger 2 с MVVM.Вот что у меня есть,

Мой модуль привязки ViewModel:

@Module
public abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(MedicationsViewModel.class)
    @Singleton
    abstract ViewModel bindMedicationsViewModel(MedicationsViewModel viewModel);

    @Binds
    @Singleton
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}

Мой фрагмент:

class MedicationsFragment : DaggerFragment() {
    private lateinit var binding : FragmentMedicationsBinding
    private lateinit var viewModel: MedicationsViewModel

    @Inject lateinit var viewModelFactory : ViewModelFactory

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        binding = FragmentMedicationsBinding.inflate(inflater, container, false)

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)

        val adapter = MedicationAdapter()
        binding.rvMedications.adapter = adapter

        viewModel.getMedications().observe(viewLifecycleOwner, Observer { items ->
            if (items != null) {adapter.submitList(items); adapter.notifyDataSetChanged()}
        })

        return binding.root
    }
}

И моя ViewModel выглядит так:

class MedicationsViewModel @Inject constructor(
    private val repository: MedicationRepository,
    ): ViewModel() {
        private var medications : MutableLiveData<MutableList<Medication>> = MutableLiveData()
        private val disposable: CompositeDisposable = CompositeDisposable()

        fun getMedications() : MutableLiveData<MutableList<Medication>>{
            getMockMedications()
            return medications }

        private fun getMockMedications(){
            val medication1 = Medication(1,"Mock Med 1", "2018-01-01","once per day",true,null)
            val medication2 = Medication(2,"Mock Med 2", "2018-01-02","once per day",false,"before 15 minutes")

            val mockList: MutableList<Medication> = mutableListOf()
            mockList.add(medication1)
            mockList.add(medication2)

            medications.postValue(mockList)
        }

        fun addMedication(medication: Medication){
            medications.value?.add(medication)
            medications.notifyObserver()
        }

        fun <T> MutableLiveData<T>.notifyObserver() {
            this.postValue(this.value)
        }
    }

С помощью кнопки в моем фрагменте я открываю действие и добавляю еще один элемент лекарства в мою модель просмотра.После завершения () из моей деятельности я вижу фрагмент добавленного лекарства в моем RecyclerView.Однако, когда я поворачиваю экран, я теряю только что добавленный предмет и снова ухожу с ложными предметами лекарства.Здесь я думаю, что создается новый экземпляр моей модели представления.

Возможно, проблема в том, что Dagger создает новый экземпляр моей модели представления каждый раз, когда мой фрагмент (и активность контейнера) воссоздается.Но я думал, что аннотирование моего ViewModelFactory с областью действия @Singleton исправило бы это.

Кто-нибудь может увидеть, чего мне не хватает?

Спасибо.

1 Ответ

0 голосов
/ 18 ноября 2018

Вы смешиваете как минимум 4 разные вещи здесь.


Возможно, проблема в том, что Dagger создает новый экземпляр моей модели представления каждый раз, когда мой фрагмент (и активность контейнера)воссоздан.

Вы объявляете viewModel введенным в поле при вставке фрагмента (поскольку вы добавили аннотацию @Inject к свойству ...)

@Inject lateinit var viewModelFactory : ViewModelFactory

Это означает, что Dagger будет вводить поле, когда вы звоните AndroidInjection.inject(..).Поскольку вы используете инъекцию конструктора (@Inject в конструктор MedicationsViewModel), Dagger каждый раз будет создавать новый объект (вы не охватывали класс), но вы никогда не используете объектВ любом случае, потому что ...


Сразу после , когда произошло, вы переопределяете поле с помощью viewModel из ViewModelFactory

viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)

Поскольку ViewModelFactory использует@Binds @IntoMap @Singleton bindMedicationsViewModel этот должен повторно использовать один и тот же объект и иметь @Singleton область видимости.Вы не поделились своей реализацией, поэтому, если это не тот же объект, вы можете неправильно обработать свой @Singleton компонент.

Больше проблем ...

Но яМысль, аннотирующая мою ViewModelFactory с областью действия @Singleton, исправила бы это.

Обычно следует помещать области видимости в класс , а не в привязку.

@Singleton // scope the class!
class MedicationsViewModel @Inject constructor()

Это потому, что обычно это детали реализации того, что есть у чего-то.Но в любом случае вам не следует использовать @Singleton здесь вообще, так как вы пытаетесь использовать ViewModel thingy .Если вы хотите использовать @Singleton для вашей ViewModel, тогда вам не нужен весь бит ViewModelProviders.Как вы можете видеть, это приведет только к ошибкам и путанице, поскольку оба имеют разные области действия и обрабатываются по-разному.

Лучшая настройка

Предполагая, что вы хотите использовать ViewModels, вы делаете , а не сфокусировать ViewModel на Dagger.Вы позволяете компонентам Android Arch обрабатывать вещи.Вы используете Dagger только для того, чтобы удалить шаблон создания объекта, чтобы ViewModel.Factory мог легко создать новую ViewModel при необходимости .Вы делаете это, привязывая ViewModel к коллекции, которая передается на вашу фабрику (карта с <Class, Provider<ViewModel>>). Вызов provider.get() всегда будет создавать новый объект, как и ожидалось.В конце концов, жизненный цикл обрабатывается компонентами арки.

@Binds
@IntoMap
@ViewModelKey(MedicationsViewModel.class) // no scope here!
abstract ViewModel bindMedicationsViewModel(MedicationsViewModel viewModel);

// neither here!
class MedicationsViewModel @Inject constructor()

После установки ViewModelStoreOwner (ViewModelProviders.of(this)) будет управлять жизненным циклом ViewModels и решать, когда повторно использовать или воссоздать модель.Если вы введете область видимости Dagger в ViewModel, она либо ничего не сделает (если область имеет более короткий срок действия), либо может привести к ошибкам или неожиданному поведению (при использовании @Singleton или более долгоживущих)

ДалееViewModel.Factory также должен быть с незаданной областью

@Binds // no scope here either!
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);

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

И последнее, но не менее важное: вы этого не делаетевведите поле с помощью Dagger, но вы используете ViewModelProviders для его инициализации.Хотя он не должен делать ничего, поскольку вы переопределяете его снова несколько мгновений спустя, он все еще является неприятным запахом и приведет только к путанице.

// in your fragment / acitvity
lateinit var viewModelFactory : ViewModelFactory // no @Inject for the viewmodel here!

// it will be assigned in onCreate!
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)

Надеюсь, это кое-что прояснит!

...