emilpmp
emilpmp

Reputation: 1736

Updating view from recyclerview adapter using MVP pattern

I have a fragment called "OverviewFragment" and i have a presenter and Contract for it. I am creating a recyclerview adapter from "Overview fragment".

I have certain doubts regarding how to populate content inside the recyclerview. I have read that an adapter should only act as a View in MVP. But I have certain conditions to be satisfied to display the contents inside recyclerview. Where should i write this business logic?

Also there are answers in SO that we should not create presenters for viewholder. So basically i need to write business logics inside adapter to populate contents in recyclerview or I need to link that to "overview" fragment and then do the business logic in the fragment presenter.

Which is the correct method?

Upvotes: 0

Views: 1204

Answers (1)

Anton Kazakov
Anton Kazakov

Reputation: 2764

This is how I solve this prob. First all business logic located in Domain level, but you can create any other abstraction level to map your Data Models to View Layer Models. So only specific models located in presentation level. Then our adapter looks like this:

class CustomerCardsAdapter(
    var cards: MutableList<ListItem> = mutableListOf()
) : RecyclerView.Adapter<CustomerCardsAdapter.ItemViewHolder>() {

    lateinit var cardItemClick: (cardId: ClientCard) -> Unit

    override fun getItemViewType(position: Int) = cards[position].getListItemType().ordinal

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomerCardsAdapter.ItemViewHolder {
        val resId = when (ItemType.values()[viewType]) {
            ItemType.CARD -> R.layout.card_item
            ItemType.SIMPLE_ITEM -> R.layout.account_simple_item_view
            else -> throw IllegalArgumentException("viewType = $viewType create not support")
        }
        val view = LayoutInflater.from(viewGroup.context).inflate(resId, viewGroup, false)
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ItemViewHolder, position: Int) {
        val item = cards[position]
        when (item.getListItemType()) {
            ItemType.CARD -> viewHolder.populateCard(item)
            ItemType.SIMPLE_ITEM -> viewHolder.populateSimpleItem(item)
            else -> throw IllegalArgumentException("viewType = ${item.getListItemType()} create not support")
        }
    }

    override fun getItemCount() = cards.size

    inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun populateCard(item: ListItem) {
            with(itemView as CustomerCardView) {
                val card = item as ClientCard
                populate(card)
                setOnClickListener { cardItemClick(card) }
            }
        }

        fun populateSimpleItem(item: ListItem) {
            with(itemView as SimpleItemView) {
                val simpleItem = item as SimpleItem
                populate(simpleItem)
                setOnClickListener { simpleItem.action() }
            }
        }
    }
}

So onCreateViewHolder just inflates Views and onBindViewHolder method calling ViewHolders methods to populate data.

RecyclerView items inherited from View classes and contains logic to actually present our data to Views.

private const val CORNER_RADIUS = 4f
private const val SHADOW_ALPHA = 60

class CustomerCardView
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : LinearLayout(context, attrs, defStyleAttr) {

    private val imageCard by bind<BlurryCardView>(R.id.card_image)
    private val cardNumberTextView by bind<TextView>(R.id.card_number_text_view)
    private val imageIps by bind<IpsImageView>(R.id.card_image_ips)
    private val cardNameTextView by bind<TextView>(R.id.card_name_text_view)
    private val cardActivateView by bind<View>(R.id.card_activate)
    private val cardBlockView by bind<View>(R.id.card_unblock)
    private val imageContactlessPayView by bind<ImageView>(R.id.image_contactless_pay)
    private val smsNotificationsView by bind<ImageView>(R.id.image_sms_notifications)
    private val shadowView by bind<ShadowContainer>(R.id.card_shadow)

    fun populate(card: ClientCard) {
        with(card) {
            cardNameTextView.text = name
            cardNumberTextView.text = when {
                isNotActiveOnlyTokenization -> context.getString(R.string.card_is_not_active_only_tokenization)
                isTokenizationNotActivation -> context.getString(R.string.card_is_tokenization_not_activation)
                isTokenizationAndActivation -> context.getString(R.string.card_is_tokenization_and_activation)
                isBlockedToken -> context.getString(R.string.card_is_blocked_token)
                !isActivated -> context.getString(R.string.card_not_activated_text)
                supportsUnblocking() -> context.getString(R.string.card_blocked_text)
                isActivated && !supportsUnblocking() -> {
                    maskLast4Symbols(number)
                }
                else -> maskLast4Symbols(number)
            }
            imageContactlessPayView.setVisibility(tokens.isNotEmpty())
            smsNotificationsView.setVisibility(isAlfaCheck)

            populateCardImage(card)
        }
    }

    private fun populateCardImage(card: ClientCard) {
        imageCard.setCornerRadius(CORNER_RADIUS)
        shadowView.shadowParams.shadowAlpha = SHADOW_ALPHA
        post {
            val url = ImageUrlFactory.createUrl(card.cardType, imageCard.width, imageCard.height)
            imageCard.populateImage(url, {
                imageIps.populateWhite(card.ips)
                changeVisibility(card)
            })
        }
    }

    private fun changeVisibility(card: ClientCard) {
        with(card) {
            val blocked = (isLocked && isActivated && !isNotActiveOnlyTokenization) || isBlockedToken
            cardActivateView.setVisibility(card.showIsNotActiveIcon())
            cardBlockView.setVisibility(blocked)
        }
    }
}

Upvotes: 0

Related Questions