Skip to content

Latest commit

 

History

614 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

droidcon Kenya — Android 🔥🔨

CI codecov License PRs welcome

Kotlin Compose BOM AGP Gradle minSdk Material 3

The official Android app for droidcon Kenya, the Android developer conference held in Nairobi. It carries the schedule, speakers, sponsors, organisers and event feed, works offline once it has synced, and lets attendees bookmark the sessions they plan to attend.

The app is also the community's shared teaching codebase: a current, opinionated, multi-module Android project that people learn from and contribute to. Most of the decisions in here are written down rather than assumed — see docs/ and AGENTS.md.


Contents


Designs

This is the link to the app designs:


Running the project

Clone it and build. There is no setup step.

git clone https://github.com/droidconKE/droidconKeKotlin.git
cd droidconKeKotlin
./gradlew assembleDebug

Or open the folder in Android Studio and press Run.

You do not need to install or configure a JDK. gradle/gradle-daemon-jvm.properties pins the daemon to Java 17 and the foojay resolver in settings.gradle.kts downloads it if your machine hasn't got one. The Android SDK components, the Gradle distribution, and the emulators the instrumentation tests run on are all provisioned by the build too.

The debug build is signed with the checked-in keystore/dckedebug.keystore, so a fresh clone installs over an existing build and keeps the Firebase debug SHA-1 stable. That is deliberate — it is a shared debug key, not a secret, and the release key is not in this repo.


Module layout

Module What lives there
app Application, MainActivity, navigation, notifications, DI root
core:model Pure Kotlin data classes. A JVM module — no Android on its classpath
core:common Dispatcher qualifiers and other cross-cutting plumbing
core:designsystem chai — colours, typography, shapes, shared components
core:ui Presentation models, shared composables, navigation keys, resources
core:domain Repository interfaces and sync contracts
core:data Repository implementations, the sync worker, mappers
core:database Room database, DAOs, entities
core:network Ktor client, DTOs, Remote Config
core:screenshot Roborazzi harness. Test-only
core:testing Shared test doubles. Test-only
benchmarks Macrobenchmark and baseline profile generation. Not shipped
feature:* about auth feed home sessions speakers — screens, view models, tests, goldens
build-logic Convention plugins — every module's build config comes from here
graph TD
    app[":app"]
    feature["<b>:feature:*</b><br/>about · auth · feed<br/>home · sessions · speakers"]
    ui[":core:ui"]
    ds[":core:designsystem"]
    domain[":core:domain"]
    model[":core:model"]
    data[":core:data"]
    database[":core:database"]
    network[":core:network"]

    app --> feature
    app --> data
    app --> database
    app --> network

    feature --> ui
    ui --> ds
    ui --> domain
    domain --> model

    data --> domain
    data --> database
    data --> network

    classDef pure fill:#0b7285,stroke:#0b7285,color:#ffffff;
    classDef uic fill:#5f3dc4,stroke:#5f3dc4,color:#ffffff;
    classDef io fill:#2b8a3e,stroke:#2b8a3e,color:#ffffff;
    class domain,model pure;
    class app,feature,ui,ds uic;
    class data,database,network io;
Loading

The dependency rules: core:model depends on nothing and has no Android on its classpath, which the build enforces — that is what would make a move to Kotlin Multiplatform a port rather than a rewrite. core:data depends on core:domain and never the reverse. A feature module never depends on another feature module; anything two features need belongs in core:ui, and cross-feature navigation goes through the NavKeys it owns. app is the only module that may depend on every feature, because the composition root knows about all of them by definition.

Note that core:database and core:network do not depend on core:domain either. They own their own DTOs and Room entities and know nothing about the domain model; core:data is the only module that sees both sides, and the mappers there are the seam. That is why swapping the API representation of a session does not reach the UI.

A new module applies the convention plugins rather than copying a build.gradle.kts:

plugins {
    alias(libs.plugins.droidconke.android.library)
    alias(libs.plugins.droidconke.android.hilt)
}

Architecture

Offline-first. The UI never waits on the network: screens read from Room, and a WorkManager job refreshes Room from the API in the background. A cold start with no connection still shows the last synced schedule.

sequenceDiagram
    autonumber
    participant UI as Compose screen
    participant VM as ViewModel
    participant Repo as Repository<br/>(:core:data)
    participant Room as Room<br/>(:core:database)
    participant Ktor as Ktor<br/>(:core:network)
    participant Work as SyncDataWorker

    UI->>VM: collect uiState
    VM->>Repo: observe sessions
    Repo->>Room: query, returns a Flow
    Room-->>UI: cached data, immediately

    Note over Work: periodic + on app start
    Work->>Repo: sync() for sessions, speakers,<br/>sponsors, organisers, feed
    Repo->>Ktor: fetch
    Ktor-->>Repo: DTOs
    Repo->>Room: replace
    Room-->>UI: Flow re-emits, UI updates
Loading

The five repositories sync concurrently and the job only reports success when all of them succeed, so a partial refresh is retried rather than treated as done.

Fuller treatment, including the navigation model and the Room schema: docs/architecture.md.


