Slide 1

Slide 1 text

Elevating NordVPN App’s performance

Slide 2

Slide 2 text

Agenda. • Understanding why we need App performance. • How we can approach app performing good? • Integrating performance nitty grittier using Jetpack Compose. • Understanding Baseline profiles and improvements. @hi_man_shoe

Slide 3

Slide 3 text

Basic Implementation. Jetpack Compose @hi_man_shoe

Slide 4

Slide 4 text

@hi_man_shoe

Slide 5

Slide 5 text

But there are lot of Things beyond this right? Jetpack Compose @hi_man_shoe

Slide 6

Slide 6 text

@hi_man_shoe

Slide 7

Slide 7 text

@hi_man_shoe

Slide 8

Slide 8 text

Baseline Profiles The magic is called: @hi_man_shoe

Slide 9

Slide 9 text

But let’s understand what is the it does Baseline Profile MAGIC @hi_man_shoe

Slide 10

Slide 10 text

AoT . @hi_man_shoe Ahead of Time

Slide 11

Slide 11 text

JiT . @hi_man_shoe Just in Time

Slide 12

Slide 12 text

@hi_man_shoe Theory Over!

Slide 13

Slide 13 text

@hi_man_shoe Baseline Profile : 02 File > New > New Module

Slide 14

Slide 14 text

@hi_man_shoe Baseline Profile : 02 AndroidManifest.xml

Slide 15

Slide 15 text

@hi_man_shoe Baseline Profile : 02 *.gradle benchmark { initWith buildTypes.release versionNameSuffix '+benchmark' signingConfig signingConfigs.debug matchingFallbacks = ['release'] debuggable false proguardFiles('baseline-profiles-rules.pro') }

Slide 16

Slide 16 text

@hi_man_shoe Baseline Profile : 02 AndroidManifest.xml benchmark { initWith buildTypes.release versionNameSuffix '+benchmark' signingConfig signingConfigs.debug matchingFallbacks = ['release'] debuggable false proguardFiles('baseline-profiles-rules.pro') }

Slide 17

Slide 17 text

@hi_man_shoe Baseline Profile : 02 baseline-profiles-rules.pro -dontobfuscate

Slide 18

Slide 18 text

@hi_man_shoe Baseline Profiles What will it do? How we do and work with it?

Slide 19

Slide 19 text

@hi_man_shoe We need to setup any critical user flow that we want to improve the speed for! Baseline Profile

Slide 20

Slide 20 text

@hi_man_shoe Base Setup for Generator @RunWith(AndroidJUnit4::class) abstract class BaselineProfileGeneratorScaffold { @get:Rule val rule = BaselineProfileRule() abstract fun MacrobenchmarkScope.invoke() @Test fun profileGenerator() { rule.collectBaselineProfile( packageName = PACKAGE_NAME, maxIterations = 15 ) { startActivityAndWait() invoke() } } }

Slide 21

Slide 21 text

@hi_man_shoe Base Setup for Generator @RunWith(AndroidJUnit4::class) abstract class BaselineProfileGeneratorScaffold { @get:Rule val rule = BaselineProfileRule() abstract fun MacrobenchmarkScope.invoke() @Test fun profileGenerator() { rule.collectBaselineProfile( packageName = PACKAGE_NAME, maxIterations = 15 ) { startActivityAndWait() invoke() } } }

Slide 22

Slide 22 text

@hi_man_shoe Base Setup for Generator @RunWith(AndroidJUnit4::class) abstract class BaselineProfileGeneratorScaffold { @get:Rule val rule = BaselineProfileRule() abstract fun MacrobenchmarkScope.invoke() @Test fun profileGenerator() { rule.collectBaselineProfile( packageName = PACKAGE_NAME, maxIterations = 15 ) { startActivityAndWait() invoke() } } }

Slide 23

Slide 23 text

@hi_man_shoe Baseline Profiles Setup Complete.

Slide 24

Slide 24 text

@hi_man_shoe Startup Baseline Profile Generation class StartupBaselineProfileGenerator : BaselineProfileGeneratorScaffold() { override fun MacrobenchmarkScope.invoke() { startActivityAndWait() } }

Slide 25

Slide 25 text

@hi_man_shoe class StartupBaselineProfileGenerator : BaselineProfileGeneratorScaffold() { override fun MacrobenchmarkScope.invoke() { startActivityAndWait() } } Startup Baseline Profile Generation

Slide 26

Slide 26 text

@hi_man_shoe class StartupBaselineProfileGenerator : BaselineProfileGeneratorScaffold() { override fun MacrobenchmarkScope.invoke() { startActivityAndWait() } } Startup Baseline Profile Generation

Slide 27

Slide 27 text

@hi_man_shoe class StartupBaselineProfileGenerator : BaselineProfileGeneratorScaffold() { override fun MacrobenchmarkScope.invoke() { startActivityAndWait() } } Startup Baseline Profile Generation

Slide 28

