Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AdMob Ads in an endless RecyclerView with Paging library

I am using paging library to fetch data from server, I was thinking of showing Ad after every 10 items. So as the user scrolls down and new items are fetched and added to PagedList. I want the new Ad to be loaded and added in RecyclerView as well just like Instagram shows ads in feeds. So if the user scrolls to 200 items, 20 ads will be shown gradually!!. I have read a couple of tutorials but I haven't figured an easy way to do it.

Here is my adapter.

class RequestsPagedAdapter(
        private val retryCallback: () -> Unit, private val from: From)
    : PagedListAdapter<RequestsQuery.Request, RecyclerView.ViewHolder>(POST_COMPARATOR) {
    private var networkState: NetworkState? = null

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            R.layout.z_request_item -> (holder as RequestViewHolder).bind(getItem(position)!!, from)
            R.layout.network_state_item -> (holder as NetworkStateItemViewHolder).bindTo(networkState)
            R.layout.ad_admob_banner -> (holder as AdMobViewHolder)
        }
    }

    override fun onBindViewHolder(
            holder: RecyclerView.ViewHolder,
            position: Int,
            payloads: MutableList<Any>) {
        if (payloads.isNotEmpty()) {
            val item = getItem(position)
            (holder as RequestViewHolder).updateProduct(item!!)
        } else {
            onBindViewHolder(holder, position)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            R.layout.z_request_item -> RequestViewHolder.create(parent)
            R.layout.network_state_item -> NetworkStateItemViewHolder.create(parent, retryCallback)
            R.layout.ad_admob_banner -> AdMobViewHolder.create(parent)
            else -> throw IllegalArgumentException("unknown view type $viewType")
        }
    }

    private fun hasExtraRow() = networkState != null && networkState != NetworkState.LOADED

    override fun getItemViewType(position: Int): Int {
        return when {
            hasExtraRow() && position == itemCount - 1 -> R.layout.network_state_item
            position % ITEMS_PER_AD == 0 + 2 -> R.layout.ad_admob_banner
            else -> R.layout.z_request_item
        }
    }

    override fun getItemCount(): Int {
        return super.getItemCount() + if (hasExtraRow()) 1 else 0
    }

    fun setNetworkState(newNetworkState: NetworkState?) {
        val previousState = this.networkState
        val hadExtraRow = hasExtraRow()
        this.networkState = newNetworkState
        val hasExtraRow = hasExtraRow()
        if (hadExtraRow != hasExtraRow) {
            if (hadExtraRow) {
                notifyItemRemoved(super.getItemCount())
            } else {
                notifyItemInserted(super.getItemCount())
            }
        } else if (hasExtraRow && previousState != newNetworkState) {
            notifyItemChanged(itemCount - 1)
        }
    }

    companion object {
        val POST_COMPARATOR = object : DiffUtil.ItemCallback<RequestsQuery.Request>() {
            override fun areContentsTheSame(oldItem: RequestsQuery.Request, newItem: RequestsQuery.Request): Boolean =
                    oldItem == newItem

            override fun areItemsTheSame(oldItem: RequestsQuery.Request, newItem: RequestsQuery.Request): Boolean =
                    oldItem.id() == newItem.id()
        }
    }
}

This is how data is submitted to PagedList from Fragment.

private fun getRequests() {
    subcategoryRequestsListViewModel.requests.observe(viewLifecycleOwner, Observer {
        adapter.submitList(it)
    })
    subcategoryRequestsListViewModel.networkState.observe(viewLifecycleOwner, Observer {
        adapter.setNetworkState(it)
    })
}

And ViewHolder

class AdMobViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    companion object {
        fun create(parent: ViewGroup): AdMobViewHolder {
            val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.ad_admob_banner, parent, false)
            return AdMobViewHolder(view)
        }
    }
}
like image 641
Nux Avatar asked Jun 05 '19 12:06

Nux


3 Answers

There can not be an easy example of it than google example here The only difference is that it takes it data from XML file saved in raw and you take your data from elsewhere. I hope you can easily modify this example according to your needs.

