Files
sizzletracker-android/app/build.gradle.kts
Reactorcoremeltdown 2f671ed372 UI smoothness: distinct isPlaying flow + allocation-free grid draw (v0.5.1)
Two changes that cut playback-time UI jank (measured on-device):
- Expose a deduped isPlaying StateFlow (maps the transport flow through
  distinctUntilChanged) and collect THAT in the transport toolbar, so the
  PLAY/PAUSE button recomposes on play↔stop instead of ~12×/second as the
  playhead advances (the full transport emits every line).
- Precompute the pattern grid's fixed-width text (gutter hex, note names,
  velocity/channel) into class-load lookup tables. The grid redraws on
  every playhead line and scroll pixel, and the old toString/padStart
  formatting allocated a String per cell per frame — steady draw-phase
  garbage. The draw is now allocation-free (also improves GlyphCache hit
  rate via stable string instances).

gfxinfo over ~12s of playback on the busy track (moto g84):
  janky frames 47.2% → 32.0%; 99th-pct frame 129ms → 53ms;
  missed vsync 40 → 10; 50th 18ms → 14ms.

No engine/audio change. Remaining jank is the auto-follow scroll
redrawing the whole grid each line (everything shifts), not addressed here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:49:08 +02:00

136 lines
5.0 KiB
Kotlin

// Build configuration for the single application module. This is where we turn
// on Compose, set the min/target Android versions, and list dependencies.
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "space.rcmd.android.sizzle"
compileSdk = 34
// Native (Oboe) low-latency output backend; see src/main/cpp.
// NDK r27+ builds native libs 16 KB-page-aligned by default and ships a
// 16 KB-aligned libc++_shared.so (required for Android 15 / Google Play).
ndkVersion = "27.2.12479018"
defaultConfig {
applicationId = "space.rcmd.android.sizzle"
// minSdk 26 (Android 8.0) is the floor for the native MIDI API
// (android.media.midi) and AAudio low-latency audio we rely on.
minSdk = 26
targetSdk = 34
versionCode = 16
versionName = "0.5.1"
// We provide our own instrumentation runner if/when tests are added.
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake { arguments += "-DANDROID_STL=c++_shared" }
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
buildTypes {
release {
// Shrinking is off by default so newcomers get readable stack traces.
// Flip to true once the app stabilises.
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
// A realistic-performance "staging" variant for testing UI responsiveness
// on-device. The `debug` build is `debuggable = true`, which makes ART run
// the app un-optimised and makes Compose several times slower — so measuring
// UI "snappiness" on a debug build is misleading. This variant flips
// debuggability off (the single biggest perf lever) while reusing the debug
// signing key, so it still installs without a release keystore. R8/shrinking
// stays off to avoid needing JNI/Compose keep-rules for now.
create("staging") {
initWith(getByName("debug"))
isDebuggable = false
isJniDebuggable = false
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += "debug"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
prefab = true // exposes the Oboe AAR's native headers/lib to CMake
}
packaging {
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
// Give the release APK a predictable, versionCode-stamped name so it drops straight
// into the private F-Droid repo: space.rcmd.android.sizzle_<versionCode>.apk. Uses
// the legacy variant API (the Kotlin-DSL `android {}` receiver doesn't expose it).
(extensions.getByName("android") as com.android.build.gradle.AbstractAppExtension)
.applicationVariants.all {
if (buildType.name == "release") {
val vc = versionCode
outputs.all {
(this as com.android.build.gradle.internal.api.BaseVariantOutputImpl)
.outputFileName = "space.rcmd.android.sizzle_$vc.apk"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.activity.compose)
// Compose UI toolkit — the BOM keeps every Compose artifact on one version.
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
debugImplementation(libs.androidx.compose.ui.tooling)
// Background playback + media-style notification transport controls.
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.common)
// Persistence for settings & themes.
implementation(libs.androidx.datastore.preferences)
// Coroutines for the audio/sequencer background work.
implementation(libs.kotlinx.coroutines.android)
// Oboe: low-latency native audio (AAudio) — the native output backend.
// 1.9.3+ ships 16 KB-page-aligned .so files (required by Android 15 / Play).
implementation("com.google.oboe:oboe:1.10.0")
// JVM unit tests for the pure model/io logic (no device needed).
testImplementation("junit:junit:4.13.2")
}