可在ViewHolder内部流动

问题描述 投票:0回答:2

我已经开始在android中再次编程,因为在过去的4年中它已经发生了很大的变化,我有点困惑。我试图从我的ViewHolder中的RxJava和Flowable异步加载我的服务器数据。我正在使用第三方库来处理我在适配器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方法中调用服务器,这是不理想的,并且每次用户滚动时都会调用它,在调用Activity并调用compositeDisposable之前我无法取消调用它们,这意味着我将为一个视图设置多个一次性:(

我的问题是,如何在ViewHolder中使用observable并在屏幕上没有显示时停止提供它?

android kotlin android-recyclerview rx-java rx-java2
2个回答
2
投票

你能做的最好的事情是:

  1. 依靠onViewAttachedToWindowonViewDetachedToWindow来控制您的订阅生命周期(订阅/处置)。
  2. onBindViewHolder上创建/检索您的observable
  3. 在父视图(片段/活动)中对您的适配器进行Nullify以强制适配器为所有窗口调用onViewDetachedToWindow以确保您没有泄漏任何内容(例如片段,在onDestoryView中调用list.adapter = null
  4. delaySubscription(例如400毫秒)添加到您的observables以减少滞后,同时快速滚动(当列表在动画上滑动时,您不想订阅任何内容)
  5. 确保您的observable的第一次发射是同步的,因此缓存的数据可以在屏幕旋转后的布局之前传递。这样您就可以避免在旋转过程中出现动画故障,Android可以正常恢复View状态。

第5点可能有点令人困惑,因此有一些资源更广泛地解释了这一点:

因此,基本上同步发射意味着在订阅方法返回之前,在订阅observable时将发出该项。例如,这种行为可能是通过在缓存之后不修改订阅发射线程的链中的某种缓存(如replay(1),startWith()等)来实现的。

例:

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

println("after subscribe")

假设我们订阅主线程。在那种情况下,我们打印:

    0
    after subscribe
    2

因此,正如您所看到的,即使两个项目都将在主线程上发出,其中一个将在订阅时立即到达,而第二个将在looper上发布。


0
投票

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)你应该用List实现execute方法吗?我不知道Foo的具体类型。

2)计数包含您需要的计数列表。在这个示例代码中,我使用for-each来匹配数据。但你可以使用自己的匹配代码。

3)与上面相同,您可以使用自己的匹配代码。

因此,订阅将在整个过程中仅被调用一次。

© www.soinside.com 2019 - 2024. All rights reserved.