Salah Nahed🆕 Design-Driven Firebase in Flutter: Building Custom In-App Messaging with SwiftUI &...
In-app messaging plays a critical role in how users interact with modern apps — from promoting offers to driving engagement and retention.
Published on: April 11, 2025 · ⏱️ 10 min read
Before diving into the custom renderer, we first needed to enable Jetpack Compose in our Flutter project’s native Android layer. This meant updating the build.gradle file with the proper buildFeatures, setting the kotlinCompilerExtensionVersion, and pulling in all the right dependencies.
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.8"
}
Then we added the essential Compose libraries:
// Compose BOM
implementation platform("androidx.compose:compose-bom:2025.02.00")
// Core UI
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-tooling-preview"
implementation "androidx.compose.material3:material3"
// Runtime
implementation "androidx.activity:activity-compose:1.10.1"
// For debugging previews
debugImplementation "androidx.compose.ui:ui-tooling"
1.4.8?
Because if you go any higher while using Kotlin 1.8.22 (which Flutter ships with), Compose will politely scream at you. We initially tried newer versions, only to be met with this charming message:
This version of the Compose Compiler requires Kotlin version 1.9.23 but you are using 1.8.22 which is not known to be compatible…
📌 Check the Compose-Kotlin compatibility table for more details.
Then it hits you with the legendary line:
"…or suppressKotlinVersionCompatibilityCheck but don’t say I didn’t warn you!"
Yeah. Compose isn’t messing around 😅.
So we locked it down to 1.4.8, which plays nicely with the Kotlin version Flutter currently ships.
Firebase tells us: implement FirebaseInAppMessagingDisplay and register it. That’s all. No examples, no docs for Compose. Just vibes.
FirebaseInAppMessaging.getInstance().setMessageDisplayComponent(NologyInAppMessageDisplay(context))
Let's walk through the custom implementation we built — clean, robust, and fully integrated with your Compose UI.
NologyInAppMessageDisplay
class NologyInAppMessageDisplay(private val context: Context) : FirebaseInAppMessagingDisplay {
override fun displayMessage(
inAppMessage: InAppMessage,
callbacks: FirebaseInAppMessagingDisplayCallbacks
) {
// Implementation logic
}
}
✅ What This Class Does:
ModalMessage.appData for optional buttons.DialogFragment.messageClicked() / messageDismissed().imageRawData directly, no network call required.imageUrl.Intent.ACTION_VIEW with the URL.Firebase caches campaigns for up to 24 hours — but test mode lets you bypass that using a device’s FID (Firebase Installation ID).
On Android:
Look for this in Logcat:
I/FIAM.Headless: Starting InAppMessaging runtime with Installation ID <YOUR_ID>
On iOS:
Go to Xcode → Product > Scheme > Edit Scheme…
Add argument: -FIRDebugEnabled
Then find in logs:
[Firebase/InAppMessaging] Starting InAppMessaging runtime with Firebase Installation ID <YOUR_ID>
{
"secondaryTitle": "SKIP",
"secondaryTextColor": "#000000",
"secondaryBackgroundColor": "#C9C9C9"
}
Setting up proper deep link support is a big deal — and deserves its own article.
I’ll cover everything in a future piece:
If that sounds helpful — drop a comment and I’ll prioritize it!
Building custom layouts for Firebase In-App Messaging isn’t just about styling — it’s about creating immersive, native, analytics-driven experiences that match your app’s personality and performance goals.
From deep link support to analytics tracking, we stayed close to Firebase’s native protocols while still delivering a UI that feels ours.
I’m Salah Nahed, a Flutter & mobile craftsman passionate about building beautiful cross-platform experiences.
Let’s connect:
Thanks for reading — and see you in the next deep dive ✌️