├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── devexpert │ │ └── composerecompositions │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── devexpert │ │ │ └── composerecompositions │ │ │ ├── MainActivity.kt │ │ │ ├── data │ │ │ └── Movie.kt │ │ │ └── ui │ │ │ ├── screens │ │ │ ├── ColumnKeys.kt │ │ │ ├── DeferRead.kt │ │ │ ├── DerivedState.kt │ │ │ ├── LazyListKeys.kt │ │ │ ├── StableState.kt │ │ │ └── shared │ │ │ │ ├── RefreshAction.kt │ │ │ │ ├── Screen.kt │ │ │ │ ├── TopBar.kt │ │ │ │ └── TopBarAction.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── io │ └── devexpert │ └── composerecompositions │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.kotlin 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Compose Recompositions -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compose Recomposition Demo 2 | 3 | This project demonstrates various issues that can arise with recompositions in Jetpack Compose and how to solve them. 4 | 5 | ## Requirements 6 | 7 | - **Android Studio**: Version 2024.1.1 Patch 2 (Koala) or later. 8 | - **Android SDK**: Compile SDK version 34. 9 | - **Kotlin**: Version 2.0.0. 10 | - **Gradle**: Version 8.5.2. 11 | 12 | ## Setup 13 | 14 | 1. **Clone the repository**: 15 | 16 | 2. **Open the project** in Android Studio. 17 | 18 | 3. **Build the project**: 19 | - Use the `Build` menu in Android Studio or run `./gradlew build` from the command line. 20 | 21 | 4. **Run the app**: 22 | - Use the `Run` button in Android Studio or run `./gradlew installDebug` from the command line. 23 | 24 | ## Examples 25 | 26 | 1. **Unstable Class**: Demonstrates issues with recompositions when using an unstable class. 27 | 2. **Unstable External Class**: Shows how external classes can cause recomposition problems. 28 | 3. **Lambdas**: Explains how lambdas can affect recompositions. 29 | 4. **LazyList**: Discusses recomposition issues with `LazyList`. 30 | 5. **Columns**: Highlights problems with recompositions in columns. 31 | 6. **Continuously Changing State**: Demonstrates issues with state that changes continuously. 32 | 7. **Modifiers**: Shows how modifiers can impact recompositions. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.androidApplication) 3 | alias(libs.plugins.jetbrainsKotlinAndroid) 4 | alias(libs.plugins.compose.compiler) 5 | } 6 | 7 | android { 8 | namespace = "io.devexpert.composerecompositions" 9 | compileSdk = 34 10 | 11 | defaultConfig { 12 | applicationId = "io.devexpert.composerecompositions" 13 | minSdk = 24 14 | targetSdk = 34 15 | versionCode = 1 16 | versionName = "1.0" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary = true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_1_8 35 | targetCompatibility = JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = "1.8" 39 | } 40 | buildFeatures { 41 | compose = true 42 | } 43 | packaging { 44 | resources { 45 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 46 | } 47 | } 48 | } 49 | 50 | dependencies { 51 | 52 | implementation(libs.androidx.core.ktx) 53 | implementation(libs.androidx.lifecycle.runtime.ktx) 54 | implementation(libs.androidx.activity.compose) 55 | implementation(platform(libs.androidx.compose.bom)) 56 | implementation(libs.androidx.ui) 57 | implementation(libs.androidx.ui.graphics) 58 | implementation(libs.androidx.ui.tooling.preview) 59 | implementation(libs.androidx.material3) 60 | implementation(libs.coil.compose) 61 | implementation(libs.androidx.lifecycle.viewmodel.compose) 62 | testImplementation(libs.junit) 63 | androidTestImplementation(libs.androidx.junit) 64 | androidTestImplementation(libs.androidx.espresso.core) 65 | androidTestImplementation(platform(libs.androidx.compose.bom)) 66 | androidTestImplementation(libs.androidx.ui.test.junit4) 67 | debugImplementation(libs.androidx.ui.tooling) 68 | debugImplementation(libs.androidx.ui.test.manifest) 69 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/io/devexpert/composerecompositions/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("io.devexpert.composerecompositions", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import io.devexpert.composerecompositions.ui.screens.ColumnKeys 11 | import io.devexpert.composerecompositions.ui.screens.DeferRead 12 | import io.devexpert.composerecompositions.ui.screens.DerivedState 13 | import io.devexpert.composerecompositions.ui.screens.LazyListKeys 14 | import io.devexpert.composerecompositions.ui.theme.ComposeRecompositionsTheme 15 | 16 | class MainActivity : ComponentActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContent { 20 | //StableState() 21 | //LazyListKeys() 22 | //ColumnKeys() 23 | //DerivedState() 24 | DeferRead() 25 | } 26 | } 27 | } 28 | 29 | @Composable 30 | fun Greeting(name: String, modifier: Modifier = Modifier) { 31 | Text( 32 | text = "Hello $name!", 33 | modifier = modifier 34 | ) 35 | } 36 | 37 | @Preview(showBackground = true) 38 | @Composable 39 | fun GreetingPreview() { 40 | ComposeRecompositionsTheme { 41 | Greeting("Android") 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/data/Movie.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.data 2 | 3 | data class Movie ( 4 | val id: Int, 5 | val title: String, 6 | val description: String, 7 | val image: String 8 | ) 9 | 10 | fun buildMovies(count: Int) = (1..count).map { 11 | Movie( 12 | id = it, 13 | title = "Movie $it", 14 | description = "Description $it", 15 | image = "https://picsum.photos/100/100?id=$it") 16 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/ColumnKeys.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.Scaffold 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.runtime.key 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.runtime.setValue 12 | import androidx.compose.ui.Modifier 13 | import io.devexpert.composerecompositions.data.buildMovies 14 | import io.devexpert.composerecompositions.ui.screens.shared.RefreshAction 15 | import io.devexpert.composerecompositions.ui.screens.shared.Screen 16 | import io.devexpert.composerecompositions.ui.screens.shared.TopBar 17 | 18 | @Composable 19 | fun ColumnKeys() { 20 | Screen { 21 | var movies by remember { mutableStateOf(buildMovies(4)) } 22 | Scaffold( 23 | topBar = { 24 | TopBar("Column Keys") { 25 | RefreshAction { movies = movies.shuffled() } 26 | } 27 | } 28 | ) { padding -> 29 | Column( 30 | modifier = Modifier.padding(padding) 31 | ) { 32 | for (movie in movies) { 33 | key(movie.id) { 34 | MovieItem(movie = movie) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/DeferRead.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.offset 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.shape.CircleShape 10 | import androidx.compose.material3.Scaffold 11 | import androidx.compose.material3.Slider 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.runtime.mutableFloatStateOf 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.runtime.setValue 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.draw.clip 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.unit.IntOffset 21 | import androidx.compose.ui.unit.dp 22 | import io.devexpert.composerecompositions.ui.screens.shared.Screen 23 | import io.devexpert.composerecompositions.ui.screens.shared.TopBar 24 | 25 | @Composable 26 | fun DeferRead() { 27 | Screen { 28 | Scaffold( 29 | topBar = { TopBar("Defer Read") } 30 | ) { padding -> 31 | var sliderValue by remember { mutableFloatStateOf(0f) } 32 | Column( 33 | modifier = Modifier 34 | .padding(padding) 35 | .padding(horizontal = 16.dp) 36 | ) { 37 | Slider(value = sliderValue, onValueChange = { sliderValue = it }) 38 | RedBall(offset = { sliderValue * 200 }) 39 | } 40 | } 41 | } 42 | } 43 | 44 | @Composable 45 | fun RedBall(offset: () -> Float) { 46 | Box( 47 | modifier = Modifier 48 | .offset { IntOffset(offset().toInt(), 0) } 49 | .size(56.dp) 50 | .clip(CircleShape) 51 | .background(Color.Red) 52 | ) 53 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/DerivedState.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens 2 | 3 | import androidx.compose.foundation.lazy.LazyColumn 4 | import androidx.compose.foundation.lazy.items 5 | import androidx.compose.foundation.lazy.rememberLazyListState 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.KeyboardArrowUp 8 | import androidx.compose.material3.Scaffold 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.derivedStateOf 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.runtime.rememberCoroutineScope 14 | import io.devexpert.composerecompositions.data.buildMovies 15 | import io.devexpert.composerecompositions.ui.screens.shared.Screen 16 | import io.devexpert.composerecompositions.ui.screens.shared.TopBar 17 | import io.devexpert.composerecompositions.ui.screens.shared.TopBarAction 18 | import kotlinx.coroutines.launch 19 | 20 | @Composable 21 | fun DerivedState() { 22 | 23 | val scope = rememberCoroutineScope() 24 | val lazyState = rememberLazyListState() 25 | val showScrollToTop by remember { derivedStateOf { lazyState.firstVisibleItemIndex > 0 } } 26 | 27 | Screen { 28 | Scaffold( 29 | topBar = { 30 | TopBar("Derived State") { 31 | if (showScrollToTop) { 32 | TopBarAction( 33 | imageVector = Icons.Default.KeyboardArrowUp, 34 | contentDescription = "Scroll to top", 35 | onClick = { 36 | scope.launch { 37 | lazyState.scrollToItem(0) 38 | } 39 | } 40 | ) 41 | } 42 | } 43 | } 44 | ) { padding -> 45 | LazyColumn( 46 | state = lazyState, 47 | contentPadding = padding 48 | ) { 49 | items(buildMovies(100), key = { it.id }) { 50 | MovieItem(movie = it) 51 | } 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/LazyListKeys.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens 2 | 3 | import androidx.compose.foundation.layout.size 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.items 6 | import androidx.compose.foundation.shape.CircleShape 7 | import androidx.compose.material3.ListItem 8 | import androidx.compose.material3.Scaffold 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.runtime.remember 14 | import androidx.compose.runtime.setValue 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.draw.clip 17 | import androidx.compose.ui.unit.dp 18 | import coil.compose.AsyncImage 19 | import io.devexpert.composerecompositions.data.Movie 20 | import io.devexpert.composerecompositions.data.buildMovies 21 | import io.devexpert.composerecompositions.ui.screens.shared.RefreshAction 22 | import io.devexpert.composerecompositions.ui.screens.shared.Screen 23 | import io.devexpert.composerecompositions.ui.screens.shared.TopBar 24 | 25 | @Composable 26 | fun LazyListKeys() { 27 | Screen { 28 | var movies by remember { mutableStateOf(buildMovies(4)) } 29 | Scaffold( 30 | topBar = { 31 | TopBar("LazyList Keys") { 32 | RefreshAction { movies = movies.shuffled() } 33 | } 34 | }, 35 | ) { padding -> 36 | LazyColumn( 37 | contentPadding = padding 38 | ) { 39 | items(movies, key = { it.id }) { 40 | MovieItem(movie = it) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | @Composable 48 | fun MovieItem(movie: Movie) { 49 | ListItem( 50 | headlineContent = { Text(movie.title) }, 51 | supportingContent = { Text(movie.description) }, 52 | leadingContent = { 53 | AsyncImage( 54 | model = movie.image, 55 | contentDescription = movie.title, 56 | modifier = Modifier 57 | .size(56.dp) 58 | .clip(CircleShape) 59 | ) 60 | } 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/StableState.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Favorite 9 | import androidx.compose.material.icons.filled.FavoriteBorder 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.material3.IconToggleButton 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Scaffold 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.Stable 17 | import androidx.compose.runtime.getValue 18 | import androidx.compose.runtime.mutableStateOf 19 | import androidx.compose.runtime.remember 20 | import androidx.compose.runtime.setValue 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import io.devexpert.composerecompositions.ui.screens.shared.Screen 24 | import io.devexpert.composerecompositions.ui.screens.shared.TopBar 25 | 26 | @Stable 27 | data class Contact(var name: String) 28 | 29 | @Composable 30 | fun StableState() { 31 | Screen { 32 | Scaffold( 33 | topBar = { TopBar("Stable State") } 34 | ) { padding -> 35 | 36 | val contact = remember { Contact("John Doe") } 37 | var selected by remember { mutableStateOf(false) } 38 | 39 | Row( 40 | modifier = Modifier 41 | .fillMaxSize() 42 | .padding(padding), 43 | horizontalArrangement = Arrangement.Center, 44 | verticalAlignment = Alignment.CenterVertically 45 | ) { 46 | ContactDetail(contact) 47 | ToggleHeart( 48 | checked = selected, 49 | onCheckedChange = { selected = it } 50 | ) 51 | } 52 | 53 | } 54 | } 55 | 56 | } 57 | 58 | @Composable 59 | private fun ContactDetail(contact: Contact) { 60 | Text( 61 | text = contact.name, 62 | style = MaterialTheme.typography.headlineSmall 63 | ) 64 | } 65 | 66 | @Composable 67 | fun ToggleHeart(checked: Boolean, onCheckedChange: (Boolean) -> Unit) { 68 | IconToggleButton(checked, onCheckedChange) { 69 | Icon( 70 | imageVector = if (checked) Icons.Default.Favorite else Icons.Default.FavoriteBorder, 71 | contentDescription = "Favorite" 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/shared/RefreshAction.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens.shared 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Refresh 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun RefreshAction(onClick: () -> Unit){ 9 | TopBarAction( 10 | imageVector = Icons.Default.Refresh, 11 | contentDescription = "Refresh", 12 | onClick = onClick 13 | ) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/shared/Screen.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens.shared 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Surface 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import io.devexpert.composerecompositions.ui.theme.ComposeRecompositionsTheme 9 | 10 | @Composable 11 | fun Screen(content: @Composable () -> Unit) { 12 | ComposeRecompositionsTheme { 13 | Surface( 14 | modifier = Modifier.fillMaxSize(), 15 | color = MaterialTheme.colorScheme.background, 16 | content = content 17 | ) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/shared/TopBar.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens.shared 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TopAppBar 7 | import androidx.compose.runtime.Composable 8 | 9 | @OptIn(ExperimentalMaterial3Api::class) 10 | @Composable 11 | fun TopBar( 12 | title: String, 13 | actions: @Composable RowScope.() -> Unit = {} 14 | ) { 15 | TopAppBar( 16 | title = { Text(title) }, 17 | actions = actions 18 | ) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/screens/shared/TopBarAction.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.screens.shared 2 | 3 | import androidx.compose.material3.Icon 4 | import androidx.compose.material3.IconButton 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.vector.ImageVector 7 | 8 | @Composable 9 | fun TopBarAction( 10 | imageVector: ImageVector, 11 | contentDescription: String, 12 | onClick: () -> Unit 13 | ) { 14 | IconButton(onClick = onClick) { 15 | Icon( 16 | imageVector = imageVector, 17 | contentDescription = contentDescription 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | private val DarkColorScheme = darkColorScheme( 19 | primary = Purple80, 20 | secondary = PurpleGrey80, 21 | tertiary = Pink80 22 | ) 23 | 24 | private val LightColorScheme = lightColorScheme( 25 | primary = Purple40, 26 | secondary = PurpleGrey40, 27 | tertiary = Pink40 28 | 29 | /* Other default colors to override 30 | background = Color(0xFFFFFBFE), 31 | surface = Color(0xFFFFFBFE), 32 | onPrimary = Color.White, 33 | onSecondary = Color.White, 34 | onTertiary = Color.White, 35 | onBackground = Color(0xFF1C1B1F), 36 | onSurface = Color(0xFF1C1B1F), 37 | */ 38 | ) 39 | 40 | @Composable 41 | fun ComposeRecompositionsTheme( 42 | darkTheme: Boolean = isSystemInDarkTheme(), 43 | // Dynamic color is available on Android 12+ 44 | dynamicColor: Boolean = true, 45 | content: @Composable () -> Unit 46 | ) { 47 | val colorScheme = when { 48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 49 | val context = LocalContext.current 50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 51 | } 52 | 53 | darkTheme -> DarkColorScheme 54 | else -> LightColorScheme 55 | } 56 | val view = LocalView.current 57 | if (!view.isInEditMode) { 58 | SideEffect { 59 | val window = (view.context as Activity).window 60 | window.statusBarColor = colorScheme.primary.toArgb() 61 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 62 | } 63 | } 64 | 65 | MaterialTheme( 66 | colorScheme = colorScheme, 67 | typography = Typography, 68 | content = content 69 | ) 70 | } -------------------------------------------------------------------------------- /app/src/main/java/io/devexpert/composerecompositions/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package io.devexpert.composerecompositions.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devexpert-io/compose-recompositions/8b644bc223c0cf2ef2d9097e0f8f745cc4ded655/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Compose Recompositions 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |