Embracing AGP 9 and JDK 21: A Gradual Path to Android Build Optimization

As Android developers, major AGP (Android Gradle Plugin) updates are always significant. AGP 9 in particular promises a stricter, faster build environment, moving away from ambiguous configurations.

Instead of waiting for its full release and then scrambling to fix issues, why not start preparing your project now, gradually aligning it with "AGP 9 standards" through your gradle.properties file?

 

🤔 Why JDK 21 and AGP 9 Now? (The Ultimate Synergy)

When transitioning to AGP 9, updating to JDK 21 isn't just a "requirement"; it's a powerful "booster" that dramatically enhances your development experience.

  • Performance Synchronization: JDK 21's improved resource management, including features like Virtual Threads, allows Gradle to fully leverage its parallel build capabilities, leading to more stable and efficient builds.
  • Language Specification Alignment: By targeting Java 21, you bridge potential gaps in type inference and bytecode generation in mixed Java/Kotlin projects, especially as Kotlin 2.x gains traction.
  • Precision R8 Optimization: AGP 9 is optimized to parse and transform class files generated by JDK 21. This means that even with stricter settings, R8 can more accurately understand modern code structures, reducing the need for excessive keep rules while safely shrinking code.

This combination offers the kind of seamless experience you get from pairing the latest OS with the latest CPU.

 

🤔 Prepare with gradle.properties: 10 Flags to Enable Today

The strategy is simple: enable one flag at a time, fix any errors that arise, and then move to the next. This iterative approach is the most reliable way to prepare for AGP 9.

1. Structure Enforcement (Clean Up Your Project)

  • android.uniquePackageNames=true: Prevents duplicate package names across modules, eliminating resource conflicts.
  • android.usesSdkInManifest.disallowed=true: Enforces placing minSdk, targetSdk, etc., in build.gradle instead of AndroidManifest.xml.
  • android.defaults.buildfeatures.resvalues=true: Explicitly controls the generation of resValue entries.

2. Build Speed Enhancements

  • android.enableAppCompileTimeRClass=true: Uses lightweight R classes during app compilation, significantly improving build times for large projects.
  • android.sdk.defaultTargetSdkToCompileSdkIfUnset=true: Automatically sets targetSdk to compileSdk if unspecified, preventing inconsistent behavior.
  • android.dependency.useConstraints=true: Utilizes Gradle's "Constraints" feature for dependency resolution, making library version management more robust.

3. Aggressive R8 / Optimization Settings (The Biggest Hurdle)

  • android.r8.strictFullModeForKeepRules=true: Enables R8's Full Mode. This maximizes optimization but requires precise keep rules for code that relies on reflection, potentially leading to crashes if not handled correctly.
  • android.r8.optimizedResourceShrinking=true: Employs a more advanced algorithm for removing unused resources, leading to smaller app sizes.

4. Next-Gen Defaults

  • android.builtInKotlin=true: Prioritizes AGP's built-in Kotlin support.
  • android.newDsl=false: Use this to maintain the current DSL while preparing for future changes.

 

🤔 Conclusion: One Flag at a Time for a Smoother Future

The AGP 9 update is akin to a major cleanup. Attempting it all at once can be overwhelming, but tackling it gradually makes it incredibly rewarding.

Why not start with android.uniquePackageNames=true? With each flag you enable, your project will move closer to a more modern, robust, and efficient build environment.

👉 AGP 9.0 移行ガイド:新旧コード比較で見るモダンビルド設定


【Android】How to handle UI interactions with event processing

 

