Текучий внутри ViewHolder - PullRequest
       35

Текучий внутри ViewHolder

0 голосов
/ 27 февраля 2019

Я снова начал программировать на Android, и, поскольку он сильно изменился за последние 4 года, я немного запутался.Я пытаюсь загрузить свои данные с сервера асинхронно с помощью RxJava и Flowable внутри моего ViewHolder.Я использую стороннюю библиотеку для обработки моих представлений в адаптере MultiViewAdaptor .Вот мой код в моем Binder

class FooBinder : ItemBinder<Foo, FooBinder.ViewHolder>() {

        @Inject
        protected lateinit var requestFavoriteUseCase: SendFooFavoriteUseCase

        @Inject
        protected lateinit var compositeDisposable: CompositeDisposable

        @Inject
        protected lateinit var retrieveFooFavoriteCountUseCase: RetrieveFooFavoriteCountUseCase

        override fun create(inflater: LayoutInflater, parent: ViewGroup) =
            ViewHolder(inflater.inflate(R.layout.item_foo_content, parent, false))

        override fun bind(holder: ViewHolder, item: Foo) {
            holder.itemView.foo_content_creator.text = with(item.creator) { "$firstName $lastName" }

            GlideApp.with(holder.itemView)
                .load(item.creator.avatar)
                .dontAnimate()
                .placeholder(R.drawable.ic_action_person)
                .error(R.drawable.ic_action_person)
                .into(holder.itemView.foo_content_profile)

            retrieveFooFavoriteCountUseCase
                    .execute(item.id)
                    .applyComputationScheduler()
                    .subscribe { count ->
                        holder.itemView.foo_content_like_count.text = "$count"
                    }
                    .addTo(compositeDisposable)
        }

        inner class ViewHolder(view: View) : ViewHolder<Foo>(view) {

            private var favorite = false

            init {

                view
                    .foo_content_button_layout
                    .clicks()
                    .flatMap {
                        favorite = !favorite
                        requestFavoriteUseCase.execute(FavoriteVM(item.id, favorite)).toObservable()
                    }
                    .subscribe {
                        view.foo_content_like_count.text = it.toString()

                        var res = R.drawable.ic_action_like_default
                        if (favorite) {
                            res = R.drawable.ic_action_like_enabled
                        }
                        view.foo_content_like_icon.setImageResource(res)
                    }
                    .addTo(compositeDisposable)
            }

        }

    }

Как вы видите, я должен вызывать сервер в bind методе, который не идеален и вызывается каждый разПользователь прокручивает также, я не могу отменить их, пока действие не будет удалено, и не будет вызван CompositeDisposable , что означает, что у меня будет многоразовое одноразовое отображение :(

Мой вопрос, как я могу использоватьможно увидеть внутри ViewHolder и прекратить кормить его, когда он не отображается на экране?

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

Лучшее, что вы можете сделать, это:

  1. Положитесь на onViewAttachedToWindow и onViewDetachedToWindow для управления жизненным циклом подписки (подписка / удаление).
  2. Создание / получение вашегонаблюдаемые в onBindViewHolder
  3. Обнулите ваш адаптер на утилизаторе в родительском представлении (фрагмент / действие), чтобы заставить адаптер вызывать onViewDetachedToWindow для всех окон, чтобы убедиться, что вы ничего не пропускаете (пример для фрагмента, в onDestoryView вызов list.adapter = null)
  4. Добавьте delaySubscription (например, 400 мс) к вашим наблюдаемым, чтобы уменьшить отставание при быстрой прокрутке (вы не хотите подписываться на что-либо, пока список скользит по анимации)
  5. Убедитесь, что первое излучение ваших наблюдаемых является синхронным, чтобы кэшированные данные могли быть доставлены до размещения после поворота экрана.Таким образом, вы избежите сбоев анимации во время поворота, и Android сможет правильно восстановить состояние просмотра.

Точка 5 может немного сбивать с толку, поэтому некоторые ресурсы объясняют это более широко:

Таким образом, в основном синхронное излучение означает, что элемент будетиспускается при подписке на наблюдаемый, прежде чем возвращается метод подписки.Например, такое поведение может быть достигнуто с помощью некоторого вида кэширования (например, replay (1), startWith () и т. Д.) В цепочке, которая не изменяет потоки эмиссии подписки после кэширования.

Пример:

Observable.create(2)
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .startWith(0)
    .subscribe(println(it))

println("after subscribe")

Допустим, мы подписались на основную ветку.В этом случае мы собираемся напечатать:

    0
    after subscribe
    2

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

0 голосов
/ 27 февраля 2019

bind методы (первоначально onBindViewHolder) вызывались каждый раз, когда представление пытается показать пользователю.(Действительно, этот метод вызывается снова, когда пользователь выполняет прокрутку вниз и вверх)

Итак, сетевой процесс в onBindViewHolder, называемый сетевым процессом, является плохой идеей с точки зрения проблем жизненного цикла или производительности.

Я предложил этот способ для достижения этого.

  1. получение всех данных, связанных item.id до инициализации адаптера.
  2. Сопоставление данных с Foo и извлеченными данными
  3. Инициализация адаптера с результатом шага 2.
  4. В onBindViewHolder, holder.itemView.foo_content_like_count.text = item.count вместо этого подпишитесь retrieveFooFavoriteCountUseCase.

Пример кода, он может не работать хорошо.Я просто привожу, как это работает.)

  val items = ...
  retrieveFooFavoriteCountUseCase
      .execute(items.map { it.id }) // 1)
      .applyComputationScheduler()
      .subscribe { counts -> 
          for ((index, count) in counts.indexed()) { // 2)
              items[index] = items[index].apply { this.count = count } // 3)
          }

          // TODO: initialize adapter with items
      }.addTo(compositeDisposable)

1) вы должны реализовать методы execute с помощью List?Я не знаю конкретный тип Foo.

2) count содержит список нужных вам счетчиков.В этом примере кода я использовал for-each для сопоставления данных.но вы можете использовать собственный соответствующий код.

3) То же, что и выше, вы можете использовать собственный соответствующий код.

В результате подписка будет вызываться только один раз в течение всего процесса.

...