From eba2836995c7194f99c9484b3335f75efa43bfed Mon Sep 17 00:00:00 2001 From: Dipen Pradhan Date: Sat, 6 Jun 2026 19:06:30 -0700 Subject: [PATCH 1/3] feat: adopt the Compose BOM (2024.12.01) for unified version management - Replace compose/material3 version constants with composeBom - Strip explicit versions from BOM-managed artifacts - Import the BOM platform on every configuration that receives Compose artifacts (implementation, debugImplementation, androidTestImplementation, :data) - Migrate API removals that come with Compose 1.7 / Material3 1.3: SmallTopAppBar -> TopAppBar, rememberRipple -> material3 ripple - Update the outdated README Compose badge to the BOM version Fixes #186 --- README.md | 4 +-- .../ui/animation/AnimationScreen.kt | 4 +-- .../home/advancelists/AdvanceListsActivity.kt | 4 +-- .../ui/home/customfling/FlingListActivity.kt | 4 +-- .../ui/home/dialogs/DialogsActivity.kt | 4 +-- .../ui/home/dynamic/DynamicUIActivity.kt | 4 +-- .../ui/home/lists/ListViewActivity.kt | 4 +-- .../ui/learnwidgets/AppBars.kt | 4 +-- .../build/dependencies/Dependencies.kt | 26 +++++++++---------- .../DependencyHandlerExtensions.kt | 9 +++++++ .../build/dependencies/Versions.kt | 3 +-- data/build.gradle.kts | 1 + .../datingapp/DatingHomeActivity.kt | 4 +-- .../instagram/components/stories/StoryItem.kt | 4 +-- 14 files changed, 44 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 0a4c1de3..edfc1c66 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ - - + + diff --git a/app/src/main/java/com/guru/composecookbook/ui/animation/AnimationScreen.kt b/app/src/main/java/com/guru/composecookbook/ui/animation/AnimationScreen.kt index 97becc74..32c7d92d 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/animation/AnimationScreen.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/animation/AnimationScreen.kt @@ -10,8 +10,8 @@ import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -39,7 +39,7 @@ fun AnimationScreen() { Scaffold( modifier = Modifier.testTag(TestTags.ANIM_SCREEN_ROOT), topBar = { - SmallTopAppBar( + TopAppBar( title = { Text(text = "Animations") }, navigationIcon = { IconButton(onClick = { animateIcon = !animateIcon }) { diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt index b89d5438..ffd0c8b7 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt @@ -16,8 +16,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -49,7 +49,7 @@ class AdvanceListsActivity : ComponentActivity() { // A surface container using the 'background' color from the theme Scaffold( topBar = { - SmallTopAppBar( + TopAppBar( title = { Text(text = "Advance Lists(In Progress)") }, navigationIcon = { IconButton(onClick = { onBackPressed() }) { diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt index 88adf82e..cb7e5f7f 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt @@ -18,8 +18,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.livedata.observeAsState @@ -139,7 +139,7 @@ fun ListViewContent( */ Scaffold( topBar = { - SmallTopAppBar( + TopAppBar( title = { Column(modifier = Modifier.padding(4.dp)) { /* diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt index e53afdc9..10eb1bd6 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt @@ -24,9 +24,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -77,7 +77,7 @@ class DialogsActivity : AppCompatActivity() { fun DialogScreen(onBack: () -> Unit) { Scaffold( topBar = { - SmallTopAppBar( + TopAppBar( title = { Text(text = "Dialogs") }, navigationIcon = { IconButton(onClick = onBack) { diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt index fcc3f87b..44ef743f 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt @@ -13,8 +13,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -64,7 +64,7 @@ class DynamicUIActivity : ComponentActivity() { fun DynamicUiWrapper(uiType: String, onback: () -> Unit) { Scaffold( topBar = { - SmallTopAppBar( + TopAppBar( title = { Text(text = uiType) }, navigationIcon = { IconButton(onClick = onback) { diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt index 45c1f9f2..59022f45 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt @@ -21,8 +21,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -73,7 +73,7 @@ fun BaseView(isDarkTheme: Boolean, content: @Composable () -> Unit) { fun ListViewContent(listType: String, onBack: () -> Unit) { Scaffold( topBar = { - SmallTopAppBar( + TopAppBar( title = { Column(modifier = Modifier.padding(4.dp)) { Text(text = "ListView") diff --git a/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt b/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt index ef1e7752..cb496194 100644 --- a/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt +++ b/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt @@ -25,8 +25,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar as Material3TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -57,7 +57,7 @@ fun AppBars() { fun TopAppBarsDemo() { SubtitleText(subtitle = "Top App bar") - SmallTopAppBar( + Material3TopAppBar( title = { Text(text = "Home") }, navigationIcon = { IconButton(onClick = {}) { diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt index c2c63dd6..8028a45b 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt @@ -17,19 +17,19 @@ object Dependencies { // Removed googleMaps = "com.google.android.libraries.maps:maps" as it's not publicly available const val playServicesMaps = "com.google.android.gms:play-services-maps:${Versions.playServicesMaps}" - const val composeUi = "androidx.compose.ui:ui:${Versions.compose}" - const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}" + // Compose BOM keeps all androidx.compose artifacts below at compatible versions + const val composeBom = "androidx.compose:compose-bom:${Versions.composeBom}" + const val composeUi = "androidx.compose.ui:ui" + const val composeUiTooling = "androidx.compose.ui:ui-tooling" // Material Design 1 and 3 dependencies - const val composeMaterial = "androidx.compose.material:material:${Versions.compose}" - const val composeMaterial3 = "androidx.compose.material3:material3:${Versions.material3}" - const val composeMaterial3WindowSizeClass = "androidx.compose.material3:material3-window-size-class:${Versions.material3}" - const val composeMaterialIconsExtended = - "androidx.compose.material:material-icons-extended:${Versions.compose}" - + const val composeMaterial = "androidx.compose.material:material" + const val composeMaterial3 = "androidx.compose.material3:material3" + const val composeMaterial3WindowSizeClass = "androidx.compose.material3:material3-window-size-class" + const val composeMaterialIconsExtended = "androidx.compose.material:material-icons-extended" + // Runtime and State Management - const val composeRuntime = "androidx.compose.runtime:runtime:${Versions.compose}" - const val composeRuntimeLivedata = - "androidx.compose.runtime:runtime-livedata:${Versions.compose}" + const val composeRuntime = "androidx.compose.runtime:runtime" + const val composeRuntimeLivedata = "androidx.compose.runtime:runtime-livedata" // Layout and Navigation const val composeConstraintLayout = @@ -41,8 +41,8 @@ object Dependencies { const val composeLottie = "com.airbnb.android:lottie-compose:${Versions.lottie}" const val composeNavigation = "androidx.navigation:navigation-compose:${Versions.navCompose}" - const val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4:${Versions.compose}" - const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.compose}" + const val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4" + const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest" /** * Custom fling behaviour diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt index 54a15de4..d5f3ff9d 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt @@ -2,7 +2,13 @@ package com.guru.composecookbook.build.dependencies import org.gradle.api.artifacts.dsl.DependencyHandler +/** Imports the Compose BOM so the unversioned Compose artifacts resolve consistently. */ +private fun DependencyHandler.addComposeBom(configurationName: String) { + add(configurationName, platform(Dependencies.composeBom)) +} + fun DependencyHandler.addComposeOfficialDependencies() { + addComposeBom("implementation") composeOfficialDependencies.forEach { add("implementation", it) } @@ -15,6 +21,7 @@ fun DependencyHandler.addComposeThirdPartyDependencies() { } fun DependencyHandler.addComposeDebugDependencies() { + addComposeBom("debugImplementation") composeDebugDependencies.forEach { add("debugImplementation", it) } @@ -33,6 +40,7 @@ fun DependencyHandler.addKotlinTestDependencies() { } fun DependencyHandler.addDataDependencies() { + addComposeBom("implementation") add("ksp", Dependencies.roomCompiler) dataDependencies.forEach { add("implementation", it) @@ -76,6 +84,7 @@ fun DependencyHandler.addThirdPartyUnitTestsDependencies() { } fun DependencyHandler.addAndroidInstrumentationTestsDependencies() { + addComposeBom("androidTestImplementation") androidInstrumentationTestsDependencies.forEach { add("androidTestImplementation", it) } diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt index 270f8411..94caffad 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt @@ -1,7 +1,7 @@ package com.guru.composecookbook.build.dependencies internal object Versions { - const val compose = "1.6.1" // Latest stable Compose UI version + const val composeBom = "2024.12.01" // Compose BOM maps ui/material/runtime etc. versions const val activityCompose = "1.8.2" // Latest stable const val pagingCompose = "3.2.1" // Latest stable const val navCompose = "2.7.7" // Latest stable navigation @@ -19,7 +19,6 @@ internal object Versions { const val appcompat = "1.6.1" // Latest stable const val paletteKtx = "1.0.0" // Latest stable const val material = "1.11.0" // Latest stable Material Design 1 - const val material3 = "1.2.1" // Latest stable Material 3 const val playServicesAds = "22.5.0" // Latest stable const val googleMaps = "2.0.1" // Latest Google Maps version const val playServicesMaps = "18.2.0" // Latest stable diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 4e206914..98e33820 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -6,5 +6,6 @@ plugins { android { namespace = "com.guru.composecookbook.data" } dependencies { + implementation(platform(com.guru.composecookbook.build.dependencies.Dependencies.composeBom)) implementation(com.guru.composecookbook.build.dependencies.Dependencies.composeRuntime) } diff --git a/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt b/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt index e37eb3d8..659b7501 100644 --- a/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt +++ b/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt @@ -21,8 +21,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -115,7 +115,7 @@ fun DatingHomeAppbar(navType: MutableState) { DatingNavType.CHATS -> "Chats" DatingNavType.PROFILE -> "My Profile" } - SmallTopAppBar( + TopAppBar( title = { Text(title, style = typography.h6) }, actions = { IconButton(onClick = {}) { Icons.Default.MyLocation } }, ) diff --git a/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt b/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt index 3ee365fa..01e0e3d3 100644 --- a/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt +++ b/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt @@ -8,8 +8,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -59,7 +59,7 @@ fun StoryItem( ) .clickable( interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = ProfileSizes.large / 2), + indication = ripple(bounded = false, radius = ProfileSizes.large / 2), enabled = true, onClickLabel = null, onClick = onClick, From 272e4ed10e5d782e6349bde1e6f38a0014a2557e Mon Sep 17 00:00:00 2001 From: Dipen Pradhan Date: Sat, 6 Jun 2026 22:53:41 -0700 Subject: [PATCH 2/3] feat: upgrade AndroidX Lifecycle to 2.8.7 All lifecycle artifacts (viewmodel-compose, viewmodel-ktx, livedata-ktx, runtime-ktx, viewmodel-savedstate) move together via the shared androidLifecycleGrouped version. Fixes #192 --- .../com/guru/composecookbook/build/dependencies/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt index 94caffad..46711afc 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt @@ -10,7 +10,7 @@ internal object Versions { const val coilCompose = "2.5.0" // Latest stable const val kotlin = "2.1.20" // Kotlin 2.x with the K2 compiler const val coroutines = "1.7.3" // Latest stable - const val androidLifecycleGrouped = "2.7.0" // Latest stable + const val androidLifecycleGrouped = "2.8.7" // Latest stable 2.8.x const val flinger = "1.0.5" // Keeping as is since it's a third-party lib const val paging = "3.2.1" // Latest stable const val lottie = "6.3.0" // Latest stable From d8323ed612f9a9da5cc1d5926339072c251bafc5 Mon Sep 17 00:00:00 2001 From: Dipen Pradhan Date: Sat, 6 Jun 2026 23:04:05 -0700 Subject: [PATCH 3/3] feat: upgrade Navigation Compose to 2.8.9 with type-safe routes - navCompose 2.7.7 -> 2.8.9; add kotlinx-serialization-json 1.8.0 - Apply the Kotlin serialization plugin in gmail and tiktok demos - Gmail: string routes -> @Serializable route objects - TikTok: TikTokScreen becomes a serializable sealed interface; the profile route carries a typed userId argument read via toRoute(), bottom bar selection uses NavDestination.hasRoute Fixes #193 --- buildSrc/build.gradle.kts | 5 ++- .../build/dependencies/Dependencies.kt | 2 + .../build/dependencies/GroupedDependencies.kt | 3 +- .../build/dependencies/Versions.kt | 3 +- demos/gmail/build.gradle.kts | 3 ++ .../gmail/ui/home/GmailHome.kt | 22 ++++++---- demos/tiktok/build.gradle.kts | 3 ++ .../composecookbook/tiktok/TikTokScreen.kt | 40 +++++++++++++++---- .../composecookbook/tiktok/TiktokActivity.kt | 31 +++++++------- 9 files changed, 81 insertions(+), 31 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1f16e886..b7ecc8c3 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -39,7 +39,10 @@ dependencies { // in order to be able to apply "org.jetbrains.kotlin.plugin.compose" in the common scripts implementation("org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.1.20") - // in order to be able to apply "com.google.devtools.ksp" in the common scripts + // in order to be able to apply "org.jetbrains.kotlin.plugin.serialization" in module scripts + implementation("org.jetbrains.kotlin:kotlin-serialization:2.1.20") + + // in order to be able to apply "com.google.devtools.ksp" in the common scripts // (KSP version must match the Kotlin version above) implementation("com.google.devtools.ksp:symbol-processing-gradle-plugin:2.1.20-1.0.31") diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt index 8028a45b..38ae8b4c 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt @@ -67,6 +67,8 @@ object Dependencies { "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" const val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" + const val kotlinxSerializationJson = + "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationJson}" const val viewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidLifecycleGrouped}" const val liveDataKtx = diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt index 8cd6ff23..a61c1ece 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt @@ -30,7 +30,8 @@ internal val composeDebugDependencies = listOf( internal val kotlinDependencies = listOf( Dependencies.kotlinStandardLibraryJdk8, Dependencies.coroutinesCore, - Dependencies.coroutinesAndroid + Dependencies.coroutinesAndroid, + Dependencies.kotlinxSerializationJson ) internal val kotlinTestDependencies = listOf( diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt index 46711afc..c9984d51 100644 --- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt +++ b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt @@ -4,12 +4,13 @@ internal object Versions { const val composeBom = "2024.12.01" // Compose BOM maps ui/material/runtime etc. versions const val activityCompose = "1.8.2" // Latest stable const val pagingCompose = "3.2.1" // Latest stable - const val navCompose = "2.7.7" // Latest stable navigation + const val navCompose = "2.8.9" // Stable navigation with type-safe routes const val constraintLayoutCompose = "1.1.0" // Latest stable const val fontAwesomeCompose = "1.1.0" // Keeping as is since it's a third-party lib const val coilCompose = "2.5.0" // Latest stable const val kotlin = "2.1.20" // Kotlin 2.x with the K2 compiler const val coroutines = "1.7.3" // Latest stable + const val kotlinxSerializationJson = "1.8.0" // For type-safe navigation routes const val androidLifecycleGrouped = "2.8.7" // Latest stable 2.8.x const val flinger = "1.0.5" // Keeping as is since it's a third-party lib const val paging = "3.2.1" // Latest stable diff --git a/demos/gmail/build.gradle.kts b/demos/gmail/build.gradle.kts index 8007c68a..4073b635 100644 --- a/demos/gmail/build.gradle.kts +++ b/demos/gmail/build.gradle.kts @@ -1,9 +1,11 @@ import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies +import com.guru.composecookbook.build.dependencies.addKotlinDependencies plugins { /** See [common-compose-module-configs-script-plugin.gradle.kts] file */ id("common-compose-module-configs-script-plugin") + id("org.jetbrains.kotlin.plugin.serialization") } android { namespace = "com.guru.composecookbook.gmail" } @@ -13,6 +15,7 @@ dependencies { implementation(project(":data")) implementation(project(":theme")) + addKotlinDependencies() addComposeOfficialDependencies() addCoreAndroidUiDependencies() } diff --git a/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt b/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt index c3010872..494fffb6 100644 --- a/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt +++ b/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt @@ -68,16 +68,24 @@ import com.guru.composecookbook.gmail.ui.details.MessageDetailScreen import com.guru.composecookbook.theme.graySurface import com.guru.composecookbook.theme.green500 import kotlin.math.absoluteValue +import kotlinx.serialization.Serializable + +/** Type-safe navigation routes for the Gmail demo. */ +@Serializable object GmailHomeRoute + +@Serializable object GmailDetailRoute + +@Serializable object GmailCreateRoute @Composable fun GmailScreen() { val navController = rememberNavController() - NavHost(navController = navController, startDestination = "home") { - composable("home") { GmailHome(navController = navController) } + NavHost(navController = navController, startDestination = GmailHomeRoute) { + composable { GmailHome(navController = navController) } - composable("detail") { MessageDetailScreen(navController = navController) } + composable { MessageDetailScreen(navController = navController) } - composable("create") { CreateMessageScreen(navController = navController) } + composable { CreateMessageScreen(navController = navController) } } } @@ -157,7 +165,7 @@ fun IconWithBadge(badge: Int, icon: ImageVector, modifier: Modifier = Modifier) fun GmailFloatingActionButton(navController: NavHostController, expandState: Boolean) { FloatingActionButton( - onClick = { navController.navigate("create") }, + onClick = { navController.navigate(GmailCreateRoute) }, modifier = Modifier.padding(16.dp).height(48.dp).widthIn(min = 48.dp), backgroundColor = MaterialTheme.colors.surface, contentColor = MaterialTheme.colors.primary, @@ -241,14 +249,14 @@ fun GmailContent( AnimatedVisibility(visible = visible.value) { Box(modifier = Modifier.background(green500)) { GmailListActionItems(modifier = Modifier.align(Alignment.CenterEnd)) - GmailListItem(it) { navController.navigate("detail") } + GmailListItem(it) { navController.navigate(GmailDetailRoute) } } } } } SearchLayout(searchOffsetY.value, scaffoldState.drawerState, showUserDialog) { - navController.navigate("create") + navController.navigate(GmailCreateRoute) } } } diff --git a/demos/tiktok/build.gradle.kts b/demos/tiktok/build.gradle.kts index 05f45d1c..f8ae2a0b 100644 --- a/demos/tiktok/build.gradle.kts +++ b/demos/tiktok/build.gradle.kts @@ -1,11 +1,13 @@ import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies import com.guru.composecookbook.build.dependencies.addGoogleAndroidDependencies +import com.guru.composecookbook.build.dependencies.addKotlinDependencies import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies plugins { /** See [common-compose-module-configs-script-plugin.gradle.kts] file */ id("common-compose-module-configs-script-plugin") + id("org.jetbrains.kotlin.plugin.serialization") } android { namespace = "com.guru.composecookbook.tiktok" } @@ -16,6 +18,7 @@ dependencies { implementation(project(":data")) implementation(project(":theme")) + addKotlinDependencies() addComposeOfficialDependencies() addCoreAndroidUiDependencies() addThirdPartyUiDependencies() diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt index ae9df718..86af936d 100644 --- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt +++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt @@ -1,15 +1,41 @@ package com.guru.composecookbook.tiktok -sealed class TikTokScreen(val route: String) { - object Home : TikTokScreen("Home") +import kotlinx.serialization.Serializable - object Discover : TikTokScreen("Discover") +/** + * Type-safe navigation routes for the TikTok demo. Each screen is a serializable route; [route] is + * kept as the human readable label shown in the bottom bar. + */ +sealed interface TikTokScreen { + val route: String - object Create : TikTokScreen("Create") + @Serializable + data object Home : TikTokScreen { + override val route = "Home" + } - object Inbox : TikTokScreen("Inbox") + @Serializable + data object Discover : TikTokScreen { + override val route = "Discover" + } - object Me : TikTokScreen("Me") + @Serializable + data object Create : TikTokScreen { + override val route = "Create" + } - object Profile : TikTokScreen("Profile") + @Serializable + data object Inbox : TikTokScreen { + override val route = "Inbox" + } + + @Serializable + data object Me : TikTokScreen { + override val route = "Me" + } + + @Serializable + data class Profile(val userId: String) : TikTokScreen { + override val route = "Profile" + } } diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt index 57438e1d..58319dae 100644 --- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt +++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt @@ -20,11 +20,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute import com.guru.composecookbook.theme.ComposeCookBookTheme import com.guru.composecookbook.theme.tiktokBlack import com.guru.composecookbook.tiktok.components.discovers.DiscoverScreen @@ -60,17 +62,18 @@ fun TiktokAppContent() { @Composable fun TikTokBottomNavigation(navController: NavHostController) { val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.arguments?.getString("route") + val currentDestination = navBackStackEntry?.destination BottomNavigation(backgroundColor = tiktokBlack) { TiktokDemoDataProvider.bottomBarList.forEach { tiktokScreen -> + val isSelected = currentDestination?.hasRoute(tiktokScreen::class) == true BottomNavigationItem( icon = { BottomBarIcon(tiktokScreen) }, - selected = currentRoute == tiktokScreen.route, + selected = isSelected, onClick = { navController.popBackStack(navController.graph.startDestinationId, false) - if (currentRoute != tiktokScreen.route) { - navController.navigate(tiktokScreen.route) + if (!isSelected) { + navController.navigate(tiktokScreen) } }, label = { @@ -91,7 +94,7 @@ fun BottomBarIcon(screen: TikTokScreen) { TikTokScreen.Create -> TiktokCreateIcon() TikTokScreen.Inbox -> Icon(imageVector = Icons.Filled.Inbox, contentDescription = null) TikTokScreen.Me -> Icon(imageVector = Icons.Filled.Person, contentDescription = null) - TikTokScreen.Profile -> { + is TikTokScreen.Profile -> { /* this branch added to satisfy exhaustive when */ } } @@ -101,19 +104,19 @@ fun BottomBarIcon(screen: TikTokScreen) { fun TikTokBodyContent(navController: NavHostController, modifier: Modifier = Modifier) { NavHost( navController = navController, - startDestination = TikTokScreen.Home.route, + startDestination = TikTokScreen.Home, modifier = modifier, ) { - composable(TikTokScreen.Home.route) { + composable { HomeScreen(tiktokInteractionEvents = { handleInteractionEvent(it, navController) }) } - composable(TikTokScreen.Discover.route) { DiscoverScreen() } - composable(TikTokScreen.Create.route) { Text(text = "Create:TODO") } - composable(TikTokScreen.Inbox.route) { Text(text = "Inbox:TODO") } - composable(TikTokScreen.Me.route) { ProfileScreen("10", navController) } + composable { DiscoverScreen() } + composable { Text(text = "Create:TODO") } + composable { Text(text = "Inbox:TODO") } + composable { ProfileScreen("10", navController) } // This navigation is for going to user profile but it should be moved to separate place - composable("${TikTokScreen.Profile.route}/{userId}") { backStackEntry -> - ProfileScreen(backStackEntry.arguments?.getString("userId")!!, navController) + composable { backStackEntry -> + ProfileScreen(backStackEntry.toRoute().userId, navController) } } } @@ -125,7 +128,7 @@ fun handleInteractionEvent( when (tiktokHomeInteractionEvents) { is TiktokHomeInteractionEvents.OpenProfile -> { navController.navigate( - "${TikTokScreen.Profile.route}/${tiktokHomeInteractionEvents.album.id}" + TikTokScreen.Profile(userId = tiktokHomeInteractionEvents.album.id.toString()) ) } else -> {