like image 111
Vanshaj Daga Avatar answered Oct 08 '22 10:10

Vanshaj Daga


You can have a list of ads in the adapter like this:

    private val nativeAds = mutableListOf<UnifiedNativeAd>()

Then your onBindViewHolder will look like this:

   override fun onBindViewHolder(holder: BaseViewHolder<Any>, position: Int) {
    when (getItemViewType(position)) {
        NATIVE_AD -> {
            val adPosition = position * nativeAds.size / itemCount
            val nativeAd = nativeAds[adPosition]
            holder.performBind(nativeAd)
        }
        else ->  holder.performBind(getItem(position))
    }

}

Your getItemViewType like this:

 override fun getItemViewType(position: Int): Int {
    val nativeAdRows = getNativeAdRows()
   return if(hasNativeAds() && nativeAdRows.contains(position)){
        NATIVE_AD
    }else {
       //get other model item
   }
}

The remaining parts of the code:

fun onNativeAdLoaded() {
    val rows = getNativeAdRows()
    Timber.d("Native Ad Rows: $rows")
    Timber.d("Total items: $itemCount")
    rows.forEach {
        notifyItemInserted(it)
    }
}

private fun hasNativeAds(): Boolean {
    return nativeAds.isNotEmpty()
}

private fun getNativeAdRows(): List<Int>{
    val rows = mutableListOf<Int>()
    for (i in 0 .. itemCount){
        if(i != 0 && i % Config.NATIVE_AD_AFTER_POSTS == 0){
            rows.add(i)
        }
    }
    return rows
}

Check this gist for sample code

like image 36
cyberman Avatar answered Oct 08 '22 10:10

cyberman


the first step must be declared a field in the adapter like blew

private val ADS_ITEM = 0
private val NORMAL_ITEM = 1

so, the next step you must create two type instance viewHolder

inner class MyViewHolder(itemView: View) : ViewHolder(itemView) {
    var title: TextView = itemView.item_text

}

inner class AdsMyViewHolder(itemView: View) : ViewHolder(itemView) {
    var title: TextView = itemView.item_text
}

and create a new instance suitable viewHolder

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    return if (viewType == NORMAL_ITEM) {
        val v = inflater.inflate(R.layout.item, parent, false)
        vh = MyViewHolder(v)
        vh as MyViewHolder
    } else {
        val v = inflater.inflate(R.layout.item_ads, parent, false)
        vh = AdsMyViewHolder(v)
        vh as AdsMyViewHolder
    }
}

don't forget override getItemViewType

override fun getItemViewType(position: Int): Int {
    return when (getItems()[position]) {
        is AdsSampleModel -> ADS_ITEM
        else -> NORMAL_ITEM
    }
}

last step bind item with suitable data

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    when (holder) {
        is MyViewHolder -> {
            vh = holder
            val model = getItems()[position] as SampleModel
            (vh as MyViewHolder).title.text = model.getId().toString()
        }
        else -> {
            vh = holder
            val model = getItems()[position] as AdsSampleModel
            (vh as AdsMyViewHolder).title.text = model.getText()
        }
    }
}

add new item after paging

fun view () {
    endless = object : EndlessRecyclerViewScrollListener(layoutManager) {
        fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView) {
            getNext()
        }
    }
    recyclerView.addOnScrollListener(endless)
}


fun getNext() {
    if (hasNextPage) {
        page++
        val callBack = object : YourCallback {
            override fun onSuccess(response: ResponseValue?) {
                if (response!!.getList().size() == 0)
                    hasNextPage = false
                else
                    addData(response!!.getList())
            }
            override fun onError() {
                // error handling
            }
        }
    }
}


private fun addData(models: List<YourModel>?) {
    for (i in models?.indices) {
        if (i % 10 == 0) {
            list.add(ADS_TYPE)
        }
    }

    // notify adapter must be doing by main thread
    runOnUiThread { adapter.notifyDataSetChanged() }
}
like image 1
Alireza Tizfahm Fard Avatar answered Oct 08 '22 08:10

Alireza Tizfahm Fard