Slide 1

Slide 1 text

Android Developer Advocate @ Stream Exploring Now in Android Jaewoong

Slide 2

Slide 2 text

skydoves @github_skydoves Android Developer Advocate @ Stream Jaewoong Eum Google Developer Expert

Slide 3

Slide 3 text

Now in Android

Slide 4

Slide 4 text

Now in Android

Slide 5

Slide 5 text

Now in Android Exploring Tech Stacks

Slide 6

Slide 6 text

Tech Stacks ● 100% Compose (activity, foundation, material3, accompanist) ● Navigation (Compose, Hilt) ● WindowManager ● Coil User Interface 01

Slide 7

Slide 7 text

Tech Stacks ● 100% Compose (activity, foundation, material3, accompanist) ● Navigation (Compose, Hilt) ● WindowManager ● Coil User Interface 01 ● DataStore ● Room Database ● Retrofit ● Kotlin Serialization ● Kotlin Coroutines ● WorkManager Business Works 02

Slide 8

Slide 8 text

Tech Stacks ● 100% Compose (activity, foundation, material3, accompanist) ● Navigation (Compose, Hilt) ● WindowManager ● Coil User Interface 01 ● DataStore ● Room Database ● Retrofit ● Kotlin Serialization ● Kotlin Coroutines ● WorkManager Business Works 02 ● Baseline Profiles ● Macrobenchmark ● Hilt ● App Startup Other Jetpacks 03

Slide 9

Slide 9 text

Now in Android Exploring App Architecture

Slide 10

Slide 10 text

Architecture Overview Unidirectional Data Flow ● Higher layers react to changes in lower layers. ● Events flow down. ● Data flows up with data streams. (Kotlin Flows) Unidirectional Event Flow

Slide 11

Slide 11 text

Architecture - UI Layer

Slide 12

Slide 12 text

Architecture - UI Layer

Slide 13

Slide 13 text

Architecture - UI Layer

Slide 14

Slide 14 text

Architecture - UI Layer Modeling UI state ● UI state always represents the underlying app data. ● UI elements handle all possible states. Transforming streams into UI state ● View models receive streams of data as Flows from data layers. ● The single flow is converted to StateFlows, which enables UI elements to read the last state. Processing user interactions (Unidirectional event flow) ● User actions are communicated from UI elements to view models. ● View models execute business logic following the user interactions.

Slide 15

Slide 15 text

Architecture - Data Layer

Slide 16

Slide 16 text

Architecture - Data Layer UI layers must be prepared to react to data changes.

Slide 17

Slide 17 text

Reads are performed from local storage as the single source of truth. Architecture - Data Layer

Slide 18

Slide 18 text

A single source of truth principle is a philosophy of aggregating the data from many systems within an organization to a single location. Architecture - Data Layer

Slide 19

Slide 19 text

Architecture - Data Layer

Slide 20

Slide 20 text

interface Syncable { /** * Synchronizes the local database backing the repository with the network. * Returns if the sync was successful or not. */ suspend fun syncWith(synchronizer: Synchronizer): Boolean } Architecture - Sync

Slide 21

Slide 21 text