🧑🏻‍💻 Summary

  • UI Only
    • Good for very small, UI-contained logic.
    • No state preservation; everything resets on recomposition.
    • Simple one-time actions still work fine.
    • Weak testability and scalability.
    • Poor separation of concerns without ViewModel.
  • StateFlow
    • Designed specifically for holding and exposing UI state.
    • Always provides the latest value after recomposition.
    • Not suitable for one-time events because old values persist.
    • Ideal for continuously changing UI states.
    • Assumes at least one active collector.
    • Ensures consistent UI across configuration changes.
  • SharedFlow
    • Optimal for one-time UI events (Toast, Snackbar, Navigation).
    • replay = 0 + extraBufferCapacity = 1 helps prevent event loss.
    • Supports multiple collectors safely.
    • Not intended for state storage.
    • Standard modern pattern for UI event handling.
    • Robust against lifecycle situations where collectors appear/disappear.
  • Channel
    • Best suited for FIFO, strictly ordered event delivery.
    • Should only have one collector (shared consumption is unsafe).
    • Ensures one-by-one processing with guaranteed order.
    • Less compatible with UI lifecycle due to collector timing issues.
    • Typically replaced by SharedFlow in UI-layer logic.
    • More appropriate for internal sequential pipelines rather than UI events.

👉 【Android】UIイベント整理術:StateFlow・SharedFlow・Channelの使い分け完全ガイド
👉 Why Kotlin Channels Are the Natural Solution for Preventing "Double Execution" of Events


How dp/sp/px Conversion Works in Android

  • This code defines extension functions to convert between dp, sp, and px.
  • It relies on density (for dp) and scaledDensity (for sp) extracted from Android’s DisplayMetrics.
  • The goal is to keep UI elements visually consistent across devices with different screen densities.

 

🧑🏻‍💻 Why These Conversions Matter (Foundation)

Screen Density Model


+---------------------------------------------+
| density       → converts dp ↔ px            |
| scaledDensity → converts sp ↔ px (font size)|
+---------------------------------------------+

  • dp: density-independent pixels
  • sp: scale-independent pixels (respects user font size setting)
  • px: raw physical pixels

density and scaledDensity come from:


resources.displayMetrics

This ensures the UI scales correctly across devices.

 

🧑🏻‍💻 Key Conversion Logic

1. dp → px

Formula: px = dp × density


dpToPx(dp) = dp.value * density

2. dp → sp

( dp → px → sp )
Formula: sp = dp × density ÷ scaledDensity


dpToSp(dp) = (dp.value * density / scale).sp

3. px → dp

Formula: dp = px ÷ density


toDp(px) = (px / density).dp

4. px → sp

Formula: sp = px ÷ scaledDensity


toSp(px) = (px / scale).sp

5. sp → dp

Formula: dp = sp × scaledDensity ÷ density


spToDp(sp) = (sp.value * scale / density).dp

6. sp → px

Formula: px = sp × scaledDensity


spToPx(sp) = sp.value * scale

7. Type-Specific Extensions

The code also adds natural calling styles:


Dp.toPx(context)
Float.toDp(context)
TextUnit.toPx(context)
TextUnit.toDp(context)

These simply delegate to the Context converters and make the API flexible.

 

🧑🏻‍💻 Visualization — Full Conversion Map


+------------------+           +------------------+
|       Dp         | <------>  |        px        |
|   (dp.value)     |           |     (Float)      |
+------------------+           +------------------+
          |                               ^
          | dpToSp / spToDp               |
          v                               |
+------------------+           +------------------+
|       Sp         | <------>  |    scaled px     |
|  (TextUnit.sp)   |           |  (scaledDensity) |
+------------------+           +------------------+

 

🧑🏻‍💻 Notes & Caveats

  • scaledDensity changes when users adjust system font size.
  • Jetpack Compose usually hides px conversions, but you still need px for:
    • Custom drawing
    • Canvas operations
    • Bitmap sizing
  • Expert consultation recommended for deeply understanding DPI internals in OEM-modified environments.

 

🧑🏻‍💻 References

👉 dp / px / sp 完全相互変換
👉 DisplayMetrics  |  API reference  |  Android Developers
👉 各種のピクセル密度をサポートする  |  Compatibility  |  Android Developers