Slide 28 text

@hi_man_shoe class AnotherBaselineProfileGenerator : BaselineProfileGeneratorScaffold() { override fun MacrobenchmarkScope.invoke() { with(device) { findObject(….) } } } Another Baseline Profile Generation

Slide 29

Slide 29 text

Execute. • Add Baseline profile grade plugin • ./gradlew :app:generateBaselineProfile • ./gradlew :app:generate{variant}BaselineProfile • Run on Release build always. @hi_man_shoe

Slide 30

Slide 30 text

Device. @hi_man_shoe • Run on Rooted device • Gradle Manage Device with AOSP image • */src/release/generated/baselineProfiles

Slide 31

Slide 31 text

@hi_man_shoe */baseline-prof.txt File name

Slide 32

Slide 32 text

@hi_man_shoe This will be shipped with aab/apk. Result

Slide 33

Slide 33 text

@hi_man_shoe Create Release Branch Trigger CI for Baseline Profile Commit the generated Code Release Release process at NordVPN

Slide 34

Slide 34 text

@hi_man_shoe

Slide 35

Slide 35 text

@hi_man_shoe Compose’s Performance.

Slide 36

Slide 36 text

@hi_man_shoe Recomposition in Compose Draw on init Any change ReDo it again.

Slide 37

Slide 37 text

@hi_man_shoe Always use Stable parameters in Composable function Jetpack Compose

Slide 38

Slide 38 text

Stable/Unstable. • If a composable has stable parameters that have not changed, Recomposition won’t happen • If a composable has unstable parameters it will always recomposes itself when it re-composes the component's parent. @hi_man_shoe

Slide 39

Slide 39 text

@hi_man_shoe So what are Stable Params? Jetpack Compose

Slide 40

Slide 40 text

Stable Params • data class should be with val • We should not pass Collections / Observers • Pass Wrapper classes or Kotlin.Immutable collection or any custom classes. @hi_man_shoe

Slide 41

Slide 41 text

Wrapper Class @hi_man_shoe @Immutable data class CustomerStateCollection( val items: List = emptyList(), )

Slide 42

Slide 42 text

@hi_man_shoe How do we test the unstability for Composables?

Slide 43

Slide 43 text

@hi_man_shoe Layout Inspector

Slide 44

Slide 44 text

@hi_man_shoe Layout Inspector

Slide 45

Slide 45 text

@hi_man_shoe Compose Compiler Reports: A report that tells us how the App’s Compose Code.

Slide 46

Slide 46 text

@hi_man_shoe Compose Compiler Reports: subprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { if (project.findProperty("composeCompilerReports") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } if (project.findProperty("composeCompilerMetrics") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } } } }

Slide 47

Slide 47 text

@hi_man_shoe Compose Compiler Reports: subprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { if (project.findProperty("composeCompilerReports") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } if (project.findProperty("composeCompilerMetrics") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } } } }

Slide 48

Slide 48 text

@hi_man_shoe Compose Compiler Reports: subprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { if (project.findProperty("composeCompilerReports") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } if (project.findProperty("composeCompilerMetrics") == "true") { freeCompilerArgs += [ "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.buildDir.absolutePath + "/compose_compiler" ] } } } }

Slide 49

Slide 49 text

@hi_man_shoe Compose Compiler Reports: ./gradlew assembleRelease -PcomposeCompilerReports=true

Slide 50

Slide 50 text

@hi_man_shoe Compose Compiler Reports: ./gradlew assemble{VARIANT}Release -PcomposeCompilerReports=true

Slide 51

Slide 51 text

@hi_man_shoe Compose Compiler Reports: • module-class.txt : A report on the stability of classes in this • module-composables.txt : A report on how restartable and skippable the composables are. • module-composables.csv : A CSV Version of report.

Slide 52

Slide 52 text

@hi_man_shoe composables.txt restartable scheme("[androidx.compose.ui.UiComposable]") fun ServerList( stable index: Int unstable servers: List stable OnServerClick: Function1 stable modifier: Modifier? = @static Companion )

Slide 53

Slide 53 text

@hi_man_shoe composables.txt restartable scheme("[androidx.compose.ui.UiComposable]") fun ServerList( stable index: Int unstable servers: List stable OnServerClick: Function1 stable modifier: Modifier? = @static Companion )

Slide 54

Slide 54 text

Fix. @hi_man_shoe @Immutable data class ServerCollection( val servers: List = emptyList(), )

Slide 55

Slide 55 text

Or . @hi_man_shoe @Immutable data class ComposeCollection(val data: List)

Slide 56

Slide 56 text

Or . @hi_man_shoe @Immutable data class ComposeCollection(val data: List)

Slide 57

Slide 57 text

@hi_man_shoe How we do @NordVPN Pull Request Run the Compiler check Comment in PR

Slide 58

Slide 58 text

@hi_man_shoe

Slide 59

Slide 59 text

@hi_man_shoe With this, Thank you!