interface Syncable { /** * Synchronizes the local database backing the repository with the network. * Returns if the sync was successful or not. */ suspend fun syncWith(synchronizer: Synchronizer): Boolean } Architecture - Sync interface TopicsRepository : Syncable class OfflineFirstTopicsRepository @Inject constructor( .. ) : TopicsRepository { override suspend fun syncWith(synchronizer: Synchronizer): Boolean = synchronizer.changeListSync( ..

Slide 22

Slide 22 text

interface Syncable { /** * Synchronizes the local database backing the repository with the network. * Returns if the sync was successful or not. */ suspend fun syncWith(synchronizer: Synchronizer): Boolean } Architecture - Sync interface TopicsRepository : Syncable class OfflineFirstTopicsRepository @Inject constructor( .. ) : TopicsRepository { override suspend fun syncWith(synchronizer: Synchronizer): Boolean = synchronizer.changeListSync( .. Versioning each model

Slide 23

Slide 23 text

@HiltWorker class SyncWorker @AssistedInject constructor( ... ) : CoroutineWorker(appContext, workerParams), Synchronizer { override suspend fun doWork(): Result = withContext(ioDispatcher) { // First sync the repositories in parallel val syncedSuccessfully = awaitAll( async { topicRepository.syncWith(this) }, async { authorsRepository.syncWith(this) }, async { newsRepository.syncWith(this) }, ).all { it } if (syncedSuccessfully) Result.success() else Result.retry() } Architecture - Sync

Slide 24

Slide 24 text

@HiltWorker class SyncWorker @AssistedInject constructor( ... ) : CoroutineWorker(appContext, workerParams), Synchronizer { override suspend fun doWork(): Result = withContext(ioDispatcher) { // First sync the repositories in parallel val syncedSuccessfully = awaitAll( async { topicRepository.syncWith(this) }, async { authorsRepository.syncWith(this) }, async { newsRepository.syncWith(this) }, ).all { it } if (syncedSuccessfully) Result.success() else Result.retry() } Architecture - Sync Synchronize all repositories

Slide 25

Slide 25 text

class SyncInitializer : Initializer { override fun create(context: Context): Sync { WorkManager.getInstance(context).apply { // Run sync on app startup and ensure only one sync worker runs at any time enqueueUniqueWork( SyncWorkName, ExistingWorkPolicy.REPLACE, SyncWorker.startUpSyncWork() ) } return Sync } override fun dependencies(): List>> = .. } Architecture - Sync Returns OneTimeWorkRequest

Slide 26

Slide 26 text

Architecture - Sync

Slide 27

Slide 27 text

Architecture - ForYou Screen

Slide 28

Slide 28 text

Architecture - ForYou Screen

Slide 29

Slide 29 text

Architecture - ForYou Screen

Slide 30

Slide 30 text

Architecture - ForYou Screen

Slide 31

Slide 31 text

Architecture - ForYou Screen The recommendations and best practices present in this architecture can be applied to a broad spectrum of apps to allow them to scale, improve quality and robustness, and make them easier to test. However, you should treat them as guidelines and adapt them to your requirements as needed.

Slide 32

Slide 32 text

Now in Android Exploring UI Layers with Compose

Slide 33

Slide 33 text

UI Layer - Material You

Slide 34

Slide 34 text

UI Layer - Material You Dynamic color extraction algorithm

Slide 35

Slide 35 text

UI Layer - Material You ● Material Theme ● Material Components ● Dynamic Color Scheme

Slide 36

Slide 36 text

UI Layer - Material You

Slide 37

Slide 37 text

UI Layer - Material You

Slide 38

Slide 38 text

UI Layer - Material You

Slide 39

Slide 39 text

UI Layer - Material You

Slide 40

Slide 40 text

Material Theme (Color Scheme) ● Primary ● onPrimary ● PrimaryContainer ● onPrimaryContainer ● Secondary ● onSecondary ● Tertiary ● onTertiary ● Bacground ● onBackground ● Surface ● onSurface ● SurfaceVariant ● onSurfaceVariant … UI Layer - Theming

Slide 41

Slide 41 text

UI Layer - Theming Dynamic Theme Light Dark Custom Theme Default Theme Android 12 Specific General

Slide 42

Slide 42 text

private val LightDefaultColorScheme = lightColorScheme( primary = Purple40, onPrimary = Color.White, … private val DarkDefaultColorScheme = darkColorScheme( primary = Purple80, onPrimary = Purple20, … private val LightAndroidColorScheme = lightColorScheme( primary = Green40, onPrimary = Color.White, … private val DarkAndroidColorScheme = darkColorScheme( primary = Green80, onPrimary = Green20, …. UI Layer - Theming

Slide 43

Slide 43 text

@Composable fun NiaTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, androidTheme: Boolean = false, content: @Composable() () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } androidTheme && darkTheme -> DarkAndroidColorScheme androidTheme -> LightAndroidColorScheme darkTheme -> DarkDefaultColorScheme else -> LightDefaultColorScheme } UI Layer - Theming

Slide 44

Slide 44 text

@Composable fun NiaTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, androidTheme: Boolean = false, content: @Composable() () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } androidTheme && darkTheme -> DarkAndroidColorScheme androidTheme -> LightAndroidColorScheme darkTheme -> DarkDefaultColorScheme else -> LightDefaultColorScheme } UI Layer - Theming

Slide 45

Slide 45 text

@Composable fun NiaTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = false, androidTheme: Boolean = false, content: @Composable() () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } androidTheme && darkTheme -> DarkAndroidColorScheme androidTheme -> LightAndroidColorScheme darkTheme -> DarkDefaultColorScheme else -> LightDefaultColorScheme } UI Layer - Theming

Slide 46

Slide 46 text

UI Layer - Large Screen The large screens of tablets, foldables, and Chrome OS devices

Slide 47

Slide 47 text

UI Layer - Large Screen https://bit.ly/3Q0HGoS

Slide 48

Slide 48 text

UI Layer - Large Screen

Slide 49

Slide 49 text

dependencies { implementation "androidx.compose.material3:material3-window-size-class:1.0.0-alpha10" Implementation "androidx.window:window:1.0.0" } UI Layer - Large Screen

Slide 50

Slide 50 text

UI Layer - Large Screen

Slide 51

Slide 51 text

UI Layer - Large Screen

Slide 52

Slide 52 text

UI Layer - Large Screen

Slide 53

Slide 53 text

UI Layer - Large Screen

Slide 54

Slide 54 text

UI Layer - Large Screen Navigation Rail

Slide 55

Slide 55 text

UI Layer - Large Screen Compact Medium, Expanded

Slide 56

Slide 56 text

Now in Android Exploring App Performance

Slide 57

Slide 57 text

UI Layer - Compose Phases Which UI to draw Where to place UI. Measurement and placement How it renders

Slide 58

Slide 58 text

UI Layer - Recomposition

Slide 59

Slide 59 text

Composable functions can use the remember API to store an object in memory. A value computed by remember is stored in the Composition during initial composition, and the stored value is returned during recomposition. UI Layer - Remember

Slide 60

Slide 60 text

UI Layer - Remember

Slide 61

Slide 61 text

UI Layer - Remember High costs in every recomposition

Slide 62

Slide 62 text

UI Layer - Remember Recompose when key values changed

Slide 63

Slide 63 text

UI Layer - Lazy Lists Column LazyColumn

Slide 64

Slide 64 text

UI Layer - Lazy Lists

Slide 65

Slide 65 text

UI Layer - Lazy Lists

Slide 66

Slide 66 text

UI Layer - Lazy Lists

Slide 67

Slide 67 text

UI Layer - Lazy Lists

Slide 68

Slide 68 text

Performance - Baseline Profiles It seems to be janky for the first couple of seconds!

Slide 69

Slide 69 text

Performance - Baseline Profiles Compose is a library — it does not participate in system resource sharing.

Slide 70

Slide 70 text

Performance - Baseline Profiles Baseline Profiles Android Runtime

Slide 71

Slide 71 text

Performance - Baseline Profiles

Slide 72

Slide 72 text

Performance - Baseline Profiles

Slide 73

Slide 73 text

Performance - Baseline Profiles Total: 7059 lines (baseline-prof.txt) HSPLandroidx/compose/animation/AndroidFlingSpline$FlingResult;->(FF)V HSPLandroidx/compose/animation/AndroidFlingSpline;->()V HSPLandroidx/compose/animation/AndroidFlingSpline;->()V HSPLandroidx/compose/animation/AndroidFlingSpline;->flingPosition(F)Landroidx/compose/animation/AndroidFlingSpline$FlingResult; HSPLandroidx/compose/animation/CrossfadeKt$$ExternalSyntheticOutline0;->m(Landroidx/compose/runtime/Composer;)V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$1;->(Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/comp ose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function3;II)V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$2;->()V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$2;->()V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$3$1;->(Landroidx/compose/animation/core/Transition;)V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$3$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object; HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$4$1$$ExternalSyntheticOutline0;->m(Landroidx/compose/runtime/Compose r;III)V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$4$1$1$1;->(Landroidx/compose/runtime/State;)V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$4$1$1$1;->invok HSPLandroidx/compose/animation/CrossfadeKt;->Crossfade(Landroidx/compose/animation/core/Transition;Landroidx/compose/ui/Modifier; Landroidx/compose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose /runtime/Composer;II)V ….

Slide 74

Slide 74 text

References ● Now in Android ○ https://github.com/android/nowinandroid ● Exploring App Architecture ○ https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md ● Exploring UI Layer ○ https://material.io/blog/start-building-with-material-you ○ https://getstream.io/blog/material-you-jetpack-compose/ ○ https://getstream.io/blog/jetpack-windowmanager-foldable/ ○ https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes ○ https://youtu.be/EOQB8PTLkpY ○ https://developer.android.com/jetpack/compose/performance ● Performance ○ https://developer.android.com/jetpack/compose/phases ○ https://developer.android.com/topic/performance/baselineprofiles ○ https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview

Slide 75

Slide 75 text

Thank you! Android Developer Advocate @ GetStream Google Developer Expert skydoves [email protected] @github_skydoves Contacts Jaewoong Eum