Skip to content

Displaying Ads

The Growl SDK provides a complete adapter system for displaying ads inline with your chat messages. This guide covers the recommended approach using GrowlChatListAdapter.

The SDK provides two adapter options:

AdapterDescriptionUse Case
GrowlChatListAdapterUses DiffUtil for efficient updates with animationsRecommended for most apps
GrowlChatRecyclerAdapterBasic adapter requiring manual notificationsLegacy support or custom needs

Both adapters handle:

  • Rendering your custom message views
  • Rendering ad views automatically
  • Loading ads based on chat context
  • Caching loaded ads to prevent re-fetching on scroll

Your message data class must extend ChatItem.MessageItem:

import com.withgrowl.growlandroidsdk.MessageRole
import com.withgrowl.growlandroidsdk.models.ChatItem
import java.util.UUID
data class Message(
override val chatItemId: String = UUID.randomUUID().toString(),
val sender: MessageRole,
val text: String,
val timestamp: Long = System.currentTimeMillis()
) : ChatItem.MessageItem(chatItemId)

Extend the SDK’s abstract MessageViewHolder class:

import com.withgrowl.growlandroidsdk.MessageRole
import com.withgrowl.growlandroidsdk.recycler.MessageViewHolder
class MyMessageViewHolder(
private val binding: ItemChatMessageBinding
) : MessageViewHolder<Message>(binding.root) {
override fun bind(messageItem: Message, position: Int) {
binding.textViewMessage.text = messageItem.text
// Style based on sender
if (messageItem.sender == MessageRole.USER) {
binding.textViewMessage.setBackgroundResource(R.drawable.bubble_user)
// Align to right
} else {
binding.textViewMessage.setBackgroundResource(R.drawable.bubble_assistant)
// Align to left
}
}
}
import com.withgrowl.growlandroidsdk.GrowlAdView
import com.withgrowl.growlandroidsdk.models.ChatMessage
import com.withgrowl.growlandroidsdk.recycler.GrowlChatListAdapter
import androidx.core.graphics.toColorInt
class ChatFragment : Fragment() {
private lateinit var adapter: GrowlChatListAdapter
private val chatItems = mutableListOf<ChatItem>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = GrowlChatListAdapter(
adUnitId = AD_UNIT_ID,
publisherId = PUBLISHER_ID,
chatId = chatId,
createMessageViewHolder = { parent ->
val binding = ItemChatMessageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
MyMessageViewHolder(binding)
},
getChatHistory = { getChatHistoryForAds() },
stylingParams = GrowlAdView.StylingParams(
background = "#1C1E22".toColorInt(),
text = "#EEEEEE".toColorInt(),
border = "#2F2F2F".toColorInt(),
borderWidth = 1
)
)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter
}
private fun getChatHistoryForAds(): List<ChatMessage> {
return chatItems
.filterIsInstance<Message>()
.map { ChatMessage(role = it.sender.roleName, content = it.text) }
}
}
// Add a user message
val userMessage = Message(
sender = MessageRole.USER,
text = userInput
)
chatItems.add(userMessage)
// Add AI response
val aiMessage = Message(
sender = MessageRole.ASSISTANT,
text = aiResponse
)
chatItems.add(aiMessage)
// Submit updated list (GrowlChatListAdapter)
adapter.submitList(chatItems.toList())

Insert ChatItem.AdItem() where you want ads to appear:

private fun fetchAndInsertAdIfAvailable() {
lifecycleScope.launch {
val ad = GrowlAdSDK.fetchAdForChat(
context = requireContext(),
chatHistory = chatHistory,
publisherId = PUBLISHER_ID,
adUnitId = AD_UNIT_ID,
chatId = dashboardViewModel.chatId
)
if (ad != null) {
// Add an ad slot
chatItems.add(ChatItem.AdItem(
hasAttemptedLoad = true,
loadedAd = ad
))
// Submit the updated list
adapter.submitList(chatItems.toList())
// Scroll to bottom
binding.recyclerView.scrollToPosition(chatItems.size - 1)
}
}
}
private fun sendMessage(userInput: String) {
// 1. Add user message
chatItems.add(Message(sender = MessageRole.USER, text = userInput))
adapter.submitList(chatItems.toList())
// 2. Get AI response
lifecycleScope.launch {
val response = getAIResponse(userInput)
// 3. Add AI message
chatItems.add(Message(sender = MessageRole.ASSISTANT, text = response))
// 4. Add ad slot if needed
fetchAndInsertAdIfAvailable()
}
}

Customize ad appearance using StylingParams:

import com.withgrowl.growlandroidsdk.GrowlAdView
import androidx.core.graphics.toColorInt
val styling = GrowlAdView.StylingParams(
background = "#1C1E22".toColorInt(), // Card background
text = "#EEEEEE".toColorInt(), // Title and description
border = "#2F2F2F".toColorInt(), // Card border
borderWidth = 1 // Border width in dp
)
ParameterTypeDescription
backgroundInt?Card background color
textInt?Text color for title and description
borderInt?Card border/stroke color
borderWidthInt?Border width in dp
GrowlAdView.StylingParams(
background = "#121212".toColorInt(),
text = "#E0E0E0".toColorInt(),
border = "#333333".toColorInt(),
borderWidth = 1
)
GrowlAdView.StylingParams(
background = "#FFFFFF".toColorInt(),
text = "#212121".toColorInt(),
border = "#E0E0E0".toColorInt(),
borderWidth = 1
)
  1. When a ChatItem.AdItem is bound in the RecyclerView, the SDK automatically calls loadAds()
  2. The getChatHistory function provides conversation context for ad targeting
  3. Loaded ads are cached in AdItem.loadedAd to prevent re-fetching on scroll
  4. If no ad is available, the ad view is hidden automatically

For non-chat scenarios, you can use GrowlAdView directly:

<com.withgrowl.growlandroidsdk.GrowlAdView
android:id="@+id/growlAdView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
val growlAdView = binding.growlAdView
// Apply styling
growlAdView.applyStyling(GrowlAdView.StylingParams(
background = "#1C1E22".toColorInt(),
text = "#EEEEEE".toColorInt()
))
// Load ads
lifecycleScope.launch {
growlAdView.loadAds(
chatHistory = getChatHistory(),
publisherId = PUBLISHER_ID,
adUnitId = AD_UNIT_ID,
chatId = chatId,
callback = object : AdLoadCallback {
override fun onAdsLoaded(ads: List<Ad>) {
Log.d(TAG, "Loaded ${ads.size} ads")
}
override fun onError(error: String) {
Log.e(TAG, "Error: $error")
}
}
)
}

The SDK provides a sealed class for type-safe chat items:

sealed class ChatItem {
abstract val chatItemId: String
// Extend this for your messages
abstract class MessageItem(chatItemId: String) : ChatItem()
// Use this for ad slots
data class AdItem(
override val chatItemId: String = UUID.randomUUID().toString(),
var hasAttemptedLoad: Boolean = false,
var loadedAd: Ad? = null
) : ChatItem()
}

Your message class extends MessageItem, and you use AdItem directly for ad slots.