Android (Material Components)

Tier 1 · Supported

android-mdc renders Material Components widgets over JNI, inside a checked-in Gradle project. The app theme is Theme.Material3Expressive.DayNight.NoActionBar, so light and dark follow the system.

Getting started

You need the Android SDK and NDK, JDK 17 or newer, and cargo-ndk; day doctor --toolkit android checks each one and prints what’s missing. To work in Android Studio, you also need Android Studio 2026.1.4 or newer (system requirements).

cargo install day-cli cargo-ndk
day new app my-app --toolkit android-mdc
cd my-app
day launch -p android-mdc         # installs on every connected device/emulator, right ABI each
day pack -p android-mdc           # release-signed .apk + .aab

The Gradle project

platform/android/ is a standard Gradle project with short scripts. Day’s Gradle plugin, applied in settings.gradle.kts and app/build.gradle.kts, configures the app module from Day.toml and your pieces: the application id and version, the SDK levels, the Android Gradle Plugin version, source folders, libraries, the merged manifest, release minification, and signing. day build, day prepare, and day open put the plugin in place from the version of Day your app builds against, so updating Day updates your Android build and these files stay as they are.

Your own settings go in app/build.gradle.kts. They apply after Day’s plugin, so they take precedence:

android {
    defaultConfig {
        minSdk = 26
    }
}

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
}

day open -p android-mdc opens the project in Android Studio. A build started there packages the Rust library from the last day build, so run day build -p android-mdc after changing Rust code.

Caveats

  • Immersive top chrome is an opt-in. A dev.daybrite.day.EDGE_TO_EDGE <meta-data> entry in the app manifest makes the status bar transparent and floats the nav bar over full-bleed pages; nav host items marked .immersive() keep the floating scrim bar, other pages get an opaque colorPrimary bar. Content pads itself by day::safe_area() (Layout). Without the opt-in, content renders below the bars.
  • Accessibility annotations are partial; see the current limits.
  • Process death is a cold start unless your app persists its own state. Day does not restore a navigation stack for you.
  • day-piece-map has no Android arm (it would pull in a Google Play services dependency); it renders as a placeholder.
  • Runtime permissions are yours to request. Day generates the manifest entries from your [permissions] table, and day-part-permissions performs the request, but Android’s never-asked and permanently-denied states are indistinguishable without app-side persistence.
  • The emulator leg in CI tolerates flakes; the build itself gates hard.

What each piece becomes

The generated coverage matrix, checked in CI, covers every backend; this table adds links and notes.

Pieces map to both Material Components and framework android.widget classes; the table names which one each piece uses.

Day pieceAndroid class
column / row / nav_stack / sectiona ViewGroup subclass that lays children out at absolute frames
labelTextView
buttonMaterialButton
toggleMaterialSwitch
sliderSlider
text_fieldTextInputLayout wrapping TextInputEditText
text_areaEditText (framework)
picker().menu()Spinner
picker().segmented()a row of framework Buttons
picker().inline()RadioGroup
progress(f)LinearProgressIndicator
spinner()Material LoadingIndicator (M3 Expressive)
dividerMaterialDivider
scrollScrollView / HorizontalScrollView
listRecyclerView with a RecyclerView.Adapter (recycled cells; drag reorder via ItemTouchHelper)
navMaterialToolbar over a fragment-driven FrameLayout
nav_menuscrolling TextView rows
tabsBottomNavigationView
cover()a full-screen FrameLayout shell
imageImageView
vectorImageView loading a VectorDrawable staged into res/drawable/ (art outside the VectorDrawable subset ships the raster instead, with a lint heads-up)
canvasa View subclass replaying the display list onto Canvas
dialogs / alerts / promptsMaterialAlertDialogBuilder
file dialogsStorage Access Framework intents
context menusPopupMenu
external activity()ProgressBar (framework, circular)
external combobox()AutoCompleteTextView
external search_field()EditText with IME_ACTION_SEARCH
external date_picker()MaterialDatePicker, or inline DatePicker
external time_picker()MaterialTimePicker, or inline TimePicker
external pull_refresh()SwipeRefreshLayout
external webview()WebView
external media()VideoView
external lottie()LottieAnimationView (lottie-android)

Text measurement is Android’s View.measure with an AT_MOST width probe, so TextView does the line breaking.

Gradle dependencies come from the pieces you use: Lottie and SwipeRefreshLayout each contribute their own coordinate, and Day merges them into the app module.