Как реализовать RecyclerView при использовании шаблона Model View Presenter? - PullRequest
0 голосов
/ 05 мая 2020

Я впервые работаю с MVP и думаю, что у меня есть представление об этом, но я не уверен в RecyclerView. Насколько я могу сказать, MVP стремится сделать представления как можно более пассивными, поэтому все бизнес-логики c передаются в Presenter, но как этого можно достичь для Recycler View?

Вот мой код на данный момент :

Контракт

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

Presenter

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()
}


 }

1 Ответ

1 голос
/ 05 мая 2020

Вот как я это сделал, когда использовал 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)
    }
}

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

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
    }

    // ...
}

Это только один способ сделать это, я уверен, что есть много других. Мне просто нравится этот, потому что вся ответственность лежит на докладчике, здесь нет бизнес-логов c где-либо еще.

В конце концов, вы захотите внести изменения в свой список и уведомить адаптер. Для этого добавьте в свой контракт View пару методов, например notifyItemInserted(pos: Int), и при необходимости вызовите их из докладчика. Или, еще лучше, используйте DiffUtil, чтобы вам не приходилось управлять им самостоятельно!

Однако, когда вы хорошо разбираетесь в MVP, я настоятельно рекомендую вам перейти на MVVM, поскольку это официальная архитектура, продвигаемая от Google. Большинство людей находят его намного удобнее, чем MVP.

Если у вас есть вопросы, не стесняйтесь.

...