Thursday, 18 August 2022

How to insert different item e.g. native ads on each 10 items of recyclerView using diffutil "AsyncListDiffer"

Since I decided to migrate from setting items and notifyDataSetChanged() to using AsyncListDiffer SubmitList method I am facing problems with implementing native ads to RecyclerView in my current adapter class

I used this method in the old question and it worked perfectly fine with my old structure of the app, but when I tried the AsyncListDiffer it did not work

**the problem

When I use this logic in getItemViewType

if (shouldLoadNativeAds && position != 0 && position % 10 == 0) VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT

it replaces the content item with AD item e.g. if the item is 20 the recyclerView shows 18 and 2 for ads as for I used the following code (it's duplicating items when scrolling)**

video showing the duplicating problem

the old structure of PostAdapter

class PostAdapter(
    private val titleAndGridLayout: TitleAndGridLayout
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var items = arrayListOf<Item>()

    private var context: Context? = null

    val VIEW_TYPE_CONTENT = 1
    val VIEW_TYPE_AD_CARD_LAYOUT = 2
    val VIEW_TYPE_AD_GRID_LAYOUT = 3

    var isDestroyed = false

    private var adsCnt = 3


    var viewType = 0
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    fun submitList(items: List<Item>) {
        this.items.addAll(items)
        notifyDataSetChanged()
    }

    fun clearList() {
        this.items.clear()
        notifyDataSetChanged()
    }


    override fun getItemViewType(position: Int): Int {

        val androidVersionCode: Int = Build.VERSION.SDK_INT

        val shouldLoadNativeAds: Boolean = (context?.let { Utils.hasInternetConnection(it) } == true
                && androidVersionCode >= Build.VERSION_CODES.N)


        return when (viewType) {
            CARD, CARD_MAGAZINE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0)
                    VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT
            }
            TITLE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            GRID -> {
                if (shouldLoadNativeAds && (position + 1) % 10 == 0 && (position + 1) != 1) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            else -> VIEW_TYPE_CONTENT
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        this@PostAdapter.context = parent.context


        when (viewType) {
            VIEW_TYPE_CONTENT -> {
                when (this.viewType) {
                    CARD -> {
                        val cardLayoutBinding: CardLayoutBinding =
                            CardLayoutBinding.inflate(inflater, parent, false)
                        return CardViewHolder(cardLayoutBinding)
                    }
                    CARD_MAGAZINE -> {
                        val cardMagazineBinding: CardMagazineBinding =
                            CardMagazineBinding.inflate(
                                LayoutInflater.from(parent.context),
                                parent,
                                false
                            )
                        return CardMagazineViewHolder(cardMagazineBinding)
                    }
                    TITLE -> {
                        val titleLayoutBinding: TitleLayoutBinding =
                            TitleLayoutBinding.inflate(inflater, parent, false)
                        return TitleViewHolder(titleLayoutBinding)
                    }
                    else -> {
                        val gridLayoutBinding: GridLayoutBinding =
                            GridLayoutBinding.inflate(inflater, parent, false)
                        return GridViewHolder(gridLayoutBinding)
                    }
                }
            }
            VIEW_TYPE_AD_CARD_LAYOUT -> {

                val nativeAdRowBinding = AdUnifiedBinding.inflate(inflater, parent, false)
                return AdViewHolder(nativeAdRowBinding)

            }
            else -> {

                val nativeAdRowTitleGridBinding =
                    NativeAdRowTitleGridBinding.inflate(inflater, parent, false)
                return AdViewHolderGrid(nativeAdRowTitleGridBinding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item: Item = items[getRealPosition(position)]

        val intent = Intent(holder.itemView.context, DetailsActivity::class.java)

        if (getItemViewType(position) == VIEW_TYPE_CONTENT) {
            when (this.viewType) {
                CARD -> if (holder is CardViewHolder) {

                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                TITLE -> if (holder is TitleViewHolder) {
                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                GRID -> if (holder is GridViewHolder) {
                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
            }
        } else if (getItemViewType(position) == VIEW_TYPE_AD_CARD_LAYOUT) {
            if (holder is AdViewHolder) {
                holder.bindAdData()
            }
        } else {
            if (holder is AdViewHolderGrid) {
                holder.bindAdData()


                if (getItemViewType(position) == VIEW_TYPE_AD_GRID_LAYOUT) {
                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()
                }


            }
        }
    }


    private fun getRealPosition(position: Int): Int {
        return if (LIST_AD_DELTA == 0) {
            position
        } else {
            position - position / LIST_AD_DELTA
        }
    }

    override fun getItemCount(): Int {
        var additionalContent: Int = 0
        if (items.size > 0 && LIST_AD_DELTA > 0 && items.size > LIST_AD_DELTA) {
            additionalContent = (items.size + (items.size / LIST_AD_DELTA)) / LIST_AD_DELTA
        }
        return items.size + additionalContent;
    }

    override fun getItemId(position: Int): Long {
        return getRealPosition(position).toLong()
    }

    }


    companion object {
        private const val CARD = 0
        private const val CARD_MAGAZINE = 1
        private const val TITLE = 2
        private const val GRID = 3
        private const val TAG = "POST_ADAPTER"
        private const val LIST_AD_DELTA = 10

    }

    init {
        setHasStableIds(true)
    }


   
}

The new one with AsyncListDiffer

class PostAdapter(
     private val titleAndGridLayout: TitleAndGridLayout
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
    private var context:Context?=null

    val VIEW_TYPE_CONTENT = 1
    val VIEW_TYPE_AD_CARD_LAYOUT = 2
    val VIEW_TYPE_AD_GRID_LAYOUT = 3

    var isDestroyed = false

    private var adsCnt = 3





    var viewType = 0
        set(value) {
            field = value
            notifyDataSetChanged()
        }



    private val differCallback = object : DiffUtil.ItemCallback<Item>() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return (oldItem.id == newItem.id)
        }

        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
          return (oldItem == newItem)
        }
    }

    val differ = AsyncListDiffer(this, differCallback)



    override fun getItemViewType(position: Int): Int {

        val androidVersionCode: Int = Build.VERSION.SDK_INT

        val shouldLoadNativeAds: Boolean = (context?.let { Utils.hasInternetConnection(it) } == true
                && androidVersionCode >= Build.VERSION_CODES.N)


        return when (viewType) {
            CARD, CARD_MAGAZINE -> {
                if (shouldLoadNativeAds && position > 0 && position  % LIST_AD_DELTA == 0)
                    VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT
            }
            TITLE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            else -> VIEW_TYPE_CONTENT
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        this@PostAdapter.context = parent.context

        val nativeAdRowBinding = AdUnifiedBinding.inflate(inflater, parent, false)
        val nativeAdRowTitleGridBinding =
            NativeAdRowTitleGridBinding.inflate(inflater, parent, false)
        when (viewType) {
            VIEW_TYPE_CONTENT -> {
                when (this.viewType) {
                    CARD -> {
                        val cardLayoutBinding: CardLayoutBinding =
                            CardLayoutBinding.inflate(inflater, parent, false)
                        return CardViewHolder(cardLayoutBinding)
                    }
                    CARD_MAGAZINE -> {
                        val cardMagazineBinding: CardMagazineBinding =
                            CardMagazineBinding.inflate(
                                LayoutInflater.from(parent.context),
                                parent,
                                false
                            )
                        return CardMagazineViewHolder(cardMagazineBinding)
                    }
                    TITLE -> {
                        val titleLayoutBinding: TitleLayoutBinding =
                            TitleLayoutBinding.inflate(inflater, parent, false)
                        return TitleViewHolder(titleLayoutBinding)
                    }
                    else -> {
                        val gridLayoutBinding: GridLayoutBinding =
                            GridLayoutBinding.inflate(inflater, parent, false)
                        return GridViewHolder(gridLayoutBinding)
                    }
                }
            }
            VIEW_TYPE_AD_CARD_LAYOUT -> {

                                return AdViewHolder(nativeAdRowBinding)

            }
            else -> {

                                return AdViewHolderGrid(nativeAdRowTitleGridBinding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item: Item = differ.currentList[getRealPosition(position)]

        val intent = Intent(holder.itemView.context, DetailsActivity::class.java)

        if (getItemViewType(getRealPosition(position)) == VIEW_TYPE_CONTENT) {
            when (this.viewType) {
                CARD -> if (holder is CardViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                TITLE -> if (holder is TitleViewHolder) {

                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems("titleLayout")

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
                GRID -> if (holder is GridViewHolder) {

                    holder.bind(item)
                    if (position == itemCount - 1) {
                        titleAndGridLayout.tellFragmentToGetItems("gridLayout")
                    }
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra("postItem", item)
                        view.context.startActivity(intent)
                    }
                }
            }
        } else if (getItemViewType(getRealPosition(position)) == VIEW_TYPE_AD_CARD_LAYOUT) {
            if (holder is AdViewHolder) {
                holder.bindAdData()
            }
        } else {
            holder as AdViewHolderGrid
            holder.bindAdData()
            holder.setIsRecyclable(false)
            if (position == itemCount - 1) {
                titleAndGridLayout.tellFragmentToGetItems("gridLayout")
            }
        }
    }




     private fun getRealPosition(position: Int): Int {
        return if (LIST_AD_DELTA == 0) {
            position
        } else {
            position - position / LIST_AD_DELTA
        }
    }

    override fun getItemCount(): Int {
        var additionalContent: Int = 0
        val itemsSize = differ.currentList.size
        
        if (itemsSize > 0 && LIST_AD_DELTA > 0 && itemsSize > LIST_AD_DELTA) {
            additionalContent = (itemsSize + (itemsSize / LIST_AD_DELTA)) / LIST_AD_DELTA
        }
        return itemsSize + additionalContent;
    }

    override fun getItemId(position: Int): Long {
        return getRealPosition(position).toLong()
    }


    companion object {
        private const val CARD = 0
        private const val CARD_MAGAZINE = 1
        private const val TITLE = 2
        private const val GRID = 3
        private const val TAG = "POST_ADAPTER"
        private const val LIST_AD_DELTA = 10

    }

    init {
        setHasStableIds(true)
    }


    inner class AdViewHolder(private val binding: AdUnifiedBinding) :
        RecyclerView.ViewHolder(binding.root) {

        private val videoOptions = VideoOptions.Builder()
            .setStartMuted(false)
            .build()


        private var adOptions = NativeAdOptions.Builder()
            .setVideoOptions(videoOptions)
            .build()

        fun bindAdData() {
            val builder =
                AdLoader.Builder(binding.root.context, "ca-app-pub-3940256099942544/2247696110")

            builder.forNativeAd { nativeAd ->
                                                               if(isDestroyed){

                   Toast.makeText(this@PostAdapter.context, "$isDestroyed", Toast.LENGTH_SHORT).show()

                   Log.e(TAG, "bindAdData: $isDestroyed", )

                   nativeAd.destroy()

                   Log.e(TAG, "bindAdData: ${nativeAd.body.toString()}", )
               }
                populateNativeAdView(nativeAd, binding)
            }

            builder.withNativeAdOptions(adOptions)


            val adLoader = builder
                .withAdListener(
                    object : AdListener() {
                        override fun onAdFailedToLoad(loadAdError: LoadAdError) {

                            if (adsCnt > 0) {
                                bindAdData()
                            } else {
                                adsCnt -= 1
                            }

                            val error =
                                """
           domain: ${loadAdError.domain}, code: ${loadAdError.code}, message: ${loadAdError.message}
          """"
                            Toast.makeText(
                                binding.root.context,
                                "Failed to load native ad with error $error",
                                Toast.LENGTH_SHORT
                            )
                                .show()
                        }
                    }
                )
                .build()

            adLoader.loadAds(AdRequest.Builder().build(), 5)



        }

        private fun populateNativeAdView(nativeAd: NativeAd, unifiedAdBinding: AdUnifiedBinding) {
            val nativeAdView = unifiedAdBinding.root

                        nativeAdView.mediaView = unifiedAdBinding.adMedia

                        nativeAdView.headlineView = unifiedAdBinding.adHeadline
            nativeAdView.bodyView = unifiedAdBinding.adBody
            nativeAdView.callToActionView = unifiedAdBinding.adCallToAction
            nativeAdView.iconView = unifiedAdBinding.adAppIcon
            nativeAdView.priceView = unifiedAdBinding.adPrice
            nativeAdView.starRatingView = unifiedAdBinding.adStars
            nativeAdView.storeView = unifiedAdBinding.adStore
            nativeAdView.advertiserView = unifiedAdBinding.adAdvertiser

                        unifiedAdBinding.adHeadline.text = nativeAd.headline
            nativeAd.mediaContent?.let { unifiedAdBinding.adMedia.setMediaContent(it) }

                                    if (nativeAd.body == null) {
                unifiedAdBinding.adBody.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adBody.visibility = View.VISIBLE
                unifiedAdBinding.adBody.text = nativeAd.body
            }

            if (nativeAd.callToAction == null) {
                unifiedAdBinding.adCallToAction.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adCallToAction.visibility = View.VISIBLE
                unifiedAdBinding.adCallToAction.text = nativeAd.callToAction
            }

            if (nativeAd.icon == null) {
                unifiedAdBinding.adAppIcon.visibility = View.GONE
            } else {
                unifiedAdBinding.adAppIcon.setImageDrawable(nativeAd.icon?.drawable)
                unifiedAdBinding.adAppIcon.visibility = View.VISIBLE
            }

            if (nativeAd.price == null) {
                unifiedAdBinding.adPrice.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adPrice.visibility = View.VISIBLE
                unifiedAdBinding.adPrice.text = nativeAd.price
            }

            if (nativeAd.store == null) {
                unifiedAdBinding.adStore.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adStore.visibility = View.VISIBLE
                unifiedAdBinding.adStore.text = nativeAd.store
            }

            if (nativeAd.starRating == null) {
                unifiedAdBinding.adStars.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adStars.rating = nativeAd.starRating!!.toFloat()
                unifiedAdBinding.adStars.visibility = View.VISIBLE
            }

            if (nativeAd.advertiser == null) {
                unifiedAdBinding.adAdvertiser.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adAdvertiser.text = nativeAd.advertiser
                unifiedAdBinding.adAdvertiser.visibility = View.VISIBLE
            }

           nativeAdView.setNativeAd(nativeAd)


        }
    }

    inner class AdViewHolderGrid(private val binding: NativeAdRowTitleGridBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bindAdData() {
            val builder =
                AdLoader.Builder(binding.root.context, "ca-app-pub-3940256099942544/2247696110")
            builder.forNativeAd { nativeAd: NativeAd ->


                if (isDestroyed) {
                    nativeAd.destroy()
                }

                val styles =
                    NativeTemplateStyle.Builder().withMainBackgroundColor(
                        ColorDrawable(
                            ContextCompat.getColor(
                                binding.root.context,
                                R.color.backgroundColor
                            )
                        )
                    ).build()

                val template: TemplateView = binding.myTemplate

                Log.d(TAG, "bindAdData: ${nativeAd.body}")

                template.setStyles(styles)
                template.setNativeAd(nativeAd)


            }
            val adLoader = builder
                .withAdListener(
                    object : AdListener() {
                        override fun onAdFailedToLoad(loadAdError: LoadAdError) {

                            if (adsCnt > 0) {
                                bindAdData()
                            } else {
                                adsCnt -= 1
                            }

                            val error =
                                """
           domain: ${loadAdError.domain}, code: ${loadAdError.code}, message: ${loadAdError.message}
          """"
                            Toast.makeText(
                                binding.root.context,
                                "Failed to load native ad with error $error",
                                Toast.LENGTH_SHORT
                            )
                                .show()
                        }
                    }
                )
                .build()

            adLoader.loadAds(AdRequest.Builder().build(), 5)
        }
    }

}


from How to insert different item e.g. native ads on each 10 items of recyclerView using diffutil "AsyncListDiffer"

No comments:

Post a Comment