├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── colors.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
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── dev
│ │ │ │ └── sagar
│ │ │ │ └── composeperformance
│ │ │ │ ├── ui
│ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ ├── lambda
│ │ │ │ ├── LambdaViewModel.kt
│ │ │ │ ├── LambdaExampleMethodReference.kt
│ │ │ │ ├── LambdaExampleRemember.kt
│ │ │ │ └── LambdaExample.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── modifier
│ │ │ │ ├── frequentchange
│ │ │ │ │ ├── ClickableModifier.kt
│ │ │ │ │ └── ClickableExample.kt
│ │ │ │ └── phases
│ │ │ │ │ ├── DrawingExample.kt
│ │ │ │ │ ├── DrawingFix.kt
│ │ │ │ │ ├── LayoutExample.kt
│ │ │ │ │ └── LayoutFix.kt
│ │ │ │ ├── runtimestability
│ │ │ │ ├── RuntimeStabilityFix.kt
│ │ │ │ └── RuntimeStabilityExample.kt
│ │ │ │ ├── module
│ │ │ │ ├── ModuleFixedExample.kt
│ │ │ │ └── ModuleExample.kt
│ │ │ │ ├── collections
│ │ │ │ ├── CollectionExample.kt
│ │ │ │ ├── CollectionSolutionKotlinx.kt
│ │ │ │ └── CollectionSolutionAnnotation.kt
│ │ │ │ ├── viewitem
│ │ │ │ └── InterViewItem.kt
│ │ │ │ └── HomeScreen.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── dev
│ │ │ └── sagar
│ │ │ └── composeperformance
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── dev
│ │ └── sagar
│ │ └── composeperformance
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── domain
├── .gitignore
├── src
│ ├── main
│ │ ├── java
│ │ │ └── dev
│ │ │ │ └── sagar
│ │ │ │ └── domain
│ │ │ │ └── ContactInfo.kt
│ │ └── AndroidManifest.xml
│ └── test
│ │ └── java
│ │ └── dev
│ │ └── sagar
│ │ └── domain
│ │ └── ExampleUnitTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── domainfixed
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── dev
│ │ │ └── sagar
│ │ │ └── domainfixed
│ │ │ └── ContactInfoFixed.kt
│ ├── test
│ │ └── java
│ │ │ └── dev
│ │ │ └── sagar
│ │ │ └── domainfixed
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── dev
│ │ └── sagar
│ │ └── domainfixed
│ │ └── ExampleInstrumentedTest.kt
├── build.gradle.kts
└── proguard-rules.pro
├── .idea
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── migrations.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── misc.xml
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── settings.gradle.kts
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domainfixed/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ComposePerformance
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/domain/src/main/java/dev/sagar/domain/ContactInfo.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.domain
2 |
3 | data class ContactInfo(
4 | val name: String,
5 | val number: Int,
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hellosagar/ComposePerformance/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/domainfixed/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/domainfixed/src/main/java/dev/sagar/domainfixed/ContactInfoFixed.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.domainfixed
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class ContactInfoFixed(
7 | val name: String,
8 | val number: Int,
9 | )
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 18 18:15:27 CEST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.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/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "ComposePerformance"
17 | include(":app")
18 | include(":domain")
19 | include(":domainfixed")
20 |
--------------------------------------------------------------------------------
/domain/src/test/java/dev/sagar/domain/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.domain
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/domainfixed/src/test/java/dev/sagar/domainfixed/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.domainfixed
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/sagar/composeperformance/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/lambda/LambdaViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.lambda
2 |
3 | import androidx.lifecycle.ViewModel
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 |
6 | class LambdaViewModel: ViewModel() {
7 |
8 | private val _keys: MutableStateFlow> = MutableStateFlow(mutableListOf(1))
9 | val keys: MutableStateFlow> = _keys
10 |
11 | fun onClick() {
12 | println("Clicked on Button")
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/domainfixed/src/androidTest/java/dev/sagar/domainfixed/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.domainfixed
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("dev.sagar.domainfixed", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/dev/sagar/composeperformance/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance
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("dev.sagar.composeperformance", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/domain/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
--------------------------------------------------------------------------------
/domainfixed/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2 | plugins {
3 | id("com.android.library")
4 | id("kotlin-android")
5 | }
6 |
7 | android {
8 | namespace = "dev.sagar.domainfixed"
9 | compileSdk = 34
10 |
11 | defaultConfig {
12 | multiDexEnabled = true
13 | }
14 |
15 | buildTypes {
16 | release {
17 | isMinifyEnabled = false
18 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
19 | }
20 | }
21 | compileOptions {
22 | sourceCompatibility = JavaVersion.VERSION_1_8
23 | targetCompatibility = JavaVersion.VERSION_1_8
24 | }
25 | kotlinOptions {
26 | jvmTarget = "1.8"
27 | }
28 | }
29 |
30 | dependencies {
31 |
32 | implementation(libs.core.ktx)
33 | implementation(libs.compose.runtime)
34 | }
35 |
--------------------------------------------------------------------------------
/domainfixed/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
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Surface
9 | import androidx.compose.ui.Modifier
10 | import com.ramcosta.composedestinations.DestinationsNavHost
11 | import dev.sagar.composeperformance.ui.theme.ComposePerformanceTheme
12 |
13 | class MainActivity : ComponentActivity() {
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContent {
18 | ComposePerformanceTheme {
19 | // A surface container using the 'background' color from the theme
20 | Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
21 | DestinationsNavHost(navGraph = NavGraphs.root)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.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 | )
35 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/frequentchange/ClickableModifier.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.frequentchange
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.items
6 | import androidx.compose.foundation.lazy.rememberLazyListState
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateListOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.platform.testTag
13 | import com.ramcosta.composedestinations.annotation.Destination
14 |
15 | /**
16 | * Using the Modifier.clickable() in a remember block which returns the same modifier everytime
17 | * and avoid recompositions.
18 | */
19 |
20 | @Composable
21 | @Destination
22 | fun ClickableFix() {
23 | val state = rememberLazyListState()
24 |
25 | // Solution: Put the Modifier in remember block
26 | val keys = remember {
27 | mutableStateListOf(1)
28 | }
29 | val onClickModifier = remember {
30 | Modifier.clickable { keys.add(keys.size + 1) }.testTag("clickable")
31 | }
32 | LazyColumn(state = state) {
33 | items(
34 | items = keys,
35 | key = { key ->
36 | key
37 | }
38 | ) { key ->
39 | Text(
40 | modifier = onClickModifier, text = "item: $key"
41 | )
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/lambda/LambdaExampleMethodReference.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.lambda
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material3.Button
5 | import androidx.compose.material3.Checkbox
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
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 com.ramcosta.composedestinations.annotation.Destination
14 |
15 | /**
16 | * Example demonstrating how to stabilize the lambda param using the method reference.
17 | *
18 | * Reason why it works:
19 | * - Prevent the creation of a new class, which in turns reference unstable type class.
20 | * - Method references are @Stable functional types
21 | */
22 | @Composable
23 | @Destination
24 | fun LambdaExampleMethodReference() {
25 |
26 | val viewModel: LambdaViewModel = remember {
27 | LambdaViewModel()
28 | }
29 | var isChecked by remember {
30 | mutableStateOf(false)
31 | }
32 | Column(
33 | modifier = Modifier
34 | ) {
35 | Checkbox(checked = isChecked, onCheckedChange = {
36 | isChecked = it
37 | })
38 |
39 | // SOLUTION: Use a Method References
40 | Button(onClick = viewModel::onClick) {
41 | Text(text = "Click Me")
42 | }
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/lambda/LambdaExampleRemember.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.lambda
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material3.Button
5 | import androidx.compose.material3.Checkbox
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
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 com.ramcosta.composedestinations.annotation.Destination
14 |
15 | /**
16 | * Example demonstrating how to stabilize the lambda param using the remember block.
17 | *
18 | * Reason why it works:
19 | * - Remember the lambda instance between recompositions.
20 | * Ensure the exact same instance of the lambda.
21 | *
22 | * P.S: Tip: When remembering a lambda, pass any captured variables as keys
23 | * to remember so that the lambda will be recreated if those variables change.
24 | *
25 | */
26 | @Composable
27 | @Destination
28 | fun LambdaExampleRemember() {
29 |
30 | val viewModel: LambdaViewModel = remember {
31 | LambdaViewModel()
32 | }
33 | val onClick = remember(viewModel) {
34 | { viewModel.onClick() }
35 | }
36 | var isChecked by remember {
37 | mutableStateOf(false)
38 | }
39 | Column(
40 | modifier = Modifier
41 | ) {
42 | Checkbox(checked = isChecked, onCheckedChange = {
43 | isChecked = it
44 | })
45 |
46 | Button(onClick = {
47 | onClick.invoke()
48 | }) {
49 | Text(text = "Click Me")
50 | }
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2 | plugins {
3 | id("com.android.library")
4 | id("kotlin-android")
5 | }
6 |
7 | android {
8 | namespace = "dev.sagar.domain"
9 | compileSdk = 34
10 |
11 | defaultConfig {
12 | multiDexEnabled = true
13 | }
14 |
15 | buildTypes {
16 | release {
17 | isMinifyEnabled = false
18 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
19 | }
20 | }
21 | compileOptions {
22 | sourceCompatibility = JavaVersion.VERSION_1_8
23 | targetCompatibility = JavaVersion.VERSION_1_8
24 | }
25 | kotlinOptions {
26 | jvmTarget = "1.8"
27 | }
28 | }
29 |
30 | dependencies {
31 |
32 | implementation(libs.core.ktx)
33 | implementation(libs.lifecycle.runtime.ktx)
34 | implementation(libs.activity.compose)
35 | implementation(platform(libs.compose.bom))
36 | implementation(libs.ui)
37 | implementation(libs.ui.graphics)
38 | implementation(libs.ui.tooling.preview)
39 | implementation(libs.material3)
40 | testImplementation(libs.junit)
41 | androidTestImplementation(libs.androidx.test.ext.junit)
42 | androidTestImplementation(libs.espresso.core)
43 | androidTestImplementation(platform(libs.compose.bom))
44 | androidTestImplementation(libs.ui.test.junit4)
45 | debugImplementation(libs.ui.tooling)
46 | debugImplementation(libs.ui.test.manifest)
47 | // implementation(project(":domain"))
48 | implementation(project(":domainfixed"))
49 |
50 | // Kotlinx immutable collection
51 | implementation(libs.kotlinx.collections.immutable)
52 |
53 | // Lifeycle Viewmodel compose
54 | implementation(libs.compose.viewmodel)
55 |
56 | // Kotlin coroutines core
57 | implementation(libs.coroutines.core)
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/frequentchange/ClickableExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.frequentchange
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.items
6 | import androidx.compose.foundation.lazy.rememberLazyListState
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateListOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Modifier
12 | import com.ramcosta.composedestinations.annotation.Destination
13 |
14 | /**
15 | * ISSUE: Modifier.Clickable{} is a composed modifier.
16 | *
17 | * Why its not skippable?
18 | * - Composable functions that return values are not skippable.
19 | * - Composed modifier Composable lambda returns a value (Modifier). Due to which on every
20 | * recomposition a new instance of Modifier is returned and thus recompositions of composable.
21 | *
22 | * Checkout the issue tracker for more details.
23 | * - https://issuetracker.google.com/issues/241154852#
24 | * - https://issuetracker.google.com/issues/206021557#comment2
25 | * - https://stackoverflow.com/questions/76059969/why-this-code-is-causing-recomposition-jetpack-compose
26 | *
27 | * SOLUTION:
28 | * 1. Put the Modifier in remember block
29 | * 2. Use the Modifier as singleton
30 | */
31 | @Composable
32 | @Destination
33 | fun ClickableExample() {
34 | val state = rememberLazyListState()
35 | val keys = remember {
36 | mutableStateListOf(1)
37 | }
38 | LazyColumn(state = state) {
39 | items(
40 | items = keys,
41 | key = { key ->
42 | key
43 | }
44 | ) { key ->
45 | Text(
46 | // ISSUE: Clickable using composed modifier.
47 | modifier = Modifier.clickable {
48 | keys.add(keys.size + 1)
49 | }, text = "item: $key"
50 | )
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/runtimestability/RuntimeStabilityFix.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.runtimestability
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import com.ramcosta.composedestinations.annotation.Destination
16 |
17 | @Composable
18 | @Destination
19 | fun RuntimeStabilityFix() {
20 | var isChecked by remember {
21 | mutableStateOf(false)
22 | }
23 | val contact = ContactInfoFix(name = "Sagar", number = 1234567890)
24 | Column(
25 | modifier = Modifier
26 | ) {
27 | Checkbox(checked = isChecked, onCheckedChange = {
28 | isChecked = it
29 | })
30 | Contact(contact = contact)
31 | }
32 | }
33 |
34 | @Composable
35 | private fun Contact(contact: ContactInfoFix) {
36 | Column {
37 | Text(text = contact.name)
38 | Spacer(modifier = Modifier.height(8.dp))
39 | Text(text = "${contact.number}")
40 | }
41 | }
42 |
43 | // Solution: Params are defined as "val" which means it's immutable, and makes the runtime stability stable
44 | private data class ContactInfoFix(
45 | val name: String,
46 | val number: Int,
47 | )
48 |
49 |
50 | /*
51 | Compiler generated anonymous class code from lambda.
52 | -----------------------------------------------------------------------------------
53 | stable class ContactInfoFix {
54 | stable var name: String
55 | stable var number: Int
56 | = Stable
57 | }
58 | -----------------------------------------------------------------------------------
59 | */
60 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/module/ModuleFixedExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.module
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateListOf
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import com.ramcosta.composedestinations.annotation.Destination
17 | import dev.sagar.domainfixed.ContactInfoFixed
18 |
19 | /**
20 | * ISSUE: Composable is taking a data class which is in a separate module.
21 | *
22 | * Things to get the class stable:
23 | * - Add dependency to Compose compiler runtime
24 | * - Mark the class Stable using the annotation(s) here in that case @Immutable
25 | */
26 | @Composable
27 | @Destination
28 | fun ModuleFixedExample() {
29 | var isChecked by remember {
30 | mutableStateOf(false)
31 | }
32 | Column(
33 | modifier = Modifier
34 | ) {
35 | Checkbox(checked = isChecked, onCheckedChange = {
36 | isChecked = it
37 | })
38 | ContactModuleFixed(contact = ContactInfoFixed(name = "Sagar", number = 1234567890))
39 | }
40 | }
41 |
42 | @Composable
43 | private fun ContactModuleFixed(contact: ContactInfoFixed) {
44 | Column {
45 | Text(text = contact.name)
46 | Spacer(modifier = Modifier.height(8.dp))
47 | Text(text = "${contact.number}")
48 | }
49 | }
50 |
51 | /*
52 | Compiler report(s)
53 | -----------------------------------------------------------------------------------
54 | restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ContactModuleFixed(
55 | stable contact: ContactInfoFixed
56 | )
57 | -----------------------------------------------------------------------------------
58 | */
59 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/lambda/LambdaExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.lambda
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material3.Button
5 | import androidx.compose.material3.Checkbox
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
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 com.ramcosta.composedestinations.annotation.Destination
14 |
15 | /**
16 | * ISSUE: Composable is taking a lambda as param and inside that param accessing the unstable
17 | * type class
18 | *
19 | * SOLUTION:
20 | * 1. Use a Method References
21 | * 2. Remembered Lambdas.
22 | */
23 | @Composable
24 | @Destination
25 | fun LambdaExample() {
26 | val viewModel = remember {
27 | LambdaViewModel()
28 | }
29 | var isChecked by remember {
30 | mutableStateOf(false)
31 | }
32 | Column(
33 | modifier = Modifier
34 | ) {
35 | Checkbox(checked = isChecked, onCheckedChange = {
36 | isChecked = it
37 | })
38 |
39 | // ISSUE: Composable is taking a lambda as param and inside that param accessing the unstable
40 | Button(
41 | onClick = {
42 | viewModel.onClick()
43 | }) {
44 | Text(text = "Click Me")
45 | }
46 | }
47 | }
48 |
49 | /*
50 | Compiler report(s)
51 | -----------------------------------------------------------------------------------
52 | unstable class LambdaViewModel {
53 | unstable val _keys: MutableStateFlow>
54 | unstable val keys: MutableStateFlow>
55 | = Unstable
56 | }
57 | -----------------------------------------------------------------------------------
58 |
59 |
60 |
61 | Compiler generated anonymous class code from lambda.
62 | -----------------------------------------------------------------------------------
63 | class OnClickLambda(val viewModel: LambdaViewModel) {
64 | operator fun invoke() {
65 | viewModel.onClick()
66 | }
67 | }
68 | -----------------------------------------------------------------------------------
69 | */
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/runtimestability/RuntimeStabilityExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.runtimestability
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import com.ramcosta.composedestinations.annotation.Destination
16 |
17 | /**
18 | * ISSUE: Composable is taking ContactInfo type as param which is unstable because it's params are
19 | * defined as "var" which means it's mutable, and can be changed at runtime. So, its runtime stability
20 | * is Unstable and thus makes the composable non-skippable.
21 | *
22 | * SOLUTION:
23 | * 1. Make your param as "val" instead of "var
24 | */
25 | @Composable
26 | @Destination
27 | fun RuntimeStabilityExample() {
28 | var isChecked by remember {
29 | mutableStateOf(false)
30 | }
31 | val contact = ContactInfo(name = "Sagar", number = 1234567890)
32 | Column(
33 | modifier = Modifier
34 | ) {
35 | Checkbox(checked = isChecked, onCheckedChange = {
36 | isChecked = it
37 | })
38 | Contact(contact = contact)
39 | }
40 | }
41 |
42 | @Composable
43 | private fun Contact(contact: ContactInfo) {
44 | Column {
45 | Text(text = contact.name)
46 | Spacer(modifier = Modifier.height(8.dp))
47 | Text(text = "${contact.number}")
48 | }
49 | }
50 |
51 | // ISSUE: Params are defined as "var" which means it's mutable, and can be changed at runtime.
52 | private data class ContactInfo(
53 | var name: String,
54 | var number: Int,
55 | )
56 |
57 |
58 | /*
59 | Compiler generated anonymous class code from lambda.
60 | -----------------------------------------------------------------------------------
61 | unstable class ContactInfo {
62 | stable var name: String
63 | stable var number: Int
64 | = Unstable
65 | }
66 | -----------------------------------------------------------------------------------
67 | */
68 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.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 ComposePerformanceTheme(
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 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Article
2 | Links will be mentioned here soon :)
3 |
4 | ### Idea
5 | This project explains how to remove unwanted recompositions and it contains isolated examples that will help you to understand them and then apply the optimizations in your project :)
6 |
7 | ### Where to start
8 | Run the app and it contains a lot of button(s). Now what does that mean? Each item in a column shows a concept, and within that item, there are multiple buttons. The first button will take you to the buggy code and the following button is the possible solution to fix that buggy code.
9 |
10 |
11 |
12 |
13 | ### How to observe recompositions
14 | 1. Run the app
15 | 2. Open a screen with buggy code eg. `CollectionExample`
16 | 3. Open layout inspector,
17 | 4. Observe the recompositions from buggy class
18 | 5. Now open the class with the correct approach
19 | 6. Observe the recompositions
20 | 7. You will see the difference i.e less recompositions in the correct approach, thats its ✨
21 |
22 | Video Sample doing the steps above
23 |
24 |
25 | 
26 |
27 |
28 |
29 |
30 | ### What the package structure
31 | Each concept is a group by a package within which it contains one bug code and others are the possible solutions. In the example below
32 | - `CollectionExample`: Buggy code with unwanted recompositions
33 | - `CollectionExampleSolutionAnnotation`: Solution #1
34 | - `CollectionSolutionKotlinx`: Solution #2
35 |
36 |
37 |
38 | ### What's inside class
39 | In each class, it contains a class header that explains the `ISSUE` and the possible `SOLUTIONS`
40 |
41 |
42 | Special Thanks to [PhilippNowak96](https://github.com/PhilippNowak96), [hi-manshu](https://github.com/hi-manshu), [PatilShreyas](https://github.com/PatilShreyas) and [skydoves](https://github.com/skydoves) for helping me during the process 🙏
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/phases/DrawingExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.phases
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.Spring
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.animation.core.animateFloatAsState
7 | import androidx.compose.animation.core.spring
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.alpha
18 | import androidx.compose.ui.graphics.graphicsLayer
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import com.ramcosta.composedestinations.annotation.Destination
21 | import dev.sagar.composeperformance.ui.theme.ComposePerformanceTheme
22 |
23 | /**
24 | *
25 | * ISSUE: Composable modifier's param is changing frequently, that it is causing the composable
26 | * to recompose.
27 | *
28 | * SOLUTION: Localize the phase to the lowest possible level. I this case, the animationSpec
29 | * only changing the value of the alpha. So we can use the localize it to drawing phase by using the
30 | * graphicsLayer modifier instead of alpha modifier which triggers the recompositions for the composable.
31 | */
32 | @Composable
33 | @Destination
34 | fun DrawingExample() {
35 | var isShown by remember {
36 | mutableStateOf(false)
37 | }
38 | val opacity by animateFloatAsState(if (isShown) 0f else 0.5f, animationSpec(isShown))
39 | Column(
40 | modifier = Modifier
41 | ) {
42 | Button(
43 | onClick = {
44 | isShown = !isShown
45 | }) {
46 | Text(text = "Toggle Text")
47 | }
48 |
49 | // Unoptimized
50 | Text(text = "Hello World! #1", modifier = Modifier.alpha(opacity))
51 | }
52 | }
53 |
54 | private fun animationSpec(animate: Boolean): AnimationSpec = if (animate) {
55 | spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow)
56 | } else {
57 | TweenSpec(durationMillis = 0)
58 | }
59 |
60 | @Preview(showBackground = true)
61 | @Composable
62 | private fun ExampleOnePreview() {
63 | ComposePerformanceTheme {
64 | DrawingExample()
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/phases/DrawingFix.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.phases
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.Spring
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.animation.core.animateFloatAsState
7 | import androidx.compose.animation.core.spring
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.alpha
18 | import androidx.compose.ui.graphics.graphicsLayer
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import com.ramcosta.composedestinations.annotation.Destination
21 | import dev.sagar.composeperformance.ui.theme.ComposePerformanceTheme
22 |
23 | /**
24 | *
25 | * ISSUE: Composable modifier's param is changing frequently, that it is causing the composable
26 | * to recompose.
27 | *
28 | * SOLUTION: Localize the phase to the lowest possible level. I this case, the animationSpec
29 | * only changing the value of the alpha. So we can use the localize it to drawing phase by using the
30 | * graphicsLayer modifier instead of alpha modifier which triggers the recompositions for the composable.
31 | */
32 | @Composable
33 | @Destination
34 | fun DrawingFix() {
35 | var isShown by remember {
36 | mutableStateOf(false)
37 | }
38 | val opacity by animateFloatAsState(if (isShown) 0f else 0.5f, animationSpec(isShown))
39 | Column(
40 | modifier = Modifier
41 | ) {
42 | Button(
43 | onClick = {
44 | isShown = !isShown
45 | }) {
46 | Text(text = "Toggle Text")
47 | }
48 |
49 | // Optimized
50 | Text(text = "Hello World! #2", modifier = Modifier.graphicsLayer { alpha = opacity })
51 | }
52 | }
53 |
54 | private fun animationSpec(animate: Boolean): AnimationSpec = if (animate) {
55 | spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow)
56 | } else {
57 | TweenSpec(durationMillis = 0)
58 | }
59 |
60 | @Preview(showBackground = true)
61 | @Composable
62 | private fun ExampleOnePreview() {
63 | ComposePerformanceTheme {
64 | DrawingExample()
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/module/ModuleExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.module
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateListOf
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import com.ramcosta.composedestinations.annotation.Destination
17 | import dev.sagar.domain.ContactInfo
18 |
19 | /**
20 | * ISSUE: Composable is taking a data class which is in a separate module.
21 | * Thus, compose compiler cannot infer the stability of this class.
22 | * As such it declares it as unstable.
23 | *
24 | * SOLUTION:
25 | * 1. Create UI model in the app module + maps functions()
26 | * 2. Don't pass data class argument if primitives are enough.
27 | * 3. Enable the Compose compiler and make the data classes stable using the Stable annotation(s).
28 | * However it will just be the dependency for the compose runtime and not for Compose-UI.
29 | */
30 | @Composable
31 | @Destination
32 | fun ModuleExample() {
33 | println("ModuleExample")
34 | var isChecked by remember {
35 | mutableStateOf(false)
36 | }
37 | Column(
38 | modifier = Modifier
39 | ) {
40 | Checkbox(checked = isChecked, onCheckedChange = {
41 | isChecked = it
42 | })
43 | ContactModule(contact = ContactInfo(name = "Sagar", number = 1234567890))
44 |
45 | }
46 | }
47 |
48 | // ISSUE: Composable is taking a data class which is in a seperate module. The Compose compiler
49 | // cannot infer the stability of this class. As such it declares it as unstable.
50 | @Composable
51 | private fun ContactModule(contact: ContactInfo) {
52 | Column {
53 | Text(text = contact.name)
54 | Spacer(modifier = Modifier.height(8.dp))
55 | Text(text = "${contact.number}")
56 | }
57 | }
58 |
59 | /*
60 | Compiler report(s)
61 | -----------------------------------------------------------------------------------
62 | restartable scheme("[androidx.compose.ui.UiComposable]") fun ContactModule(
63 | unstable contact: ContactInfo
64 | )
65 | -----------------------------------------------------------------------------------
66 | */
67 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/phases/LayoutExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.phases
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.Spring
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.animation.core.animateIntAsState
7 | import androidx.compose.animation.core.spring
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.offset
10 | import androidx.compose.material3.Button
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import com.ramcosta.composedestinations.annotation.Destination
21 | import dev.sagar.composeperformance.ui.theme.ComposePerformanceTheme
22 |
23 | /**
24 | *
25 | * ISSUE: Composable modifier's param is changing frequently, that it is causing the composable
26 | * to recompose.
27 | *
28 | * SOLUTION: Localize the phase to the lowest possible level. I this case, the Offset changes
29 | * the placement of the composable. So we can use the localize it Layout phase by using the
30 | * offset modifier lambda version where The lambda block we provide to the modifier is invoked during
31 | * the layout phase (specifically, during the layout phase's placement step).
32 | *
33 | */
34 | @Composable
35 | @Destination
36 | fun LayoutExample() {
37 | var isShown by remember {
38 | mutableStateOf(false)
39 | }
40 | val offset by animateIntAsState(if (isShown) 0 else 300, animationSpec(isShown))
41 | Column(
42 | modifier = Modifier
43 | ) {
44 | Button(
45 | onClick = {
46 | isShown = !isShown
47 | }) {
48 | Text(text = "Toggle Text")
49 | }
50 | // Unoptimized
51 | Text(
52 | text = "Hello #1",
53 | modifier = Modifier.offset(x = offset.dp, y = offset.dp)
54 | )
55 | }
56 | }
57 |
58 | private fun animationSpec(animate: Boolean): AnimationSpec = if (animate) {
59 | spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow)
60 | } else {
61 | TweenSpec(durationMillis = 0)
62 | }
63 |
64 | @Preview(showBackground = true)
65 | @Composable
66 | private fun LayoutExamplePreview() {
67 | ComposePerformanceTheme {
68 | LayoutExample()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.3.0-alpha02"
3 | kotlin = "1.8.10"
4 | core-ktx = "1.9.0"
5 | junit = "4.13.2"
6 | androidx-test-ext-junit = "1.1.5"
7 | espresso-core = "3.5.1"
8 | lifecycle-runtime-ktx = "2.6.1"
9 | activity-compose = "1.7.2"
10 | compose-bom = "2023.03.00"
11 | compose-runtime = "1.5.0"
12 | appcompat = "1.6.1"
13 | material = "1.9.0"
14 | kotlinx-collections-immutable = "0.3.4"
15 | compose-viewmodel = "2.6.1"
16 | coroutines-core = "1.7.3"
17 |
18 | [libraries]
19 | core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
20 | junit = { group = "junit", name = "junit", version.ref = "junit" }
21 | androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
22 | espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
23 | lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
24 | activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
25 | compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
26 | ui = { group = "androidx.compose.ui", name = "ui" }
27 | ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
28 | ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
29 | ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
30 | ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
31 | ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
32 | material3 = { group = "androidx.compose.material3", name = "material3" }
33 | appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
34 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
35 | compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "compose-runtime" }
36 | kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" }
37 | compose-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "compose-viewmodel" }
38 | coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines-core" }
39 |
40 | [plugins]
41 | androidApplication = { id = "com.android.application", version.ref = "agp" }
42 | kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
43 |
44 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2 | plugins {
3 | alias(libs.plugins.androidApplication)
4 | alias(libs.plugins.kotlinAndroid)
5 | id("com.google.devtools.ksp") version "1.8.10-1.0.9"
6 | }
7 |
8 | android {
9 | namespace = "dev.sagar.composeperformance"
10 | compileSdk = 34
11 |
12 | defaultConfig {
13 | applicationId = "dev.sagar.composeperformance"
14 | minSdk = 24
15 | targetSdk = 34
16 | versionCode = 1
17 | versionName = "1.0"
18 | multiDexEnabled = true
19 |
20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21 | vectorDrawables {
22 | useSupportLibrary = true
23 | }
24 | }
25 |
26 | buildTypes {
27 | release {
28 | isMinifyEnabled = false
29 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_17
34 | targetCompatibility = JavaVersion.VERSION_17
35 | }
36 | kotlinOptions {
37 | jvmTarget = "17"
38 | }
39 | buildFeatures {
40 | compose = true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion = "1.4.3"
44 | }
45 | packaging {
46 | resources {
47 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | implementation(libs.core.ktx)
55 | implementation(libs.lifecycle.runtime.ktx)
56 | implementation(libs.activity.compose)
57 | implementation(platform(libs.compose.bom))
58 | implementation(libs.ui)
59 | implementation(libs.ui.graphics)
60 | implementation(libs.ui.tooling.preview)
61 | implementation(libs.material3)
62 | testImplementation(libs.junit)
63 | androidTestImplementation(libs.androidx.test.ext.junit)
64 | androidTestImplementation(libs.espresso.core)
65 | androidTestImplementation(platform(libs.compose.bom))
66 | androidTestImplementation(libs.ui.test.junit4)
67 | debugImplementation(libs.ui.tooling)
68 | debugImplementation(libs.ui.test.manifest)
69 | implementation(project(":domain"))
70 | implementation(project(":domainfixed"))
71 |
72 | // Kotlinx immutable collection
73 | implementation(libs.kotlinx.collections.immutable)
74 |
75 | // Lifeycle Viewmodel compose
76 | implementation(libs.compose.viewmodel)
77 |
78 | // Kotlin coroutines core
79 | implementation(libs.coroutines.core)
80 |
81 | // Easy navigation
82 | implementation("io.github.raamcosta.compose-destinations:core:1.9.52")
83 | ksp("io.github.raamcosta.compose-destinations:ksp:1.9.52")
84 |
85 | lintChecks("com.slack.lint.compose:compose-lint-checks:1.2.0")
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/modifier/phases/LayoutFix.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.modifier.phases
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.Spring
5 | import androidx.compose.animation.core.TweenSpec
6 | import androidx.compose.animation.core.animateIntAsState
7 | import androidx.compose.animation.core.spring
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.offset
10 | import androidx.compose.material3.Button
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.IntOffset
20 | import com.ramcosta.composedestinations.annotation.Destination
21 | import dev.sagar.composeperformance.ui.theme.ComposePerformanceTheme
22 |
23 | /**
24 | *
25 | * ISSUE: Composable modifier's param is changing frequently, that it is causing the composable
26 | * to recompose.
27 | *
28 | * SOLUTION: Localize the phase to the lowest possible level. I this case, the Offset changes
29 | * the placement of the composable. So we can use the localize it Layout phase by using the
30 | * offset modifier lambda version where The lambda block we provide to the modifier is invoked during
31 | * the layout phase (specifically, during the layout phase's placement step).
32 | *
33 | */
34 | @Composable
35 | @Destination
36 | fun LayoutFix() {
37 | var isShown by remember {
38 | mutableStateOf(false)
39 | }
40 | val offset by animateIntAsState(if (isShown) 0 else 300, animationSpec(isShown))
41 | Column(
42 | modifier = Modifier
43 | ) {
44 | Button(
45 | onClick = {
46 | isShown = !isShown
47 | }) {
48 | Text(text = "Toggle Text")
49 | }
50 | // Optimized
51 | Text(
52 | text = "Hello #2",
53 | modifier = Modifier.offset {
54 | // The `offsetX` state is read in the placement step
55 | // of the layout phase when the offset is calculated.
56 | // Changes in `offsetX` restart the layout.
57 | IntOffset(offset, offset)
58 | }
59 | )
60 | }
61 | }
62 |
63 | private fun animationSpec(animate: Boolean): AnimationSpec = if (animate) {
64 | spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow)
65 | } else {
66 | TweenSpec(durationMillis = 0)
67 | }
68 |
69 | @Preview(showBackground = true)
70 | @Composable
71 | private fun LayoutExamplePreview() {
72 | ComposePerformanceTheme {
73 | LayoutExample()
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/collections/CollectionExample.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.collections
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateListOf
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import com.ramcosta.composedestinations.annotation.Destination
17 |
18 | /**
19 | * ISSUE: Composable is taking a List (Collection Type) as param which is a interface. The Compose compiler cannot be
20 | * sure of the immutability of this class as it just sees the declared type and as such declares it as unstable as
21 | * implementation could still be mutable.
22 | *
23 | *
24 | * SOLUTION:
25 | * 1. Use a immutable list like kotlinx.collections.ImmutableList.
26 | * 2. Use a data class annotated with stable annotation wrapping a List.
27 | */
28 | @Composable
29 | @Destination
30 | fun CollectionExample() {
31 | var isChecked by remember {
32 | mutableStateOf(false)
33 | }
34 | val contacts: List = remember {
35 | mutableStateListOf(
36 | ContactInfo(name = "Sagar", number = 1234567890),
37 | ContactInfo(name = "Khurana", number = 1234567890),
38 | ContactInfo(name = "Foo", number = 1234567890),
39 | )
40 | }
41 | Column(
42 | modifier = Modifier
43 | ) {
44 | Checkbox(checked = isChecked, onCheckedChange = {
45 | isChecked = it
46 | })
47 | Contacts(contacts = contacts)
48 | }
49 | }
50 |
51 |
52 | // ISSUE: Composable is taking a List (Collection Type) as param which is a interface.
53 | @Composable
54 | private fun Contacts(contacts: List){
55 | contacts.forEach { contact ->
56 | Contact(contact = contact)
57 | }
58 | }
59 |
60 | @Composable
61 | private fun Contact(contact: ContactInfo) {
62 | Column {
63 | Text(text = contact.name)
64 | Spacer(modifier = Modifier.height(8.dp))
65 | Text(text = "${contact.number}")
66 | }
67 | }
68 |
69 | private data class ContactInfo(
70 | val name: String,
71 | val number: Int,
72 | )
73 |
74 |
75 | /*
76 | Compiler report(s)
77 | -----------------------------------------------------------------------------------
78 | restartable scheme("[androidx.compose.ui.UiComposable]") fun Contacts(
79 | unstable contacts: List
80 | )
81 | -----------------------------------------------------------------------------------
82 | */
83 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/collections/CollectionSolutionKotlinx.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.collections
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import com.ramcosta.composedestinations.annotation.Destination
16 | import kotlinx.collections.immutable.ImmutableList
17 | import kotlinx.collections.immutable.persistentListOf
18 |
19 | /**
20 | * Composable demonstrating how to stabilize the param i.e Collection type (List)
21 | * using a immutable list like kotlinx.collections.ImmutableList.
22 | *
23 | *
24 | * Working of PersistentList:
25 | * - Modification operations return new instances of the persistent list
26 | * with the modification applied.
27 | *
28 | *
29 | * Converting the Iterable to ImmutableList, use the following
30 | * - Iterable.toImmutableList(): ImmutableList
31 | * - Iterable.toImmutableSet(): ImmutableSet
32 | *
33 | * Note: Minimum jetpack compose version required: Version 1.2, for the compiler to consider the
34 | * kotlinx.collections.ImmutableList as stable.
35 | */
36 | @Composable
37 | @Destination
38 | fun CollectionSolutionKotlinx() {
39 | var isChecked by remember {
40 | mutableStateOf(false)
41 | }
42 |
43 | val contacts: ImmutableList = remember {
44 | persistentListOf(
45 | ContactInfoTwo(name = "Sagar", number = 1234567890),
46 | ContactInfoTwo(name = "Khurana", number = 1234567890),
47 | ContactInfoTwo(name = "Foo", number = 1234567890),
48 | )
49 | }
50 | Column(
51 | modifier = Modifier
52 | ) {
53 | Checkbox(checked = isChecked, onCheckedChange = {
54 | isChecked = it
55 | })
56 | Contacts(contacts = contacts)
57 | }
58 | }
59 |
60 | /**
61 | * Passing the ImmutableList as param to the composable.
62 | */
63 | @Composable
64 | private fun Contacts(contacts: ImmutableList) {
65 | contacts.forEach { contact ->
66 | Contact(contact = contact)
67 | }
68 | }
69 |
70 | @Composable
71 | private fun Contact(contact: ContactInfoTwo) {
72 | Column {
73 | Text(text = contact.name)
74 | Spacer(modifier = Modifier.height(8.dp))
75 | Text(text = "${contact.number}")
76 | }
77 | }
78 |
79 | private data class ContactInfoTwo(
80 | val name: String,
81 | val number: Int,
82 | )
83 |
84 | /*
85 | Compiler report(s)
86 | -----------------------------------------------------------------------------------
87 | restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun Contacts(
88 | stable contacts: ImmutableList
89 | )
90 | -----------------------------------------------------------------------------------
91 | */
92 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/collections/CollectionSolutionAnnotation.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.collections
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.Immutable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import com.ramcosta.composedestinations.annotation.Destination
17 |
18 | /**
19 | * Composable demonstrating how to stabilize the param i.e Collection type (List, Set, etc)
20 | * using a data class annotated with stable annotation (@Immutable or @Stable) wrapping a List.
21 | *
22 | * Requirements for a type to be considered stable:
23 | * - The result of equals() will always return the same result for the same two instances
24 | * - When a public property of the type changes, Composition will be notified.
25 | * - All public property types are stable as well.
26 | *
27 | * `@Immutable vs @Stable annotation?
28 | *
29 | * `@`Immutable:
30 | * - Properties will never change once constructed.
31 | * - All primitive types (String, Int, Float, etc) are considered immutable and lambdas.
32 | *
33 | *
34 | * `@`Stable:
35 | * - Properties are mutable but compose runtime will be notified whenever anything changes.
36 | * - use State i.e mutableStateOf()
37 | *
38 | *
39 | */
40 | @Composable
41 | @Destination
42 | fun CollectionExampleSolutionAnnotation() {
43 | var isChecked by remember {
44 | mutableStateOf(false)
45 | }
46 | val contacts: ContactInfoOneList = remember {
47 | ContactInfoOneList(
48 | contacts = listOf(
49 | ContactInfoOne(name = "Sagar", number = 1234567890),
50 | ContactInfoOne(name = "Khurana", number = 1234567890),
51 | ContactInfoOne(name = "Foo", number = 1234567890),
52 | )
53 | )
54 | }
55 | Column(
56 | modifier = Modifier
57 | ) {
58 | Checkbox(checked = isChecked, onCheckedChange = {
59 | isChecked = it
60 | })
61 | Contacts(contacts = contacts)
62 | }
63 | }
64 |
65 | @Composable
66 | private fun Contacts(contacts: ContactInfoOneList) {
67 | contacts.contacts.forEach { contact ->
68 | Contact(contact = contact)
69 | }
70 | }
71 | @Immutable
72 | private data class ContactInfoOneList(
73 | val contacts: List
74 | )
75 |
76 | @Composable
77 | private fun Contact(contact: ContactInfoOne) {
78 | Column {
79 | Text(text = contact.name)
80 | Spacer(modifier = Modifier.height(8.dp))
81 | Text(text = "${contact.number}")
82 | }
83 | }
84 |
85 | /**
86 | * Data class annotated with stable annotation wrapping a List.
87 | */
88 |
89 |
90 | private data class ContactInfoOne(
91 | val name: String,
92 | val number: Int,
93 | )
94 | /*
95 | ` Compiler report(s)
96 | -----------------------------------------------------------------------------------
97 | restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun Contacts(
98 | stable contacts: ContactInfoOneList
99 | )
100 | -----------------------------------------------------------------------------------
101 | */
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/viewitem/InterViewItem.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance.viewitem
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.ColumnScope
6 | import androidx.compose.foundation.layout.RowScope
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.wrapContentHeight
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.foundation.lazy.LazyItemScope
12 | import androidx.compose.material3.Button
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.foundation.lazy.items
16 | import androidx.compose.material3.Checkbox
17 | import androidx.compose.runtime.getValue
18 | import androidx.compose.runtime.mutableStateListOf
19 | import androidx.compose.runtime.mutableStateOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.runtime.setValue
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.unit.dp
24 |
25 | /**
26 | * Example showing that interfaces are not unstable by default
27 | */
28 | interface ViewItemScratch {
29 | val identifier: Int
30 | val layoutId: Int
31 |
32 | fun add(): Int
33 | fun minus(): Int
34 |
35 | @SuppressLint("NotConstructor")
36 | @Composable
37 | fun ViewItem(
38 | lazyItemScope: LazyItemScope? = null,
39 | rowScope: RowScope? = null,
40 | columnScope: ColumnScope? = null
41 | ) {
42 | }
43 | }
44 |
45 |
46 | data class TitleViewItemScratch(
47 | val text: String,
48 | val index: Int,
49 | val modifier: Modifier = Modifier,
50 | override val identifier: Int = index
51 | ) : ViewItemScratch {
52 | override val layoutId: Int
53 | get() = index
54 |
55 | override fun add(): Int {
56 | return 2
57 | }
58 |
59 | override fun minus(): Int {
60 | return 1
61 | }
62 |
63 | @Composable
64 | override fun ViewItem(
65 | lazyItemScope: LazyItemScope?, rowScope: RowScope?, columnScope: ColumnScope?
66 | ) {
67 | Text(
68 | modifier = modifier
69 | .fillMaxWidth()
70 | .wrapContentHeight()
71 | .padding(horizontal = 16.dp),
72 | text = text,
73 | )
74 | }
75 | }
76 |
77 |
78 | @Composable
79 | fun WithViewItemScratch(title: TitleViewItemScratch) {
80 | val selectionState: MutableList = remember {
81 | mutableStateListOf()
82 | }
83 | var isChecked by remember {
84 | mutableStateOf(false)
85 | }
86 | val onButtonClick = remember {
87 | {
88 | selectionState.add(
89 | TitleViewItemScratch(
90 | index = selectionState.size + 1,
91 | text = "Hello world #${selectionState.size + 1}",
92 | identifier = selectionState.size + 1,
93 | )
94 | )
95 | }
96 | }
97 | Column {
98 | Checkbox(checked = isChecked, onCheckedChange = {
99 | isChecked = it
100 | })
101 | Button(onClick = {
102 | onButtonClick.invoke()
103 | }) {
104 | Text(text = "Add")
105 | }
106 | val lsit = remember {
107 | listOf("as ", "asd")
108 | }
109 | interComp(viewItem = title)
110 | LazyColumn {
111 | items(
112 | items = selectionState,
113 | key = { viewItem: ViewItemScratch -> viewItem.identifier }
114 | ) { item ->
115 | interComp(viewItem = item)
116 | }
117 | }
118 | }
119 | }
120 |
121 | @Composable
122 | fun interComp(viewItem: ViewItemScratch){
123 | viewItem.ViewItem()
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/sagar/composeperformance/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.sagar.composeperformance
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.Divider
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import com.ramcosta.composedestinations.annotation.Destination
16 | import com.ramcosta.composedestinations.annotation.RootNavGraph
17 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator
18 | import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
19 | import dev.sagar.composeperformance.destinations.ClickableExampleDestination
20 | import dev.sagar.composeperformance.destinations.ClickableFixDestination
21 | import dev.sagar.composeperformance.destinations.CollectionExampleDestination
22 | import dev.sagar.composeperformance.destinations.CollectionExampleSolutionAnnotationDestination
23 | import dev.sagar.composeperformance.destinations.CollectionSolutionKotlinxDestination
24 | import dev.sagar.composeperformance.destinations.DrawingExampleDestination
25 | import dev.sagar.composeperformance.destinations.DrawingFixDestination
26 | import dev.sagar.composeperformance.destinations.LambdaExampleDestination
27 | import dev.sagar.composeperformance.destinations.LambdaExampleMethodReferenceDestination
28 | import dev.sagar.composeperformance.destinations.LambdaExampleRememberDestination
29 | import dev.sagar.composeperformance.destinations.LayoutExampleDestination
30 | import dev.sagar.composeperformance.destinations.LayoutFixDestination
31 | import dev.sagar.composeperformance.destinations.ModuleExampleDestination
32 | import dev.sagar.composeperformance.destinations.ModuleFixedExampleDestination
33 | import dev.sagar.composeperformance.destinations.RuntimeStabilityExampleDestination
34 | import dev.sagar.composeperformance.destinations.RuntimeStabilityFixDestination
35 |
36 | @Composable
37 | @Destination
38 | @RootNavGraph(start = true)
39 | fun HomeScreen(
40 | navigator: DestinationsNavigator
41 | ) {
42 | val list = listOf(
43 | listOf(
44 | Sample(
45 | name = "Collections",
46 | onClick = {
47 | navigator.navigate(CollectionExampleDestination())
48 | }
49 | ),
50 | Sample(
51 | name = "Annotation",
52 | onClick = {
53 | navigator.navigate(CollectionExampleSolutionAnnotationDestination())
54 | }
55 | ),
56 | Sample(
57 | name = "KotlinX",
58 | onClick = {
59 | navigator.navigate(CollectionSolutionKotlinxDestination())
60 | }
61 | ),
62 | ),
63 | listOf(
64 | Sample(
65 | name = "Lambda",
66 | onClick = {
67 | navigator.navigate(LambdaExampleDestination())
68 | }
69 | ),
70 | Sample(
71 | name = "Remember",
72 | onClick = {
73 | navigator.navigate(LambdaExampleRememberDestination())
74 | }
75 | ),
76 | Sample(
77 | name = "Method Reference",
78 | onClick = {
79 | navigator.navigate(LambdaExampleMethodReferenceDestination())
80 | }
81 | ),
82 | ),
83 | listOf(
84 | Sample(
85 | name = "Clickable Modifier",
86 | onClick = {
87 | navigator.navigate(ClickableExampleDestination())
88 | }
89 | ),
90 | Sample(
91 | name = "Clickable Fix",
92 | onClick = {
93 | navigator.navigate(ClickableFixDestination())
94 | }
95 | ),
96 | ),
97 | listOf(
98 | Sample(
99 | name = "Drawing",
100 | onClick = {
101 | navigator.navigate(DrawingExampleDestination())
102 | }
103 | ),
104 | Sample(
105 | name = "Drawing Fix",
106 | onClick = {
107 | navigator.navigate(DrawingFixDestination())
108 | }
109 | ),
110 | ),
111 | listOf(
112 | Sample(
113 | name = "Layout",
114 | onClick = {
115 | navigator.navigate(LayoutExampleDestination())
116 | }
117 | ),
118 | Sample(
119 | name = "Layout Fix",
120 | onClick = {
121 | navigator.navigate(LayoutFixDestination())
122 | }
123 | ),
124 | ),
125 | listOf(
126 | Sample(
127 | name = "Separate Module",
128 | onClick = {
129 | navigator.navigate(ModuleExampleDestination())
130 | }
131 | ),
132 | Sample(
133 | name = "Separate Module Fix",
134 | onClick = {
135 | navigator.navigate(ModuleFixedExampleDestination())
136 | }
137 | ),
138 | ),
139 | listOf(
140 | Sample(
141 | name = "Runtime Stability",
142 | onClick = {
143 | navigator.navigate(RuntimeStabilityExampleDestination())
144 | }
145 | ),
146 | Sample(
147 | name = "Runtime Stability Fix",
148 | onClick = {
149 | navigator.navigate(RuntimeStabilityFixDestination())
150 | }
151 | )
152 | )
153 | )
154 |
155 | Spacer(modifier = Modifier.height(32.dp))
156 | Column {
157 | list.forEach { samples ->
158 | Spacer(modifier = Modifier.height(16.dp))
159 | Row(
160 | modifier = Modifier
161 | .fillMaxWidth(),
162 | horizontalArrangement = Arrangement.SpaceEvenly,
163 | ) {
164 | samples.forEach { sample ->
165 | Button(onClick = sample.onClick) {
166 | Text(text = sample.name)
167 | }
168 | }
169 | }
170 | Spacer(modifier = Modifier.height(16.dp))
171 | Divider()
172 | }
173 | }
174 | }
175 |
176 | private data class Sample(
177 | val name: String,
178 | val onClick: () -> Unit,
179 | )
180 |
181 | @Composable
182 | private fun HomeScreenPreview() {
183 | HomeScreen(EmptyDestinationsNavigator)
184 | }
185 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UPDATE
6 | false
7 | true
8 |
9 | TYPE
10 | METHOD
11 | FIELD
12 |
13 |
14 | PROTECTED
15 | DEFAULT
16 | PUBLIC
17 |
18 |
19 |
20 |
21 |
22 | ^.*(public|protected|private)*.+interface\s+\w+.*
23 | /**\n
24 | * The interface ${name}.\n
25 | <#if element.typeParameters?has_content> * \n
26 | </#if>
27 | <#list element.typeParameters as typeParameter>
28 | * @param <${typeParameter.name}> the type parameter\n
29 | </#list>
30 | */
31 |
32 |
33 | ^.*(public|protected|private)*.+enum\s+\w+.*
34 | /**\n
35 | * The enum ${name}.\n
36 | */
37 |
38 |
39 | ^.*(public|protected|private)*.+class\s+\w+.*
40 | /**\n
41 | * The type ${name}.\n
42 | <#if element.typeParameters?has_content> * \n
43 | </#if>
44 | <#list element.typeParameters as typeParameter>
45 | * @param <${typeParameter.name}> the type parameter\n
46 | </#list>
47 | */
48 |
49 |
50 | .+
51 | /**\n
52 | * The type ${name}.\n
53 | */
54 |
55 |
56 |
57 |
58 | .+
59 | /**\n
60 | * Instantiates a new ${name}.\n
61 | <#if element.parameterList.parameters?has_content>
62 | *\n
63 | </#if>
64 | <#list element.parameterList.parameters as parameter>
65 | * @param ${parameter.name} the ${paramNames[parameter.name]}\n
66 | </#list>
67 | <#if element.throwsList.referenceElements?has_content>
68 | *\n
69 | </#if>
70 | <#list element.throwsList.referenceElements as exception>
71 | * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
72 | </#list>
73 | */
74 |
75 |
76 |
77 |
78 | ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+
79 | /**\n
80 | * Gets ${partName}.\n
81 | <#if element.typeParameters?has_content> * \n
82 | </#if>
83 | <#list element.typeParameters as typeParameter>
84 | * @param <${typeParameter.name}> the type parameter\n
85 | </#list>
86 | <#if element.parameterList.parameters?has_content>
87 | *\n
88 | </#if>
89 | <#list element.parameterList.parameters as parameter>
90 | * @param ${parameter.name} the ${paramNames[parameter.name]}\n
91 | </#list>
92 | <#if isNotVoid>
93 | *\n
94 | * @return the ${partName}\n
95 | </#if>
96 | <#if element.throwsList.referenceElements?has_content>
97 | *\n
98 | </#if>
99 | <#list element.throwsList.referenceElements as exception>
100 | * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
101 | </#list>
102 | */
103 |
104 |
105 | ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+
106 | /**\n
107 | * Sets ${partName}.\n
108 | <#if element.typeParameters?has_content> * \n
109 | </#if>
110 | <#list element.typeParameters as typeParameter>
111 | * @param <${typeParameter.name}> the type parameter\n
112 | </#list>
113 | <#if element.parameterList.parameters?has_content>
114 | *\n
115 | </#if>
116 | <#list element.parameterList.parameters as parameter>
117 | * @param ${parameter.name} the ${paramNames[parameter.name]}\n
118 | </#list>
119 | <#if isNotVoid>
120 | *\n
121 | * @return the ${partName}\n
122 | </#if>
123 | <#if element.throwsList.referenceElements?has_content>
124 | *\n
125 | </#if>
126 | <#list element.throwsList.referenceElements as exception>
127 | * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
128 | </#list>
129 | */
130 |
131 |
132 | ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+
133 | /**\n
134 | * The entry point of application.\n
135 |
136 | <#if element.parameterList.parameters?has_content>
137 | *\n
138 | </#if>
139 | * @param ${element.parameterList.parameters[0].name} the input arguments\n
140 | <#if element.throwsList.referenceElements?has_content>
141 | *\n
142 | </#if>
143 | <#list element.throwsList.referenceElements as exception>
144 | * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
145 | </#list>
146 | */
147 |
148 |
149 | .+
150 | /**\n
151 | * ${name}<#if isNotVoid> ${return}</#if>.\n
152 | <#if element.typeParameters?has_content> * \n
153 | </#if>
154 | <#list element.typeParameters as typeParameter>
155 | * @param <${typeParameter.name}> the type parameter\n
156 | </#list>
157 | <#if element.parameterList.parameters?has_content>
158 | *\n
159 | </#if>
160 | <#list element.parameterList.parameters as parameter>
161 | * @param ${parameter.name} the ${paramNames[parameter.name]}\n
162 | </#list>
163 | <#if isNotVoid>
164 | *\n
165 | * @return the ${return}\n
166 | </#if>
167 | <#if element.throwsList.referenceElements?has_content>
168 | *\n
169 | </#if>
170 | <#list element.throwsList.referenceElements as exception>
171 | * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
172 | </#list>
173 | */
174 |
175 |
176 |
177 |
178 | ^.*(public|protected|private)*.+static.*(\w\s\w)+.+
179 | /**\n
180 | * The constant ${element.getName()}.\n
181 | */
182 |
183 |
184 | ^.*(public|protected|private)*.*(\w\s\w)+.+
185 | /**\n
186 | <#if element.parent.isInterface()>
187 | * The constant ${element.getName()}.\n
188 | <#else>
189 | * The ${name}.\n
190 | </#if> */
191 |
192 |
193 | .+
194 | /**\n
195 | <#if element.parent.isEnum()>
196 | *${name} ${typeName}.\n
197 | <#else>
198 | * The ${name}.\n
199 | </#if>*/
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------