Подходит ли livedata Builder для одноразовых операций?

#android #kotlin #mvvm #android-livedata

#Android #kotlin #mvvm #android-livedata

Вопрос:

Например, предположим, что у нас есть представление каталога товаров с возможностью добавления товара в корзину. Каждый раз, когда пользователь нажимает кнопку добавить в корзину, вызывается метод ViewModel AddToCart, который может выглядеть следующим образом:

 //inside viewModel
fun addToCart(item:Item): LiveData<Result> = liveData {
    val result = repository.addToCart(item) // loadUser is a suspend function.
    emit(result)
}


//inside view
addButton.onClickListener = {
     viewModel.addToCart(selectedItem).observe (viewLifecycleOwner, Observer () {
          result -> //show result
    }
}
 

Что произойдет после добавления, например, 5 элементов -> будет ли в памяти 5 объектов livedata, наблюдаемых представлением?

Если да, когда они будут очищены? И если да, следует ли нам избегать livedata builder для одноразовых операций, которые могут быть вызваны несколько раз?

Ответ №1:

Ваша реализация кажется неправильной! Вы постоянно возвращаете новый LiveData объект для каждого addToCard вызова функции. Что касается вашего первого вопроса, это Yes .

Если вы хотите сделать это правильно с помощью LiveData.

 // In ViewModel

private val _result = MutableLiveData<Result>()
val result: LiveData<Result>
   get() = _resu<

fun addToCart(item: Item) {
   viewModelScope.launch {
      // Call suspend functions
      result.value = ...
   }
}

// Activity/Fragment

viewModel.result.observe(lifecycleOwner) { result ->
   // Process the result  
   ...
}

viewModel.addToCart(selectedItem)
 

Все, что вам нужно сделать, это вызвать его из activity и обработать результат. Вы также можете использовать StateFlow для этой цели. Он также имеет расширение asLiveData , которое также преобразует Flow -> LiveData.

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

1. Ваш ответ указывает на «правильный» путь, но вы не ответили на вопрос, почему это неправильно и что происходит «при постоянном возврате новых LiveData».

2. Согласно коду OP, он будет вызывать наблюдателя только один раз при нажатии кнопки, а дальнейшие последующие вызовы создадут его новый экземпляр и вызовут наблюдателя только один раз. В конце концов, эти предыдущие LiveData объекты должны автоматически собираться мусором.

3. @kaustubhpatange спасибо, ваш ответ именно таков, как я делаю это в данный момент -> но я задал этот вопрос, потому что я нашел в нескольких местах предположение о том, что MutableLiveData может или даже должен быть заменен новым блоком livedata builder, и это меня беспокоило — похоже, что это небезопасно для повторяемых одноразовых действий.

4. «повторяемый одноразовый выстрел» = парадокс! Если вам нужны одноразовые живые данные, что часто бывает для событий навигации, то создание экземпляра живых данных каждый раз, когда подписчик хочет подписаться, — очень простой способ добиться этого. Очевидно, что это работает только там, где ожидается наличие одного подписчика. В противном случае, то есть повторное использование экземпляра, создает проблему, которую SingleLiveEvent намеревается решить. Зачем создавать дополнительные проблемы?

Ответ №2:

В соответствии с LiveData реализацией:

     public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null amp;amp; !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                      " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }
 

новый Наблюдатель ( wrapper ) добавляется каждый раз, когда вы просматриваете LiveData. Глядя на это, я был бы осторожен, создавая новых наблюдателей из события просмотра (щелчка). На данный момент я не могу сказать, может ли сборщик мусора освободить эти ресурсы.

Как упоминал @kaustubhpatange, у вас должен быть один LiveData с состоянием / значением, которое может быть изменено ViewModel с каждым новым результатом. Эти живые данные можно наблюдать (один раз) в вашей функции Activity или Fragment onCreate() :

 fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  viewModel.result.observe(lifecycleOwner) { result ->
    // handle the result
  }
}
 

Используя MutableLiveData в вашей ViewModel, вы можете в основном создавать LiveData только один раз, а затем заполнять его значениями из событий кликов, ответов и т. Д.

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

1. спасибо, именно так я и поступаю в данный момент… Я пытался оценить, следует ли мне переключиться на livedata { } builder, но вы только что подтвердили мои опасения относительно нескольких экземпляров.

Ответ №3:

TL; DR

Если ваша операция одноразовая, используйте Coroutine и LiveData .

Если ваша операция работает с потоками, которые вы можете использовать Flow .

Для одноразовых операций ваш подход в порядке. Я думаю, что в liveData builder нет утечки памяти. Если вы используете, например, свойство private backing для LiveData и наблюдаете за общедоступным LiveData , это может привести к другому поведению, например, получить последнее значение, прежде чем присваивать ему новое значение.