Events, Call for Speakers, community and profile for Kenya's Kotlin & Android developers.
Android254 is the official app of the Android254 / Kotlin Kenya developer community. Members use it to find the next meetup and RSVP, submit talks, get into meetups on the day with a QR ticket, connect with other developers, browse jobs from community sponsors, and keep a speaker and member profile.
The app is built with Kotlin Multiplatform + Compose Multiplatform. It uses Material 3 chrome on Android and native-feeling chrome on iOS, and both platforms share the brand content (cards, chips, state messages, type and colour tokens).
🎨 Design source of truth: Android254 on Figma Design system v1.1: 131 components, Android + iOS, Oct 2026.
- Product overview
- Release scope
- Navigation & information architecture
- Features
- Brand
- Design system
- Screen-building rules
- Copy voice & localisation
- Android ↔ iOS mapping
- Adaptive layouts
- Screen state checklist
- Project structure
- Getting started
- Open decisions
| Who it's for | Kotlin & Android developers in Kenya and the region. Chapters: Nairobi, Mombasa, Kisumu, Kampala (Kotlin Uganda, a partner community) and online-only members. |
| Top member tasks | See the next meetup and RSVP, get in on the day, find jobs, find people. |
| Secondary tasks | Submit a talk, rate meetups, speaker feedback, mentorship, sponsored challenges, sharing a profile poster. |
| Platforms | Android (Material 3), iOS (native chrome), plus a companion website for public pages and sponsor/speaker forms. |
| Sign-in | GitHub. Guest mode is also available in one onboarding variant. |
| Languages | English and Kiswahili (sw-KE). |
| Themes | Light, Dark and System. Every screen is designed in both. |
The Figma file tags frames with milestones:
| Tag | Meaning |
|---|---|
| MVP (T2, accepted 2026-10-02) | Phone layouts for the core flows. No navigation drawer in the MVP. Submit a talk, Code of conduct, About and Our sponsors become rows on Profile and Settings. The full drawer design is kept for later. |
| M1 · January scope | Medium (600dp), Expanded (840dp) and Foldable (673×841 book-portrait) layouts for key flows (onboarding, ticket, check-in tabletop mode, submit a talk, event detail, settings). This targets the January 24, 2027 meetup. |
| M2 | List-detail layouts at Expanded widths (Events ↔ Event detail, Jobs ↔ Job detail, People ↔ Member profile, Settings ↔ sub-pages). |
Decision (Oct 2026), Option A: four root tabs.
┌────────┬────────┬────────────┬─────────┐
│ Home │ Events │ Community │ Profile │ ← NavigationBar (Android) / TabView (iOS)
└────────┴────────┴────────────┴─────────┘
│
└─ PrimaryTabRow: Content · Jobs · People
(iOS: segmented Picker)
- Home: the next meetup, shortcuts, the current promo and trending topics.
- Events: calendar and list of meetups, event and session detail.
- Community:
CommunityScreenwith three tabs: ContentTab · JobsTab · PeopleTab. Quick connect is the FAB on People. - Profile: your profile, talks, badges, mentorship and settings.
- Secondary destinations (no duplicates of tabs): Submit a talk, Code of conduct, About, Our sponsors, Settings. In the MVP these are rows on Profile/Settings. A drawer comes later.
- At ≥600dp the NavigationBar becomes a NavigationRail. Secondary items appear there too: Submit, Conduct, About, Sponsors, Settings.
- The NavigationBar shows on root tabs only. It is hidden on detail, form and onboarding screens.
Why not five tabs (Home · Events · Jobs · Community · Profile)? Five 72dp items crowd the labels. "Community" truncates at 200% font size, and longer Kiswahili labels wrap. Labels must stay readable at 360dp, 200% font and +30% Kiswahili length.
Onboarding is a short setup that builds a personalised feed. It runs as an A/B test:
| Variant | Flow |
|---|---|
| B: Sign in first (control) | Welcome + GitHub sign-in → Step 0 Your chapter → Step 1 Pick your stack → Step 2 How do you build? → Step 3 What's next for you? → Complete |
| A: Interests first | Welcome → interest steps → Step 4 Save your feed (sign in or continue as guest) → Complete (signed in / guest) |
- Your chapter: we guess the chapter from approximate location (e.g. Nairobi: 1,240 members). Members can pick Nairobi, Mombasa, Kisumu, Kampala or Online only, and follow other chapters later.
- Experiment: 50/50 split on first launch, sticky per install, new installs only.
- Primary metric: onboarding completion, meaning the user reaches Home with ≥1 topic picked in the first session.
- Secondary metrics: GitHub sign-in rate, topics picked, day-7 retention, first RSVP within 14 days.
- Guardrails: guest share +≤10 pts in A, crash-free sessions, median time-to-Home +≤20%.
- Analytics events:
onboarding_viewed{variant, step},interest_selected{topic},onboarding_skipped{step},sign_in_started/sign_in_succeeded/sign_in_failed{provider},guest_continued,onboarding_completed{variant, topics, signed_in}. - Decision rule: run until each arm has ~1,000 installs (≈4 weeks), then ship the winner. On a tie, keep B.
- States: sign-in failed, none selected, Dark, 412dp, plus tablet and foldable layouts.
The member's daily dashboard.
- Chapter switcher (e.g. "NAIROBI CHAPTER ▾") opens a chapters bottom sheet. There is also a single-chapter variant.
- Greeting: "Good morning, Zawadi".
- Your next meetup: the featured EventCard shows date, venue, RSVP status, attendees ("+42 going"), session count and View event & sessions.
- Shortcut tiles: Jobs (48 open roles), People (184 members), Quick connect (scan a code), Submit a talk (closes 10 Feb).
- Promo slot (one at a time): Call for Speakers, a ticket drop, a survey or a sponsored challenge (e.g. "Ship a Compose foldable layout", presented by Kasha Health).
- Trending topics: chips with counts (Jetpack Compose, Coroutines, KMP, Architecture).
- States: Default, Empty (new member), Loading (skeletons), Error, Dark, 200% font scale, Kiswahili.
- Post-event variants: After the meetup (rate prompt, rating skipped) and First meetup · Celebration.
- Filter chips at the top. Items are grouped into Today and Earlier.
- Example items: talk accepted, meetup in 3 days, speaker mentor assigned, recording available, trending topic.
- Read and unread
NotificationItemstates. - States: Default, All caught up, Loading, Error, Dark, 412dp.
On meetup day, Home changes into event day mode:
- "It's meetup day, Zawadi" with the doors-open time.
- TicketCard: attendee name, entry type, hall/floor. Tap it for the full QR ticket.
- Now and next: the live session (with "Live poll open") and the next session.
- At the venue: Wi-Fi name and password with Copy, directions ("Lifts on the left after security · Bring an ID").
- Today's host: the sponsor row.
- Offline: the ticket and venue info stay available with an inline offline note.
Ticket screen: Default, Dark and Offline, plus phone, tablet and foldable layouts. After a successful scan, a Checked in · Welcome toast appears.
Check-in scanner (organiser):
- Camera viewfinder with a result bottom sheet. Results: Checked in, Already checked in, Not on the list.
- Tabletop mode (foldable, M1): the upright half shows the viewfinder, the checked-in count against capacity ("38 still to arrive") and the last person scanned. The flat half holds the controls: recent scans, a manual 6-character code field, torch toggle, undo last, and end session. Results auto-return after a short progress bar.
- Events tab: a month/week calendar (
CalendarMonth/CalendarDay) plus the event list. States: empty day, loading, error, past events with a Rate the meetup prompt. - Event detail: agenda, speakers, venue, sponsor and RSVP.
- RSVP states: RSVP'd, Cancel RSVP confirm, RSVP confirmed.
- Capacity states: Few seats left (warning under 15% capacity), Full · Waitlist (error at 0), Waitlist promoted.
- After the event: Ended · Rate and Ended · Rated.
- Session detail: Upcoming and Live. Live sessions can carry a sponsor poll ("Poll by …").
- Panel sessions (new): panel formats in the agenda, plus Upcoming and Live panel session detail.
- Post-event loop: rate the meetup (1–5 rating faces), submitted state, first-meetup celebration.
- Sponsor impact report: an A4 report (complete or partial data) and an organiser mobile preview.
A 4-step form for proposing a talk:
- The pitch: title, format (talk or panel), abstract. States: title missing, draft saved, returning speaker.
- Content: outline, level, topics.
- Experience: prior speaking. First-time speakers get a mentor.
- Materials: slides and links.
→ Submitted ("Talk submitted. We'll reply by 10 Feb.")
- Panel discussions (new): add panel speakers and invite members through a search sheet (states: no results, invite failed). Invites can be Pending, Accepted, Declined, Expired, or Leave panel (confirm).
- Speaking history:
SpeakerHistoryCardshows a speaker's track record to organisers. - The same flow exists on the web (see Web companion).
Content tab: talks, recordings and articles with filters. States: empty filter, loading, error.
Jobs tab (Job board):
- Search and filters. Featured roles: at most 2 per list, always labelled "Featured · Community sponsor", always showing a salary range.
- Job detail: save, apply, and an Applications closed state.
- Role insights (poster): view stats for the people who posted a job.
- States: no results, no matching roles, 200% font scale, Expanded list-detail.
People tab (Developer network):
- Search the member directory, with an expandable map. A new-member state.
- Member profile: Not connected / Connected / Remove connection.
Quick connect (the FAB on People):
- Scan another member's QR → Member found → Connected (First connection celebration).
- My code: show your QR, or reset it (with a confirm step).
- Sponsor booths: scan a sponsor code → choose what to share → Shared with sponsor. A Sponsor code expired state.
- Camera permission handling (Android rationale and iOS system alert). Not a member code state.
Sponsors: the Our sponsors list and Sponsor detail (open roles, no open roles, loading, error).
- Profile: your info, talks, badges (with a Badge unlocked motion), topics (edit via sheet) and Talk accepted state. States: new member, loading, error, 200% font scale.
- Edit profile: link validation, discard-changes confirm, saved confirmation.
- Public profile & Open to work: a public web profile (360) and a 1200×630 share card.
- Mentorship: request a mentor (sheet), incoming requests (for mentors), Your mentorships, mentoring settings, and mentorship notifications.
- Speaker feedback: attendee feedback on your talks. A No feedback yet state.
- Sponsored challenge: Open, Submitted, Closed, plus promo and badge examples.
- Settings:
- Theme sheet (Light / Dark / System).
- Notifications (including a system notifications off state).
- Privacy (what you share; a Nothing shared state; stop-sharing confirm).
- Open to work.
- Delete account (confirm).
- Rows for Submit a talk, Code of conduct, About and Our sponsors (MVP replacement for the drawer).
The app generates share-ready speaker posters for each meetup edition.
MeetupPostercomponent. Poster variables switch theme per edition: Kotlin night, Savannah dawn, Lake teal.- Preview & share states: generating, no photo, offline, long name/title, Dark. Available on iOS too.
- Sponsor logo treatment rules come with the poster.
The companion website is responsive: Desktop 1280 and Mobile 360, Light and Dark.
- W1 (public): About us, Sponsor pitch, Sponsor tiers, Terms of service, Privacy policy, Account deletion (required by the Play Store).
- W2 (forms): Call for Speakers (sign in → 4 steps → submitted), Call for Sponsors (form, errors, success), Call for Donations.
- Organizer: a sponsor submissions dashboard.
- Web shell: top bar, footer, mobile menu, skip link, keyboard focus styles, theme toggle, photo gallery, organiser marquee, a Get the app CTA and an iOS coming soon notice.
The Android254 mark is the Kotlin K on a dark rounded tile. Use the AppLogo component in core/ui and never redraw it.
- Mark: the K on a
#2F3037tile. Show it at 24dp or larger, and never stretch it. - Name: "Android254", one word, capital A. Not "Android 254" or "android254".
- Tagline:
ANDROID · KOTLIN · KENYA. - Gradient: accents only. Never put a gradient behind the mark or text.
Full guidelines are in docs/brand.md. The feature graphic is at docs/assets/brand/feature-graphic.jpg.
All UI binds to semantic tokens, never to raw hex values or palette tones. The rule is "bind, never paste".
| Role | Light | Dark |
|---|---|---|
primary |
#6633E5 |
#CDBDFF |
onPrimary |
#FFFFFF |
#370095 |
primaryContainer |
#E8DEFF |
#54348A |
secondary |
#615B71 |
#CBC3DC |
tertiary / success |
#006C47 |
#71DAA6 |
warning |
#904D03 |
#FFB77D |
error |
#BA1B1A |
#FFB4AB |
surface / background |
#FBF8FF |
#12131A |
surfaceContainerLowest |
#FFFFFF |
#0D0E14 |
surfaceContainerLow |
#F4F2FD |
#1A1B22 |
surfaceContainer |
#EEEDF7 |
#1E1F26 |
surfaceContainerHigh |
#E9E7F1 |
#292931 |
onSurface |
#1A1B22 |
#E3E1EB |
onSurfaceVariant |
#494455 |
#CBC3D8 |
- The full set has 47 colour roles. Beyond the Material 3 roles it adds success and warning families.
- Muted text uses
onSurfaceVariant.outline(#7A7487, 4.3:1) is for borders only, never text. - The Kotlin gradient is for accents only (bars, title fills, illustrations). Don't put text on it: white on its pink end fails AA.
| Style | Font | Size/line | Letter spacing |
|---|---|---|---|
| Display L | Space Grotesk Bold | 48/56 | −1 |
| Display M | Space Grotesk Bold | 40/48 | −0.5 |
| Display S | Space Grotesk Bold | 32/40 | −0.5 |
| Headline L | Space Grotesk Bold | 28/36 | −0.5 |
| Headline M | Space Grotesk Bold | 26/34 | −0.5 |
| Headline S | Space Grotesk Bold | 24/32 | −0.5 |
| Title L | Space Grotesk Bold | 20/28 | −0.3 |
| Title M | Inter Semi Bold | 18/26 | 0 |
| Title S | Inter Semi Bold | 16/24 | 0 |
| Body L | Inter Regular | 16/24 | 0 |
| Body M | Inter Regular | 14/22 | 0 |
| Body S | Inter Regular | 12/18 | 0 |
| Label L | Inter Semi Bold | 14/20 | 0 |
| Label M | Inter Semi Bold | 12/16 | 0.2 |
| Label S | Inter Semi Bold | 12/16 | 0.3 |
| Mono/Meta | JetBrains Mono Bold | 12/16 | 0 |
| Mono/Overline | JetBrains Mono Bold | 12/16 | 0.5 |
| Mono/Code | JetBrains Mono Medium | 13/20 | 0 |
Sizes and letter spacing are in sp. The minimum size is 11sp. Each style maps to a MaterialTheme.typography slot and to an iOS Dynamic Type style. JetBrains Mono is for metadata only (e.g. JAN 24 · 09:30 AM EAT).
- Spacing: 4dp base grid. Screens use layout aliases:
screen-margin 16,card-padding 16,stack-gap 12,inline-gap 8,section-gap 32. The values 6, 10 and 14 are retired. - Shape:
xs 4·sm 8·md 12(inputs, images) ·lg 16·xl 24(cards, sheets) ·full(pill). Pill shape is only for buttons, chips and badges. - Elevation (Light theme only):
- Level 1: cards at rest.
- Level 2: FAB, menus, sticky headers.
- Level 3: bottom sheets and bars.
- Primary Glow: only on the single primary CTA per screen.
- In Dark theme, there are no shadows. Raise surfaces with
surfaceContainertones instead.
- Icons: Material Symbols (Android) and SF Symbols (iOS) at 16/20/24. No emoji in UI.
- Other variable collections in Figma: Size, Motion, Breakpoint and Poster (per-edition themes).
| Group | Components |
|---|---|
| Actions | Button (Filled, Tonal, Outlined, Text, Inverse), IconButton, Chip (Assist, Filter, Topic), SelectionControl, SegmentedButton, ShortcutTile |
| Navigation | NavigationBar(+Item), TopAppBar (Small, Home, Large), BottomActionBar, NavigationDrawerItem, TabRow, NavigationRail(+SecondaryItem) |
| Lists & inputs | SectionHeader, ListItem (1–3 lines, icon/avatar), NotificationItem, TextField (single/multiline), SearchField, CalendarDay/Month, OptionCard, Switch, SettingsRow, RatingRow |
| Cards | EventCard (Featured/Compact), PromoCard, TicketCard, JobCard, SponsorRow, StatTile, PanelSessionCard, SpeakerHistoryCard |
| Feedback & overlays | StateMessage, Skeleton, InlineNote, Toast, BottomSheet / SheetContent, dialogs |
| Identity | AppLogo, Avatar, StatusBadge |
| iOS chrome | StatusBar, NavigationBar (Large/Inline), TabBar, GlassButton, ListRow, Toggle |
All components have Enabled, Pressed, Focused and Disabled states where they apply.
Before building any UI, read core/ui/README.md and reuse or extend an existing component. Add a new component to core/ui only when none fits, and update that README in the same commit as any component change.
- Screen skeleton: TopAppBar → scrolling content on a 16dp margin → NavigationBar (root tabs only). Design at 360×800, verify at 412×915.
- TopAppBar hierarchy: Home variant on root tabs (logo + actions). Small with back on detail screens. Large only for top-level lists that collapse on scroll.
- Sections open with a
SectionHeader: an optional overline, a title, and at most one "See all". - Cards hold one thing.
- Card recipe:
surfaceContainerLowest(Light) /surfaceContainerLow(Dark) fill, 1dpoutlineVariantstroke, 24dp radius, 16dp padding. - Content order: Mono overline → title → meta → tags → one action.
- The whole card is the tap target. Secondary actions are 48dp IconButtons.
- One Featured card per screen. One PromoCard per screen at most.
- No cards inside cards. Lists of more than 3 rows use
ListItemrows. - From 600dp, cards go in a 2-column grid. Never stretch a card past 560dp.
- Card recipe:
- Status colour means status. StatTiles are neutral by default. Seats left turns warning under 15% capacity and error at 0.
- Sponsors back events, never single sessions. Use one sponsor surface per screen, always labelled ("Hosted by", "Supported by", "Presented by").
The voice is a friendly organiser at the meetup door: warm, direct and local.
- Use sentence case everywhere. Caps appear only in Mono overlines (
MEETUP #80,CALL FOR SPEAKERS). - Button labels say what happens: "Submit a talk", "Turn on reminders", "Try again". Never "OK", "Click here" or a bare "Submit".
- Errors say what failed, then what to do next. ✅ "Couldn't load events. Check your connection and try again." ❌ "Error 500: Request failed."
- Formats:
- Dates:
Sun, 24 Jan · 9:30 AM EAT - Money:
KES 1,500 - Places:
iHub Nairobi, Senteu Plaza
- Dates:
- Don't use "Oops!", "functionality", "utilize", or emoji in UI chrome.
- Kiswahili (
sw-KE):- Keep it warm, not formal ("Karibu", "Habari za asubuhi, Zawadi").
- Tech terms stay in English (Compose, KMP, RSVP, CFP, Quick connect). Translate the verbs around them ("Fanya RSVP", "Wasilisha mada").
- Dates and times: "Jumapili, 24 Jan 2027", "Saa 3:30 asubuhi". Leave EAT out.
- Text runs 20–40% longer, so labels wrap and never truncate.
- Nav labels: Nyumbani · Matukio · Jumuiya · Wasifu.
Shared Compose code covers brand content. Platform chrome stays native.
| Concern | Android (Material 3) | iOS |
|---|---|---|
| Root navigation | NavigationBar + NavigationBarItem | TabView / TabBar |
| Top bar | TopAppBar (Small / Large / Home) | NavigationStack title (inline / large) |
| Back | arrow_back + predictive back |
Native back swipe |
| Sheets | ModalBottomSheet | .sheet with .medium / .large detents |
| Dialogs | AlertDialog | .alert / .confirmationDialog |
| Date/time | DatePicker / TimePicker dialogs | DatePicker (compact / graphical) |
| Community tabs | PrimaryTabRow | Segmented Picker |
| Shared | Button, Chip, cards, StateMessage, Skeleton, InlineNote, type and colour tokens | ← same Compose components |
The Figma iOS page covers root tabs, key flows and parity flows (#19): submit a talk, Quick connect, notifications, session detail, edit profile, member profile, onboarding, and panel discussions.
| Window class | Width | Layout |
|---|---|---|
| Compact | 360–412dp | NavigationBar, single column |
| Medium | 600dp | NavigationRail, 2-column card grid |
| Expanded | 840dp | NavigationRail, list-detail panes (M2) |
| Foldable | 673×841 book-portrait | NavigationRail; tabletop posture for the check-in scanner (hinge-aware) |
Every screen must also hold up at 200% font scale.
A screen isn't done until it has:
- Default (designed at 360dp, verified at 412dp)
- Loading (
Skeleton) - Empty / Error / Offline (
StateMessage/InlineNote) - Dark theme
- 200% font scale
- Kiswahili length check
- Medium / Expanded / Foldable layouts, where the screen is in M1/M2 scope
This is a Kotlin Multiplatform project built with the Kotlin Toolchain (project.yaml / module.yaml, not Gradle).
Android254/
├── project.yaml # module list: androidApp, iosApp, shared
├── libs.versions.toml # version catalog
├── kotlin / kotlin.bat # self-bootstrapping toolchain wrappers
├── androidApp/ # Android app (namespace & applicationId: ke.kotlinke.android254)
│ ├── src/MainActivity.kt
│ └── res/
├── iosApp/ # iOS app (SwiftUI entry → Compose ViewController)
│ └── src/
└── shared/ # KMP library: Compose UI, business logic
├── src/ # common code
├── src@android/ # Android-only code
├── src@ios/ # iOS-only code
├── composeResources/ # shared resources (Res.*, package ke.kotlinke.android254.resources)
└── test*/ # common and platform tests
Current stack: Kotlin Multiplatform, Compose Multiplatform (runtime, foundation, Material 3, resources, hot reload), AndroidX Lifecycle ViewModel and runtime-compose, and Activity Compose. Targets: Android, iosArm64, iosSimulatorArm64.
The kotlin (macOS/Linux) and kotlin.bat (Windows) wrappers download the pinned toolchain on first use. You don't need to install anything separately.
./kotlin build # build everything
./kotlin run -m androidApp # run on Android
./kotlin run -m iosApp # run on iOS (macOS + Xcode required)
./kotlin test # run all tests
./kotlin test -m shared # test a single moduleYou can also use the run configurations in your IDE's toolbar.
- Onboarding order: A/B test pending (see Onboarding).
- Navigation drawer: designed, but out of the MVP.
- Decisions are logged in
docs/decisions.md. Create it as decisions are made.
Learn more: Kotlin Multiplatform · Compose Multiplatform · Kotlin Toolchain

