MikkelT
MikkelT

Reputation: 815

Compose TextField's bad composition performance

I'm current implementing Compose Navigation in my app, and I've noticed that any screen with a TextField causes considerable delay between interaction and transition start. Are there any way to optimize the TextField performance?

I made a small StopWatch that starts when I tap the button in Screen1 and ends when Screen2 is composed.

Here's my NavHost

NavHost(
    navController = navController,
    startDestination = NavRoute.Screen1,
) {
    composable<NavRoute.Screen1> {
        Button(onClick = {
            stopWatch.start()
            navController.navigate(NavRoute.QuestionnaireScreen)
        }) { Text(text = "Navigate") }
    }
    composable<NavRoute.Screen2> {
        Screen2()
    }
}

and here's my Screen2 layout

@Composable
fun Screen2() {
    Text(text = "")
    stopWatch.end()
}

Performance:

18:16:24.039  I  tick=43 // Acceptable, practically unnoticable
18:16:25.600  I  tick=37
18:16:26.574  I  tick=36
18:16:27.441  I  tick=34

But if I replace the Text with TextField

@Composable
fun Screen2() {
    TextField(value = "", onValueChange = {})
    stopWatch.end()
}

The performance shoots up for the first composition

18:21:43.679  I  qqq tick=99 // OVER DOUBLE the delay, somewhat noticable
18:21:45.443  I  qqq tick=49
18:21:47.286  I  qqq tick=49
18:21:48.836  I  qqq tick=48

Of course, 100ms is not that bad in isolation, but I'm currently working on an onboarding screen that has multiple TextFields, and multiple screens with multpl TextFields, and the delay becomes VERY noticable.

Example:

@Composable
fun Screen2() {
    TextField(value = "", onValueChange = {})
    TextField(value = "", onValueChange = {})
    TextField(value = "", onValueChange = {})
    TextField(value = "", onValueChange = {})
    TextField(value = "", onValueChange = {})
    TextField(value = "", onValueChange = {})
    stopWatch.end()
}

Perfomance:

18:25:12.158  I  tick=172 // Unacceptable performance, very noticable
18:25:14.710  I  tick=114
18:25:16.683  I  tick=105
18:25:17.864  I  tick=94
18:25:18.967  I  tick=94
18:25:20.055  I  tick=78

StopWatch impl for reference

class StopWatch {
    private var start: Long = 0
    fun start() {
        start = System.currentTimeMillis()
    }

    fun end() {
        val end = System.currentTimeMillis()
        println("tick=${end - start}")
    }
}

val stopWatch = StopWatch()

Any help would be greatly appreciated!

Upvotes: 1

Views: 57

Answers (1)

BenjyTec
BenjyTec

Reputation: 10887

You should not worry too much about performance while running the app in the debug variant. As soon as you build your app in release variant, and minification occurs, performance will improve a lot in Jetpack Compose.

I ran some basic benchmarking on your exact code. Note how the release variant with six TextFields still is faster than the debug variant with a single Text Composable:

Mode Experiment Benchmark
Debug Text 64ms
Debug 1 Textfield 218ms
Debug 6 Textfields 301ms
Release Text 11ms
Release 1 Textfield 40ms
Release 6 Textfields 57ms

If you want to test the release variant in your emulator, you can switch the build variant in Android Studio:

select build variant

select release variant

Please make sure that you have enabled minification in your release variant configuration. Additionally, when building a release variant, you need to provide a key to sign the build. For development, you can use the debug key in your build.gradle file like this:

android {
    compileSdk 34

    //...

    buildTypes {
        release {
            minifyEnabled true  // enable minification
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'  // default minification rules
            signingConfig signingConfigs.debug  // use debug key for signing
        }
    }

    //...
}

You can use baseline profiles to further boost the performance of the app, especially during the app startup.

Upvotes: 1

Related Questions