Share
Embed
2022 I/O Extended Korea Android - Exploring Now in Android
2022 I/O Extended Korea Android - Exploring Now in Android.
Jaewoong
June 11, 2022
Transcript
75 slides
-
Tech Stacks • 100% Compose (activity, foundation, material3, accompanist) •
Navigation (Compose, Hilt) • WindowManager • Coil User Interface 01 -
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 -
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 -
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 -
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. -
Architecture - Data Layer UI layers must be prepared to
react to data changes. -
Reads are performed from local storage as the single source
of truth. Architecture - Data Layer -
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 -
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 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( .. -
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 -
@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 -
@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 -
class SyncInitializer : Initializer<Sync> { 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<Class<out Initializer<*>>> = .. } Architecture - Sync Returns OneTimeWorkRequest -
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. -
UI Layer - Material You • Material Theme • Material
Components • Dynamic Color Scheme -
Material Theme (Color Scheme) • Primary • onPrimary • PrimaryContainer
• onPrimaryContainer • Secondary • onSecondary • Tertiary • onTertiary • Bacground • onBackground • Surface • onSurface • SurfaceVariant • onSurfaceVariant … UI Layer - Theming -
UI Layer - Theming Dynamic Theme Light Dark Custom Theme
Default Theme Android 12 Specific General -
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 -
@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 -
@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 -
@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 -
UI Layer - Large Screen The large screens of tablets,
foldables, and Chrome OS devices -
UI Layer - Compose Phases Which UI to draw Where
to place UI. Measurement and placement How it renders -
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 -
Performance - Baseline Profiles It seems to be janky for
the first couple of seconds! -
Performance - Baseline Profiles Compose is a library — it
does not participate in system resource sharing. -
Performance - Baseline Profiles Total: 7059 lines (baseline-prof.txt) HSPLandroidx/compose/animation/AndroidFlingSpline$FlingResult;-><init>(FF)V HSPLandroidx/compose/animation/AndroidFlingSpline;-><clinit>()V
HSPLandroidx/compose/animation/AndroidFlingSpline;-><init>()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;-><init>(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;-><clinit>()V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$2;-><init>()V HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; HSPLandroidx/compose/animation/CrossfadeKt$Crossfade$3$1;-><init>(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;-><init>(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 …. -
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 -
Thank you! Android Developer Advocate @ GetStream Google Developer Expert
skydoves [email protected] @github_skydoves Contacts Jaewoong Eum