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