C12 Business Forums · Native App Concept · Android
Parity is not a second backlog
Six Android capabilities, each one measured against the shipping Kotlin rather than guessed at. On the left, what the code does today. On the right, the concept, drawn with real C12 content on a real Pixel frame.
21 September 2026 · read from mobile/android/C12 · dev.hotwire 1.2.4 · 817 lines of Kotlin · targetSdk 35
The Android app is good, and it is drifting. It runs current Hotwire Native, it is edge-to-edge
with predictive back enabled, it has seven bridge components and a themed PDF viewer. It is not a port of the
iOS app. But it ships a different second tab, a different brand colour, a Material 3 theme it overrides
back to light, and a target SDK two platform releases behind the phone it runs on. Everything below assumes
those are fixed first — the concepts are the reason to bother.
◆
Fix these before anything else
Five differences between the two shipping apps
Read directly from both codebases. None is hard. All five are the kind of thing a member or a Chair notices when they sit next to someone on the other platform.
| What | iOS | Android | Why it matters |
| Second tab |
Forum → /cadre |
Resources → /resource_collections |
Two members in the same forum are looking at different apps. Support and training have to fork. |
| Brand colour |
#003847 SceneDelegate.swift |
#2E3E46 + #335C66 + #DEE5E7 colors.xml |
One product, four colours, none of them a C12 brand colour from joinc12.com. |
| Dark mode |
Never opts in |
Declares DayNight, overrides to white |
Two different half-answers. One decision, applied twice, fixes both. |
| Platform target |
iOS 16.4 · uses the iOS 18 tab API |
targetSdk 35 · Material 1.12 |
Android 16 shipped and 17 is current. Expressive, Live Updates and the new widget surfaces all need the target moved. |
| Deep links |
webcredentials only, no applinks |
No autoVerify intent filter |
Notification taps route correctly on both. Emailed links open a browser on both. |
1
Forum day, live on the Lock Screen
Live Updates · ProgressStyle · Android 17
Android 17 promoted progress notifications into Live Updates — persistent, ranked above everything else, and present on the always-on display and the status bar. Forum day is exactly that shape, and C12 already knows the running order.
What the shipping code does today
C12FirebaseMessagingService.kt · today
// push arrives, a notification is posted,
// the member taps, MainActivity routes the urlStack
val urlStack = intent.extras?.get("urlStack")
navigator?.route(BASE_URL + path)
// there is no ongoing notification,
// no ProgressStyle, no Live Update
grep androidx.core.app.NotificationCompat.ProgressStyle → 0 matches
Push is built and it routes correctly. What does not exist is a notification that stays and updates — which is the whole of a Live Update.
What is missing
- No ongoing notification of any kind. Every C12 notification today is a one-shot alert that the member dismisses.
- Nothing reaches the always-on display or the status-bar chip, which is where Android 17 put the real estate.
The Android translation
- The Chair drives it. Advancing the agenda in Rails posts the update — no new member behaviour.
- One number owns the card: minutes left in the session.
- Four states across the day:
Starts in 1:12 → 18 min left → Break, back at 10:45 → Meeting complete. - Identical to the iOS Live Activity in content and wording, so the two films cut together.
2
The 30-second glance
Glance · Jetpack Compose · AppWidgetProvider
Eleven months of the year a C12 member needs two facts and no screen. Glance builds widgets from Compose, so this is a small module rather than a RemoteViews rewrite.
What the shipping code does today
app/build.gradle.kts · today
implementation("dev.hotwire:core:1.2.4")
implementation("dev.hotwire:navigation-fragments:1.2.4")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.compose.material3:material3")
no androidx.glance dependency
no AppWidgetProvider in the manifest
no widget_info.xml in res/xml
Compose is already on the classpath, which means Glance is one dependency and one receiver away.
C12 — the concept
Ticked from the home screen
What is missing
- C12 has no home-screen presence at all. It exists only when a member opens it.
- The data already exists — next meeting date and todo completion are on the dashboard the app already renders.
The Android translation
- Small — Next meeting. Days, date, forum, travel time.
- Small — Owed by you. Glance supports actions, so a commitment closes without opening the app.
- Wide — Running order, on meeting day only, mirroring the Live Update.
- Material You tinting means the widget adopts the member’s wallpaper palette while keeping C12 teal as the accent.
3
The same screen, in Material 3 Expressive
Material 3 Expressive · tonal surfaces · SDK 36+
The Android app is a competent Material 2 layout wearing a Material 3 theme. Android 17’s expressive language — tonal surface levels, large shapes, the pill navigation indicator — is what phones now look like.
What the shipping code does today
themes.xml & colors.xml · today
<style name="Theme.C12" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/color_brand</item>
<!-- then overridden back to light -->
<item name="android:windowBackground">@color/white</item>
<item name="colorSurface">@color/white</item>
<item name="colorOnSurface">@android:color/black</item>
</style>
<color name="color_brand">#2E3E46</color> // iOS uses #003847
<color name="c12_buttons">#335C66</color>
<color name="tab_highlight">#DEE5E7</color>
compileSdk = 35 // Android 15. Android 17 is current.
What is missing
- DayNight is declared and then cancelled. Three hard-coded light values override the theme, so dark mode does nothing.
- Four colours for one brand, none of which match iOS.
- Material 1.12 on SDK 35 — two platform releases behind what the phone is running.
The Android translation
- One palette, generated once from C12 teal and shared with iOS as tokens.
- Tonal surface levels instead of white-on-white cards, so hierarchy survives without borders.
- Pill navigation indicator — Material’s own answer to “where am I”, and it needs no custom drawable.
- Dark mode falls out of it once the hard-coded overrides go.
4
Catch me up, on device
ML Kit GenAI · Gemini Nano · AICore
C12 discussion threads carry confidential revenue numbers, layoffs, marriages and prayer requests. Gemini Nano runs through AICore on device, so this is the only form of the feature C12 could responsibly ship.
What the shipping code does today
grep across mobile/android · today
com.google.mlkit.genai 0 matches
com.google.ai.edge.aicore 0 matches
Gemini / summariz 0 matches
// the thread itself is rendered by the Rails app
// inside a Hotwire web view — the text is right
// there, on the device, already
A bridge component can hand the thread text to Gemini Nano and post the summary back into the page — which is exactly what the existing bridge is for.
What is missing
- No on-device model of any kind, and no cloud one either — which is the right starting point, but it means the feature does not exist.
- Long threads are a wall of text in a web view with no way to catch up.
The Android translation
- The privacy line is the hero: “Summarised on this phone by Gemini Nano. Nothing was sent anywhere.”
- The last bullet is always about you — “You were asked directly” turns a summary into accountability.
- Draft, never send. The model writes into the composer; the member edits.
- Ships as a bridge component, so the Rails view decides when to offer it.
5
Finishable notifications, on separate channels
NotificationChannel · actions · App Links
C12 already sends push and already routes it correctly. What it does not do is let a member finish the job from the shade, separate the kinds of message, or open from a link that did not come from a notification.
What the shipping code does today
AndroidManifest.xml · today
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/> ← why?
<activity android:name=".main.MainActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- no VIEW filter, no android:autoVerify="true" -->
</activity>
// one implicit channel, no actions on any notification
What is missing
- No verified App Links. An emailed or texted C12 link opens Chrome and asks for a sign-in.
- One channel. A member who mutes community chatter also mutes meeting reminders.
- No actions on any notification — every one requires opening the app.
- AD_ID permission forces a Play data-safety declaration that is hard to justify here.
The Android translation
- Three channels: Replies, Meeting reminders, Curriculum. Android then handles the preference UI for free.
- “I’m praying” as a notification action — a tiny, C12-specific acknowledgement no other app has.
- The meeting reminder carries the two blockers: where to go, and what you still owe.
- Verified App Links on
c12app.com, which is the prerequisite for all of it.
6
Built for the member you actually have
BiometricPrompt · font scale · display size
C12 membership is chief executives responsible for at least five employees, carrying their own profit and loss in this app. Two things follow: getting in should be instant for them and impossible for anyone else, and the text should be whatever size they set it to.
What the shipping code does today
grep across mobile/android · today
androidx.biometric 0 matches
BiometricPrompt 0 matches
CredentialManager 0 matches
fontScale / configuration 0 matches
// MainActivity.kt — the one accessibility-adjacent line
WindowCompat.getInsetsController(window, window.decorView).apply {
// comment says "white status bar with dark content"
isAppearanceLightStatusBars = false // ...which is the opposite
}
What is missing
- No biometric gate. Anyone holding an unlocked phone can read a member’s cash position.
- No passkey support — the password still gets typed into a web view.
- Nothing responds to font scale or display size beyond the web view’s own reflow.
The Android translation
- Lock the numbers, not the app. Discussions and the schedule stay open; the financial dashboard blurs after a few idle minutes.
- Test at Largest first. If the layout holds for the member who turned it up, it holds for everyone.
- Credential Manager + passkeys removes the weakest link in a product holding member financials.
- Copy that respects the reader: “Your numbers stay covered when you put the phone down.”
→
Where these land
Sequenced by effort against demo value
Concepts 3 and 6 also close findings from the assessment, so they pay for themselves twice. The rest map one-to-one onto the iOS roadmap, which is the point.
| Concept | Phase | Prerequisite | iOS twin |
| 3 · Material 3 Expressive | Converge | targetSdk 36+, Material 1.13+ | Liquid Glass |
| 6 · Font scale & biometrics | Converge | androidx.biometric | Dynamic Type & Face ID |
| 2 · Glance widgets | Converge | androidx.glance | WidgetKit |
| 5 · Channels & App Links | Frontier | assetlinks.json on c12app.com | Notification actions |
| 1 · Live Updates | Frontier | Chair-side agenda control in Rails | Live Activity |
| 4 · Gemini Nano | Companion | AICore-capable device tier | Foundation Models |
+
Also in the prototype
Concepts 7–11, drawn but not written up here
The Android prototype carries the full sixteen scenes. These five are shown there and map to volume 2 of the iOS boards.
7
Capture the CORE figures
The annual CORE presentation wants five named Economic Value Add figures in section ten. ML Kit reads them off the statements; WorkManager attaches the documents to company_financials.
ML Kit · WorkManager
8
Curriculum on the drive
Read-it-to-me already exists in the curriculum. A Media3 session plus a MediaBrowserService is what carries that same audio onto Android Auto, keeps it playing with the screen off, and remembers the resume point.
Media3 · Android Auto
9
Forum day in Google Wallet
A generic Wallet pass surfaces itself on the morning of and again at the venue, costing no notification budget.
Google Wallet API
10
Say the commitment
On-device speech recognition turns two minutes in the car into three commitments, each attributed to the peer who raised it.
SpeechRecognizer · Gemini Nano
11
Commitments in Google Tasks
Mirrored into the list the member already keeps open, with geofenced triggers that fire on arrival at the venue or leaving the office.
Tasks API · Geofencing
▶
See all sixteen
The interactive prototype runs every concept on a Pixel frame, with an autoplay walkthrough and 26 exported screenshots.