today I was trying to implement custom ads inside a horizontally oriented recycler view.
Everything went fine, till I ran the app and noticed that some of the items inside my MutableList are not displayed (or are being displayed as blank spaces, don’t know for sure) and right after every ad (only does that after ads) there’s a huge blank space.
I don’t know what to do to solve this, I’m not familiar with multiple layouts inside an adapter.
Adapter declaration:
class CardAdapter (val context2: Context, private val Cards:MutableList<Card>) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
This is my ad holder inside the adapter:
inner class HolderNativeAd(itemView: View): RecyclerView.ViewHolder(itemView){
val app_ad_background : ImageView = itemView.findViewById(R.id.ad_icon)
val ad_headline : TextView = itemView.findViewById(R.id.ad_headline)
val ad_description : TextView = itemView.findViewById(R.id.ad_description)
val ad_price : TextView = itemView.findViewById(R.id.ad_price)
val ad_store : TextView = itemView.findViewById(R.id.ad_store)
val call_to_action : CardView = itemView.findViewById(R.id.ad_call_to_action)
val ad_advertiser : TextView = itemView.findViewById(R.id.ad_advertiser)
val nativeAdView : NativeAdView = itemView.findViewById(R.id.nativeAdView)
fun createAD(context : Context){
val adLoader = AdLoader.Builder(context, context.getString(R.string.native_ad_id_test))
.forNativeAd { nativeAd ->
Log.d(TAG, "onNativeAdLoaded: ")
displayNativeAd(this@HolderNativeAd, nativeAd)
}.withNativeAdOptions(NativeAdOptions.Builder().build()).build()
adLoader.loadAd(AdRequest.Builder().build())
}
}
onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View
if(viewType == VIEW_TYPE_CONTENT){
view = LayoutInflater.from(context2).inflate(R.layout.item_card, parent, false)
return HolderCards(view)
}else{
view = LayoutInflater.from(context2).inflate(R.layout.native_ad_card, parent, false)
return HolderNativeAd(view)
}
}
onBindViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (getItemViewType(position) == VIEW_TYPE_CONTENT) {
val model: Card = Cards[position]
(holder as HolderCards).setCard(model, context2)
} else if (getItemViewType(position) == VIEW_TYPE_AD) {
(holder as HolderNativeAd).createAD(context2)
}
}
getItemViewType
override fun getItemViewType(position: Int): Int {
//logic to display Native Ad between content
if(position != 0) {
return if (position % 2 == 0) {
//after 2 items, show native ad
VIEW_TYPE_AD
} else {
VIEW_TYPE_CONTENT
}
}
return VIEW_TYPE_CONTENT
}
And getitemCount() returns Cards.size
Cards mutable population:
currenctly I have a SingleValueEventListener which grabs the cards and puts them inside a mutableList calling adapter.NotifyItemInserted() for each item.
displayNativeAd (custom method used in the ad holder)
private fun displayNativeAd(holderNativeAd: CardAdapter.HolderNativeAd, nativeAd: NativeAd) {
/* Get Ad assets from the NativeAd Object */
val headline = nativeAd.headline
val body = nativeAd.body
val background = nativeAd.icon
val callToAction = nativeAd.callToAction
val price = nativeAd.price
val store = nativeAd.store
val advertiser = nativeAd.advertiser
...
... (checks to see if a val is null or not)
holderNativeAd.nativeAdView.setNativeAd(nativeAd)
}
2
Answers
All right buckle up because this is a long one! It’s actually the "adding ads" part that’s complicating things here, not the extra
ViewHolder
type.You’re missing items because you’re replacing some of them with ads. The total number of items (
itemCount
) in yourAdapter
should be the number of cards plus the number of ads you want to display.Because you’re not handling that, you’re effectively skipping over items in
cards
with this code:You have
cards.size
number of items, and instead of showingcards[2]
you show an ad instead, andcards[2]
never gets shown. (Also that code shows an ad every two items btw,position % 2
either produces a 0 or 1, so it loops every second number – you wantposition % 3
so it’s every multiple of three. But there’s more to it than that, we’ll get to it!)So you need logic to handle the fact that your data (
cards
) and your contents (cards
+ ads) are different:itemCount
needs to include the appropriate number of adsgetItemViewType
needs to know ifposition
holds an ad or a cardonBindViewHolder
needs to be able to translateposition
to the appropriate index incards
when displaying a cardLet’s lay down the rules first – let’s say that you want an ad displayed as every third item, that starts after the first two items, and you’re happy to end with an ad, to make things simple.
So the number of ads is just how many groups of 2 there are – integer division will do that:
val adCount = cards.size / 2
The total number of items is that plus the number of cards:
override fun getItemCount() = cards.size + (cards.size / 2)
Working out whether
position
is a card or an ad is simple enough, it’s basically what you already did! Except we need to handle every third item as an ad. We also need to account for the zero-based indexing:We get ads on 2, 5 and 8. We care about finding multiples of 3 (where the modulo operation returns zero) so we can add 1 to each position. This also eliminates the need to check if
position == 0
(that special edge case was a sign your logic wasn’t consistent – don’t worry I only realised that while writing this!)fun isCard(position: Int) = (position + 1) % 3 != 0
Note that we’re using 3 here because we’re dealing with the position in the list which has been padded out with an ad every 2 places. Every 2 items in
cards
has become 2+1 items in the adapter’s content.Really we should be using a constant,
val ITEMS_PER_AD = 2
and deriving another value from that,val AD_FREQUENCY = ITEMS_PER_AD + 1
. Avoids magic numbers that are hard to read and work with, and easy to mess up. This is clearer (maybe with better names!) and you can just changeITEMS_PER_AD
to change how many there are, and everything else will adjust along with itTranslating from a
position
to acard
is the last bit. You have to account for when aposition
isn’t a valid card, i.e.isCard
is false. It’s easiest to return null here in that case.It might help to look at how the translations should work out:
Yep it’s one of them logic puzzles – what’s the pattern in this progression?
The offset is happening every multiple of 3 items, so what if we divide
position
by 3 and subtract it, removing those offsets?Nice, that looks good! So now, we need to either return null if it’s not a card, otherwise fetch the appropriate card from the data set:
Those are the pieces required to size your list properly, work out if a particular
position
is a card or an ad, and fetch the appropriate card from your data. Hopefully you can see how to work that into theAdapter
methods to work out whichitemViewType
you need, etc.You could actually just try to
getCardForPosition
inonBindViewHolder
and if the result is null, display an ad (and cast theViewHolder
you’ve been passed to the ad one, since that’s what you should be getting as they’re all using the same functions to determine what’s what). Lots of options, the logic around the list is the hard part!As for the spaces, see if it works when you have everything displaying correctly. It might resolve itself, or it might be a layout issue with your ad items. Make sure their width isn’t
match_parent
or anything. You can always use theLayout Inspector
with a running app to see exactly what’s happening in the layout on the screen, might give you some cluesI wanted to check I hadn’t missed anything so I wrote a basic implementation if it helps:
Really simple, just uses the same layout for both
ViewHolder
s with aTextView
in it. Fixed size for the layout, no spaces popping up:Hope it helps!
Yes this works fine, I have similar thing which can help too.
But the main problem here is, what if the Admob failed to load the ads ?
If there’s a condition when ads are not loading from the server at that time:
adsItem Size = 0
itemsList Size = 20 (Assume)
AD_DISPLAY_FREQUENCY = 3
So, after every 2 post an Ad will be displayed, and in getItemViewType method we have the modulas function (position%AD_FREQ..)
So. by default it will return the AD_TYPE and the AD Will not be loaded, resulting in empty ItemAdHolder layout inflation. Moreover we will skip the Post Item, as the size of adslist is 0 and we are updating the index for post items, so how to resolve this thing ? I tried checking the adItems size before getting viewType but it’s not helping
What I have tried till now is
In bindViewHolder() for ITEM_TYPE case