Tech stack

Concern Choice
Language Kotlin 2.4, coroutines and Flow
UI Jetpack Compose, Material 3, chai design system
Adaptive material3-adaptive — bar / rail / drawer and two panes, by window size
Navigation Navigation 3@Serializable NavKeys, no route strings
DI Hilt with KSP
Networking Ktor 3 with kotlinx.serialization
Persistence Room 2.8, DataStore
Background work WorkManager
Images Coil
Firebase Crashlytics, Remote Config, Messaging, Performance
Logging Timber
Tests JUnit4, Robolectric, MockK, Turbine, Compose UI test

Navigation 3 is not Navigation 2 renamed. Destinations are @Serializable keys implementing NavKey; there is no NavHost and there are no route strings. See presentation/src/main/java/com/android254/presentation/common/navigation/.


Quality gates

Everything below runs on every pull request. Run it locally before pushing and CI holds no surprises.

./gradlew spotlessApply ktlintFormat          # format first — the checks are strict
./gradlew spotlessCheck ktlintCheck detekt    # style and static analysis
./gradlew lint                                # Android Lint + Slack's Compose rules
./gradlew stabilityCheck                      # Compose recomposition regressions
./gradlew testDebugUnitTest                   # JVM + Robolectric
Tool Catches
spotless Formatting and the Apache licence header on every file
ktlint Kotlin style, official code style
detekt Complexity, code smells, TODO left in comments
Android Lint Platform, resource, manifest and API-level correctness
compose-lints Compose API shape, state and stability mistakes
compose-stability-analyzer Composables that quietly stop being skippable

There is no lint baseline and no ktlint baseline. Severity decisions live in config/lint/lint.xml, one rule per line with the reason next to it, so every suppression shows up in a diff. What each tool is set to, why, and the debt still being burned down: docs/static-analysis.md.


Testing

./gradlew testDebugUnitTest                                  # all unit tests
./gradlew :feature:sessions:testDebugUnitTest --tests "*SessionsFilterStateTest*"

Instrumentation tests run on Gradle Managed Devices, so there is no emulator to create or start — Gradle provisions and tears them down:

./gradlew :core:data:supportedApiLevelsGroupDebugAndroidTest  # api30 + api34

Coverage is measured with JaCoCo on debug variants and reported to Codecov.


Adding a dependency

All dependencies are declared in gradle/libs.versions.toml, a Gradle version catalog. One place to look, one place to bump, and changing a version does not invalidate the compilation of every module.

Add the version under [versions], then the library under [libraries]:

[versions]
splash = "1.2.0"

[libraries]
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splash" }

Then use it as implementation(libs.androidx.splashscreen) — Gradle normalises -, _ and . into . for the accessor. Check whether it belongs in an existing [bundles] entry before adding it module by module.

To see what is out of date:

./gradlew dependencyUpdates

Compose previews and ViewModels

A composable that calls hiltViewModel() cannot be previewed. Split it: keep a composable that takes the ViewModel and immediately delegates to a second one that takes plain state and callbacks, then preview the second. The Compose tooling docs cover the pattern.


Contributing

Contributions are welcome, and this repository is deliberately a good place to make a first one. CONTRIBUTING.md covers the process — issues, forks, PR expectations. AGENTS.md covers the things that have already cost this codebase a bug and are easy to reintroduce; it is worth ten minutes before your first PR.

Contributors

We would endlessly like to thank the following contributors

donald-okara
Don Okara
chepsi
Evans Chepsiror
wangerekaharun
Harun Wangereka
ndiritumichael
Michael Ndiritu
kibettheophilus
Kibet Theo
Raynafs
Rachel Murabula
janewaitara
Jane Waitara
yveskalume
Yves Kalume
misshannah
Hannah Olukoye
Borwe
Brian Orwe
chege4179
Peter Chege
robert-nganga
Robert Nganga
michaelbukachi
Michael Bukachi
KennethMathari
Kenneth Mathari
mutukuian
Ian Nthuli
jumaallan
Juma Allan
MamboBryan
MamboBryan
joelkanyi
Joel Kanyi
Jacquigee
Jacquiline Gitau
emmanuelmuturia
Emmanuel Muturia
kanake10
N3
paulodhiambo
Odhiambo Paul
jumapaul
Paul Juma
cliffgor
Cliff Gor
AmenyaEngr
Null
Terry-Mochire
Terry Mochire
whoisnjoguu
Titan
mertoenjosh
Martin Thuo
joelmuraguri
Joel Muraguri
etonotieno
Eton Otieno
mog-rn
Amos Nyaburi
lokified
Sheldon Okware
joenjogu
Joenjogu
zmhfh
Zmhfh
anuragkanojiya1
Null
stephenWanjala
Wanjala Stephen
josphatmwania
Josphat Mwania
Jeremy-Gitau
Jeremy
Dbriane208
Null
princemill
Chris Matee
BKinya
Beatrice Kinya
tamzi
Tamzi
---

Licence

Copyright 2026 droidcon Kenya

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

About

dcke

Resources

Contributing

Stars

113 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages