【问题标题】:How to implement a RecyclerView when using Model View Presenter pattern?使用 Model View Presenter 模式时如何实现 RecyclerView?
【发布时间】:2020-08-19 12:30:13
【问题描述】:

我是第一次使用 MVP,我相信我明白了,但我不确定 RecyclerView。据我所知,MVP 是为了让视图尽可能被动,以便所有业务逻辑都交给 Presenter,但是 Recycler 视图如何实现呢?

到目前为止,这是我的代码:

合同

public interface PhotosContract {
// View
interface View {//: IBaseActivity {
    fun showPhotos(photos: ArrayList<Photo>)
    fun showText(message: String)
}
// Presenter
interface Presenter {//: IBasePresenter<View> {
    fun getPhotos()
}
}

演示者

public class PhotosPresenter(var view: PhotosContract.View) :PhotosContract.Presenter {

var dataList = ArrayList<Photo>()

override fun getPhotos() {
    //call for endpoint
    val call : Call<ArrayList<Photo>> = ApiClient.getClient.getPhotos()

    call.enqueue(object: Callback<ArrayList<Photo>> {
        override fun onFailure(call: Call<ArrayList<Photo>>, t: Throwable) {
            Log.d("FAIL","FAILED")
        }

        override fun onResponse(
            call: Call<ArrayList<Photo>>,
            response: Response<ArrayList<Photo>>
        )
        {
            Log.d("SUCCESS","SUCCESSED")

            dataList.addAll(response!!.body()!!)
            Log.d("SIZELIST",dataList.size.toString())

            view.showPhotos(dataList)
            view.showText("SUCCESS")
        }

    })

}

}

RecyclerViewAdapter

class PhotosAdapter(private var dataList: List<Photo>, private val context: Context) : RecyclerView.Adapter<PhotosAdapter.PhotosViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotosAdapter.PhotosViewHolder {
    return PhotosViewHolder(LayoutInflater.from(this.context).inflate(R.layout.list_item_home, parent, false))
}

override fun getItemCount(): Int {
    return dataList.size
}

override fun onBindViewHolder(holder: PhotosAdapter.PhotosViewHolder, position: Int) {
    val dataModel = dataList[position]
    holder.titleTextView.text = dataModel.title
}

class PhotosViewHolder(itemLayoutView: View) : RecyclerView.ViewHolder(itemLayoutView){
    var titleTextView: TextView = itemLayoutView.tv_title
}

}

活动

class PhotosActivity : AppCompatActivity(),PhotosContract.View {
private lateinit var presenter: PhotosPresenter

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_photos)

    presenter = PhotosPresenter(this)

    presenter.getPhotos()

}

override fun showPhotos(photos: ArrayList<Photo>) {
    photosRecyclerView.layoutManager = LinearLayoutManager(this)
    photosRecyclerView.adapter = PhotosAdapter(photos,this)

    photosRecyclerView.adapter?.notifyDataSetChanged()
}

override fun showText(message: String) {
    Toast.makeText(this,message,Toast.LENGTH_LONG).show()
}


 }

【问题讨论】:

    标签: android kotlin mvp


    【解决方案1】:

    这是我在使用 MVP 时的做法。在您的合同中,定义一个名为ItemView 的附加视图。我这样做的方式是,每个项目视图持有者都是 MVP 视图。视图是愚蠢的,所以它只是在发生某些事情时调用演示者,然后演示者将其回调。

    interface MyContract {
    
        interface View {
            fun setTitle(title: String)
        }
    
        // Add this interface here
        interface ItemView {
            fun bindItem(item: Item)
        }
    
        interface Presenter {
            fun attach(view: View)
            fun detach()
    
            val itemCount: Int
            fun onItemClicked(pos: Int)
            fun onBindItemView(itemView: ItemView, pos: Int)
        }
    }
    

    适配器也很笨。当它需要绑定一个item view holder时,它会调用presenter来做。

    class MyAdapter : RecyclerView.Adapter<ViewHolder>() {
    
        // How many items do we have? We don't know, ask the presenter.
        override fun getItemCount() = presenter?.itemCount ?: 0
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            // How to bind the item if we only have position? We don't know, ask the presenter.
            presenter?.onBindItemView(holder, position)
        }
    
        // ...
    }
    

    ViewHolder 实现了MyContract.ItemView 接口。同样,这只是一个视图,因此它本身没有责任。它只是委托给演示者。

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), MyContract.ItemView {
    
        private val txv: TextView = view.findViewById(R.id.text_view)
    
        init {
            view.setOnClickListener {
                // What to do here, we only have the item's position? Call the presenter.
                presenter?.onItemClicked(adapterPosition)
            }
        }
    
        override fun bindItem(item: Item) {
            txv.text = item.text
        }
    }
    

    最后是演示者:

    class MyPresenter : MyContract.Presenter {
    
        private var view: View? = null
    
        private val items = mutableListOf<Item>()
    
        override fun attach(view: View) {
            this.view = view
            // ...
        }
    
        override fun detach() {
            view = null
        }
    
        override val itemCount: Int
            get() = items.size
    
        override fun onItemClicked(pos: Int) {
            val item = items[pos]
            // ...
        }
    
        override fun onBindItemView(itemView: ItemView, pos: Int) {
            itemView.bindItem(items[pos])
        }
    
        // ...
    }
    

    完整性视图,但这里没有新内容:

    class MyView : Fragment(), MyContract.View {
    
        private var presenter: Presenter? = null
    
        override fun onViewCreated(view: View) {
            // Attach presenter
            presenter = MyPresenter()
            presenter?.attach(this)
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
    
            // Detach the presenter
            presenter?.detach()
            presenter = null
        }
    
        // ...
    }
    

    这只是一种方法,我相信还有很多其他方法。我只是喜欢这个,因为所有的责任都属于演示者,其他任何地方都没有业务逻辑。

    最终,您需要对列表进行更改并通知适配器。为此,请在您的 View 合约中添加几个方法,例如 notifyItemInserted(pos: Int),并在需要时从演示者那里调用它们。或者,更好的是,使用DiffUtil,这样您就不必自己管理了!

    不过,一旦您对 MVP 有了很好的了解,我强烈建议您转向 MVVM,因为它是 Google 推广的官方架构。大多数人也发现它比 MVP 方便得多。

    如果您有任何问题,请不要犹豫。

    【讨论】:

    • 我在执行您的建议时遇到问题。首先,当我尝试在 Presenter 类中实现 Contract.Presenter 时,它向我显示了一个错误,即 Presenter 有一个我在 Contact () 中看到的参数另外,我如何在 recyclerView 适配器中调用演示者?
    • 糟糕,我忘了删除通用参数,它来自我的一个项目。我改变了它。要从适配器调用演示者,您可以在适配器的构造函数中传递演示者,或者使适配器成为视图的内部类。
    • 好的,非常感谢。现在它起作用了。我想问的最后一件事是你如何在这里对另一个活动做一个意图。我看到了另一个 stackoverflow 问题,但似乎人们无法就一个解决方案达成一致。 @尼古拉斯
    • 向视图合约添加一个方法,如openActivity(),并从演示者处调用它。
    猜你喜欢
    • 2010-12-31
    • 1970-01-01
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 2014-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多