SlideShare a Scribd company logo
1 of 28
Download to read offline
Side-effects
in Compose
์ด์ƒ์ˆ˜(vviprogrammer@gmail.com)
Side-effect ๋ž€?
โ€ข ISO/IEC 14882
โ€ข Accessing an object designated by a volatile lvalue,
modifying an object, calling a library I/O function, or
calling a function that does any of those operations are
all side effects, which are changes in the state of the
execution environment.
โ€ข ์‰ฝ๊ฒŒ ๋งํ•ด์„œ
โ€ข ์‹คํ–‰ ์ค‘์— ์–ด๋–ค ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด์„œ
๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜๋Š” ํ–‰์œ„
(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ฑ I/O, ๊ฐ์ฒด ๋ณ€๊ฒฝ ๋“ฑ)
โ€ข X = 3 + 4
Side-effect
โ€ข Composable Scope ์™ธ๋ถ€์—์„œ ์ผ์–ด๋‚˜
๋Š” ์•ฑ์˜ ์ƒํƒœ ๋ณ€ํ™”
โ€ข ์ด์ƒ์ ์ธ Composable ์€
side-effect ์— ๋Œ€ํ•ด์„œ free ํ•ด์•ผ ํ•จ
โ€ข ํ•˜์ง€๋งŒ,
ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์ ์ ˆํ•œ ์‚ฌ์šฉ์ด ํ•„์š”
โ€ข Ex) ์ผํšŒ์„ฑ ์ด๋ฒคํŠธ, ํŠน์ • ์กฐ๊ฑด ์‹œ ๋‹ค๋ฅธ
ํ™”๋ฉด์œผ๋กœ Navigate ๋“ฑ
โ€ข ๋ณดํ†ต Controlled Env. ์—์„œ ์‹คํ–‰
in Compose
Effect API
์ปดํฌ์ฆˆ์—์„œ Side-effect ๊ฐ€ ํ•„์š”ํ•  ๋•Œ
โ€ข ๊ธฐ๋ณธ์ ์ธ Compose ์˜ ์ปจ์…‰์€ side-effect ์— ๋Œ€ํ•ด Free ํ•จ์„ ์ „์ œ
โ€ข Effect API ๋ฅผ ์‚ฌ์šฉ : ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„์—์„œ side-effect ๋ฅผ ์‹คํ–‰
โ€ข Effect ๋Š” Composable ํ•จ์ˆ˜์ด๋ฉฐ UI ๋ฅผ emit ํ•˜์ง€ ์•Š์Œ
https://developer.android.com/jetpack/compose/mental-model
๐Ÿš€LaunchedEffect
โ€ข suspend ํ•จ์ˆ˜๋ฅผ Composable ๋‚ด
์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
โ€ข ์ปดํฌ์ง€์…˜ ์ง„์ž… ์‹œ ์ „๋‹ฌ๋œ ๋ธ”๋ก์„ ์ฝ”๋ฃจ
ํ‹ด์œผ๋กœ ์‹คํ–‰
โ€ข Ex) Scaffold ์•ˆ์—์„œ Snackbar
suspend ํ•จ์ˆ˜๋ฅผ Composable Scope ์—์„œ ์‹คํ–‰ ์‹œํ‚ค๊ธฐ
@Composable
fun MyScreen(
state: UiState<List<Movie>>,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
if (state.hasError) {
LaunchedEffect(scaffoldState.snackbarHostState) {
scaffoldState.snackbarHostState.showSnackbar(
message = "Error message",
actionLabel = "Retry message"
)
}
}
Scaffold(scaffoldState = scaffoldState) {
/* ... */
}
}
rememberCoroutineScope
โ€ข vs `LaunchedEffect`
โ€ข composable ์˜์—ญ์ด ์•„๋‹Œ ๊ณณ์—์„œ ์ฝ”
๋ฃจํ‹ด์„ ์ˆ˜ํ–‰
โ€ข Composition ์„ ๋ฒ—์–ด๋‚˜๋ฉด ์ž๋™์œผ๋กœ
์ทจ์†Œ๋˜๋Š” ์Šค์ฝ”ํ”„
โ€ข Composable ํ•จ์ˆ˜์ด๋ฉฐ, ํ˜ธ์ถœ๋œ
Composition ์ง€์ ์— bind ๋˜๋Š”
CoroutineScope ๋ฅผ ๋ฐ˜ํ™˜
composable ์™ธ๋ถ€์—์„œ์˜ ์ฝ”๋ฃจํ‹ด
@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {
// Composition-aware scope
val scope = rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState) {
Column {
/* ... */
Button(
onClick = {
// Create a new coroutine in the event handler to show a snackbar
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Something happened!")
}
}
) {
Text("Press me")
}
}
}
}
โญ rememberUpdatedState
โ€ข ์ƒํƒœ ๊ฐ’์ด ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ ์žฌ์‹คํ–‰๋˜๋ฉด ์•ˆ
๋˜๋Š” ํ•˜๋‚˜์˜ effect ์•ˆ์—์„œ ๊ฐ’์„ ์ฐธ์กฐ
ํ•˜๋Š” ๋ฐฉ๋ฒ•
โ€ข LaunchedEffect ์˜ key ๋กœ ์ƒ์ˆ˜๊ฐ’
์„ ์„ค์ •ํ•˜์—ฌ ํ˜ธ์ถœ๋œ ๊ณณ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ
๋”ฐ๋ฅด๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ
โ€ข rememberUpdatedState ๋ฅผ ์‚ฌ์šฉ
ํ•˜์—ฌ ์ƒ์œ„ ์ปดํฌ์ €๋ธ”์˜ ์ตœ์‹  ์ƒํƒœ ๊ฐ’์„
์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
์ตœ์‹ ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ๋ฐ›๋Š” State
@Composable
fun LandingScreen(onTimeout: () -> Unit) {
// This will always refer to the latest onTimeout function that
// LandingScreen was recomposed with
val currentOnTimeout by rememberUpdatedState(onTimeout)
// Create an effect that matches the lifecycle of LandingScreen.
// If LandingScreen recomposes, the delay shouldn't start again.
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
currentOnTimeout()
}
/* Landing screen content */
}
DisposableEffect
โ€ข key ์˜ ๋ณ€๊ฒฝ or Composable ์ด
Composition ์„ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ clean-
up ์ด ํ•„์š”ํ•œ side effect.
โ€ข key ๋“ฑ์ด ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ dispose ๋กœ ์ •๋ฆฌ
ํ•ด์ฃผ๋ฉฐ, ๋‹ค์‹œ ์‹คํ–‰
โ€ข Ex) lifecycleOwner ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ์ƒˆ
๋กœ์šด lifecycleOwner ๋กœ ์‹คํ–‰, event
observer ๋“ฑ๋ก/ํ•ด์ œ ๋“ฑ.
โ€ข ๋ฐ˜๋“œ์‹œ, ๋‚ด๋ถ€ ๋งˆ์ง€๋ง‰ ์ ˆ๋กœ `onDispose`
๋ฅผ ์ž‘์„ฑ ํ•ด์•ผํ•จ.
๋ฆฌ์†Œ์Šค Clean-up ์ด ํ•„์š”ํ•œ Effect
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit, // Send the 'started' analytics event
onStop: () -> Unit // Send the 'stopped' analytics event
) {
// Safely update the current lambdas when a new one is provided
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
// If `lifecycleOwner` changes, dispose and reset the effect
DisposableEffect(lifecycleOwner) {
// Create an observer that triggers our remembered callbacks
// for sending analytics events
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
currentOnStart()
} else if (event == Lifecycle.Event.ON_STOP) {
currentOnStop()
}
}
// Add the observer to the lifecycle
lifecycleOwner.lifecycle.addObserver(observer)
// When the effect leaves the Composition, remove the observer
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
/* Home screen content */
}
SideEffect
โ€ข Compose ์ƒํƒœ๋ฅผ non-Compose
์ƒํƒœ์— ๊ฒŒ์‹œ
โ€ข Composition์ด ์™„๋ฃŒ๋˜๊ณ  ํ˜ธ์ถœ๋จ
โ€ข Ex) FirebaseAnalytics ๋ฅผ ํ˜„์žฌ ์‚ฌ์šฉ
์ž์˜ userType ์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜์—ฌ ๋ฉ”
ํƒ€ ๋ฐ์ดํ„ฐ๋กœ ์ฒจ๋ถ€
โ€ข ์ฐธ๊ณ ) return ๊ฐ’์„ ๊ฐ–๋Š”
Composable ์€ ์ผ๋ฐ˜ kotlin ํ•จ์ˆ˜์™€
๋™์ผํ•œ naming ๊ทœ์น™์„ ์‚ฌ์šฉ
Publish Compose State ๐Ÿ‘‰ non-Compose State
@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
val analytics: FirebaseAnalytics = remember {
/* ... */
}
// On every successful composition, update FirebaseAnalytics with
// the userType from the current User, ensuring that future analytics
// events have this metadata attached
SideEffect {
analytics.setUserProperty("userType", user.userType)
}
return analytics
}
produceState
โ€ข Composition ์œผ๋กœ ๊ฐ’์„ ๋ณ€ํ™˜๋œ State ๋กœ
push ํ•˜๋Š” Coroutine ์„ Launch
โ€ข non-Compose State ๋ฅผ Compose State
๋กœ ๋ณ€ํ™˜ ํ•˜๊ณ ์ž ํ•  ๋•Œ (Flow, LiveFata ๋˜๋Š”
RxJava ๋“ฑ์˜ ๊ตฌ๋… ๊ธฐ๋ฐ˜ ์ƒํƒœ๋“ค)
โ€ข ์ƒ์‚ฐ์ž(producer) ๋Š” Composition ์— ์ง„์ž…
ํ•˜๋ฉด ์‹œ์ž‘๋˜๊ณ , Composition ์—์„œ ๋ฒ—์–ด๋‚˜๋Š”
๊ฒฝ์šฐ ์ทจ์†Œ๋œ๋‹ค.
โ€ข ๋ฐ˜ํ™˜๋œ State๋Š” ํ•ฉ์ณ์ง€๋ฏ€๋กœ(Conflates), ๋™์ผ
๊ฐ’์ด ์„ค์ •๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” re-composition ์ด
ํŠธ๋ฆฌ๊ฑฐ๋˜์ง€ ์•Š์Œ
Convert non-Compose State ๐Ÿ‘‰ Compose State // produceState ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•
@Composable
fun loadNetworkImage(
url: String,
imageRepository: ImageRepository
): State<Result<Image>> {
// Creates a State<T> with Result.Loading as initial value
// If either `url` or `imageRepository` changes, the running producer
// will cancel and will be re-launched with the new inputs.
return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
// In a coroutine, can make suspend calls
val image = imageRepository.load(url)
// Update State with either an Error or Success result.
// This will trigger a recomposition where this State is read
value = if (image == null) {
Result.Error
} else {
Result.Success(image)
}
}
}
produceState
โ€ข Flow ๋ฅผ State ๋กœ ๋ณ€๊ฒฝ ์‹œ
produceState ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ
โ€ข LiveData ์˜ ๊ฒฝ์šฐ observer ๋ฅผ
๋ถ™์ด๊ณ  ๋–ผ๋Š” ์ž‘์—…์„ ์œ„ํ•ด
DisposableEffect ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ
Convert non-Compose State ๐Ÿ‘‰ Compose State
@Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
if (context == EmptyCoroutineContext) {
collect { value = it }
} else withContext(context) {
collect { value = it }
}
}
@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember { mutableStateOf(initial) }
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
}
return state
}
โญDerivedStateOf
โ€ข ํŠน์ • ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ State Object ๋กœ ๋ถ€
ํ„ฐ ๊ณ„์‚ฐ ๋˜์—ˆ๊ฑฐ๋‚˜, ํŒŒ์ƒ๋œ ๊ฒฝ์šฐ ์‚ฌ์šฉ
โ€ข ๊ณ„์‚ฐ์— ์‚ฌ์šฉ๋œ ์ƒํƒœ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ณ€๊ฒฝ ๋ 
๋•Œ๋งˆ๋‹ค ์—ฐ์‚ฐ์ด ๋ฐœ์ƒ ํ•จ์„ ๋ณด์žฅ
โ€ข ํ•ต์‹ฌ: UI ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋ ค๋Š” ๊ฒƒ๋ณด๋‹ค
State ๋˜๋Š” key ๊ฐ€ ๋” ๋งŽ์ด ๋ณ€๊ฒฝ ๋  ๋•Œ
โ€ข ๋งˆ์ง€๋ง‰์— ๋” ์ž์„ธํžˆ..!
ํ•˜๋‚˜ ๋˜๋Š” ์—ฌ๋Ÿฌ ์ƒํƒœ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋ณ€ํ™˜
// Calculate high priority tasks only when the todoTasks or highPriorityKeywords
// change, not on every recomposition
val highPriorityTasks by remember(highPriorityKeywords) {
derivedStateOf { todoTasks.filter { it.containsWord(highPriorityKeywords) } }
}
@Composable
fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) {
val todoTasks = remember { mutableStateListOf<String>() }
val highPriorityTasks by remember(highPriorityKeywords, derivedStateOf) {
todoTasks.filter { it.containsWord(highPriorityKeywords) }
}
Box(Modifier.fillMaxSize()) {
LazyColumn {
items(highPriorityTasks) { /* ... */ }
items(todoTasks) { /* ... */ }
}
/* Rest of the UI where users can add elements to the list */
}
}
snapshotFlow
โ€ข State<T> ๊ฐ์ฒด๋ฅผ cold Flow ๋กœ ๋ณ€๊ฒฝ
โ€ข collected ๋˜๋ฉด ๋ธ”๋ก์„ ์‹คํ–‰, ์ฝ์–ด์ง„
State ๊ฐ์ฒด ์ƒํƒœ๋ฅผ emit
โ€ข value ๊ฐ€ ์ด์ „๊ณผ ๋™์ผํ•˜๋ฉด emit ํ•˜์ง€
์•Š์Œ
โ€ข Ex) ๋ชฉ๋ก์˜ ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์„ ์ง€๋‚˜์„œ ์Šคํฌ
๋กค ๋˜๋Š” ๊ฒฝ์šฐ (๋ณ€๊ฒฝ ์‹œ ๋งˆ๋‹ค ํ•œ๋ฒˆ๋งŒ ์ฒ˜๋ฆฌ)
โ€ข ๋˜ํ•œ Flow ์—ฐ์‚ฐ์ž์˜ ์ด์ ์„ ํ™œ์šฉํ•  ์ˆ˜
์žˆ๊ฒŒ๋จ.
Compose State ๐Ÿ‘‰ Flow val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.
fi
rstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.
fi
lter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
Restarting Effect
โ€ข Effect API ํ•จ์ˆ˜ ๋ช‡๋ช‡์€
๊ฐ€๋ณ€ ์ธ์ž๋กœ key ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
โ€ข key ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์‹คํ–‰์ค‘์ธ
effect ๋ฅผ cancle ๋ฐ ์ƒˆ๋กœ์šด effect ๋ฅผ ์‹คํ–‰
โ€ข ์ผ๋ฐ˜์ ์œผ๋กœ effect ๋ธ”๋ก์— ์‚ฌ์šฉ๋˜๋Š”
๊ฐ€๋ณ€ ๋ฐ ๋ถˆ๋ณ€ ๋ณ€์ˆ˜๋Š” effect api์—
ํ‚ค๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
โ€ข ๋˜๋Š” ๋ณ€์ˆ˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด effect ๊ฐ€
์žฌ์‹œ์ž‘๋˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š”
rememberUpdatedState ๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ
์‚ฌ์šฉ ํ•ด์•ผ ํ•จ.
Effect ๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ ์‹œํ‚ค๊ธฐ
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit, // Send the 'started' analytics event
onStop: () -> Unit // Send the 'stopped' analytics event
) {
// These values never change in Composition
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
/* ... */
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
๐Ÿ”‘Constants as keys
โ€ข Effect ์˜ key ๋กœ ์ƒ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด
ํ˜ธ์ถœ ์ง€์ ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ฅด๋„๋ก
ํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ข โš  ํ•˜์ง€๋งŒ LaunchedEffect(true)
๊ฐ™์€ ์ฝ”๋“œ๋Š” while(true) ๋งŒํผ์ด๋‚˜
์‚ฌ์šฉ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•จ.
(๊ผญ ํ•„์š”ํ•œ ์ ํ•ฉํ•œ ๊ฒฝ์šฐ๋งŒ ์‚ฌ์šฉ ํ•˜์ž)
Effect ์˜ key ๋กœ ์ƒ์ˆ˜ ์‚ฌ์šฉ
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
currentOnTimeout()
}
DerivedStateOf
์ข€ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
DerivedStateOf ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ ํ•ฉํ•œ ์‚ฌ๋ก€
โ€ข ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ๊ฒฐ๊ณผ ์‚ฌ์ด์— ๋ณ€๋™์ด ํฐ ๊ฒฝ์šฐ
โ€ข ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์‹œ ์‚ฌ๋ก€
โ€ข ์Šคํฌ๋กค ๊ฐ’์ด ์ž„๊ณ„๊ฐ’์„ ๋„˜์—ˆ๋Š”์ง€ ๊ด€์ฐฐ (scrollPosition > 0)
โ€ข ๋ฆฌ์ŠคํŠธ์˜ ์•„์ดํ…œ์ด ์ž„๊ณ„๊ฐ’ ๋ณด๋‹ค ํฐ์ง€ (item > 0)
โ€ข format ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (username.isValid())
๐Ÿ™‹FAQ
โ€ข Composable ๋‚ด๋ถ€์—์„œ๋Š” ๋ณดํ†ต re-
composition ์‹œ ์œ ์ง€ํ•ด์•ผ ํ•˜๋ฏ€๋กœ
remember ๋กœ ๋ž˜ํ•‘
โ€ข ๋˜๋Š” ViewModel ๋“ฑ ํ™œ์šฉ
DerivedStateOf ๋ฅผ remember ํ•ด์•ผ ํ•˜๋‚˜์š”?
@Composable
fun MyComp() {
// We need to use remember here to survive recomposition
val state = remember { derivedStateOf { โ€ฆ } }
}
class MyViewModel: ViewModel() {
// We don't need remember here (nor could we use it) because the ViewModel is
// outside of Composition.
val state = derivedStateOf { โ€ฆ }
}
๐Ÿ™‹FAQ
โ€ข remember(key) ์™€
derivedStateOf ์˜ ์ฐจ์ด๋Š”
re-composition ์˜ ํšŸ์ˆ˜์™€ ๊ด€๋ จ์žˆ์Œ
โ€ข Case 1
โ€ข ๋ถˆํ•„์š”ํ•œ re-composition์„ ๋ฒ„ํผ๋ง
โ€ข Case 2
โ€ข ์ž…๋ ฅ=์ถœ๋ ฅ=re-composition
remember(key) ์™€ derivedStateOf ์˜ ์ฐจ์ด์ ?
val result = remember(state1, state2) { calculation(state1, state2) }
val result = remember { derivedStateOf { calculation(state1, state2) } }
val isEnabled = lazyListState.firstVisibileItemIndex > 0
val isEnabled = remember {
derivedStateOf { lazyListState.firstVisibleItemIndex > 0 }
}
Case 1
Case 2
val output = remember(input) { expensiveCalculation(input) }
๐Ÿ™‹FAQ
โ€ข DerivedStateOf ๋Š” Compose
State ๊ฐ์ฒด๋ฅผ ์ฝ์„ ๋•Œ๋งŒ ์—…๋ฐ์ดํŠธ ๋จ
โ€ข DerivedStateOf ๊ฐ€ ์ƒ์„ฑ ๋  ๋•Œ ๋‚ด๋ถ€
์—์„œ ์ฝ์€ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ณ€์ˆ˜๋Š” ์ดˆ๊ธฐ ๊ฐ’์„
์บก์ฒ˜ํ•˜๊ฒŒ ๋จ
โ€ข ์—ฐ์‚ฐ ์‹œ ์ด๋Ÿฐ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด
remember์˜ key ๋กœ ์ œ๊ณต ํ•ด์•ผ ํ•จ
remember(key)์™€ DerivedStateOf ๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์•ผํ•  ๋•Œ?
@Composable
fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) {
// There is a bug here
val isEnabled by remember {
derivedStateOf { lazyListState.
fi
rstVisibleItemIndex > threshold }
}
Button(onClick = { }, enabled = isEnabled) {
Text("Scroll to top")
}
}
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
๐Ÿ™‹FAQ
โ€ข remember(key) ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๊ฒฐ
remember(key)์™€ DerivedStateOf ๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์•ผํ•  ๋•Œ?
@Composable
fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) {
val isEnabled by remember(threshold) {
derivedStateOf { lazyListState.
fi
rstVisibleItemIndex > threshold }
}
Button(onClick = { }, enabled = isEnabled) {
Text("Scroll to top")
}
}
https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
๐Ÿ™‹FAQ
โ€ข ๊ฒฐ๊ณผ๋ฅผ ์—ฐ์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๋‹ค์ค‘
์ƒํƒœ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ
โ€ข ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
โ€ข ๊ฒฐ๋ก  :
UI update < state, key update
์‹œ์— ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
์—ฌ๋Ÿฌ ์ƒํƒœ๋ฅผ ํ•ฉ์น˜๊ธฐ ์œ„ํ•ด DerivedStateOf ๋Š” ์ ํ•ฉํ•œ๊ฐ€?
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }
// This derivedStateOf is redundant
val fullName = remember { derivedStateOf { "$firstName $lastName" } }
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }
val fullName = "$firstName $lastName"
References
โ€ข https://developer.android.com/jetpack/compose/side-effects
โ€ข Android UI Development with Jetpack Compose, Thomas Kunneth
โ€ข https://developer.android.com/jetpack/compose/mental-model
โ€ข https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-
derivedstateof-63ce7954c11b
โ€ข https://hyeonk-lab.tistory.com/43
โ€ข https://romannurik.github.io/SlidesCodeHighlighter/
โ€ข https://tourspace.tistory.com/412
โ€ข
๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค
Q&A

More Related Content

Similar to Side Effect in Compose (Android Jetpack)

(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...
(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...
(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...ํƒ‘ํฌ๋ฆฌ์—๋“€(๊ตฌ๋กœ๋””์ง€ํ„ธ๋‹จ์ง€์—ญ3๋ฒˆ์ถœ๊ตฌ 2๋ถ„๊ฑฐ๋ฆฌ)
ย 
20140122 techdays mini ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ
20140122 techdays mini  ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ20140122 techdays mini  ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ
20140122 techdays mini ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ์˜์šฑ ๊น€
ย 
Kotlin with fp
Kotlin with fpKotlin with fp
Kotlin with fpMyeongin Woo
ย 
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)Hankyo
ย 
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)fefe7270
ย 
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉVert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ์ง€์› ์ด
ย 
Swift3 subscript inheritance initialization
Swift3 subscript inheritance initializationSwift3 subscript inheritance initialization
Swift3 subscript inheritance initializationEunjoo Im
ย 
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming waynejo
ย 
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...ํƒ‘ํฌ๋ฆฌ์—๋“€(๊ตฌ๋กœ๋””์ง€ํ„ธ๋‹จ์ง€์—ญ3๋ฒˆ์ถœ๊ตฌ 2๋ถ„๊ฑฐ๋ฆฌ)
ย 
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?NAVER D2
ย 
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVCAnselmKim
ย 
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ดTae-Seong Park
ย 
Unity5 ์‚ฌ์šฉ๊ธฐ
Unity5 ์‚ฌ์šฉ๊ธฐUnity5 ์‚ฌ์šฉ๊ธฐ
Unity5 ์‚ฌ์šฉ๊ธฐ์€์•„ ์ •
ย 
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)fefe7270
ย 
Airflow๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌ
Airflow๋ฅผ ์ด์šฉํ•œ  ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌAirflow๋ฅผ ์ด์šฉํ•œ  ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌ
Airflow๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌYoungHeon (Roy) Kim
ย 
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €Circulus
ย 
Python ์ดํ•ดํ•˜๊ธฐ 20160815
Python ์ดํ•ดํ•˜๊ธฐ 20160815Python ์ดํ•ดํ•˜๊ธฐ 20160815
Python ์ดํ•ดํ•˜๊ธฐ 20160815Yong Joon Moon
ย 
Start IoT with JavaScript - 6.ํ•จ์ˆ˜
Start IoT with JavaScript - 6.ํ•จ์ˆ˜Start IoT with JavaScript - 6.ํ•จ์ˆ˜
Start IoT with JavaScript - 6.ํ•จ์ˆ˜Park Jonggun
ย 
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐHyunsoo Jung
ย 

Similar to Side Effect in Compose (Android Jetpack) (20)

[Codelab 2017] ReactJS ๊ธฐ์ดˆ
[Codelab 2017] ReactJS ๊ธฐ์ดˆ[Codelab 2017] ReactJS ๊ธฐ์ดˆ
[Codelab 2017] ReactJS ๊ธฐ์ดˆ
ย 
(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...
(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...
(๊ตญ๋น„์ง€์›/์‹ค์—…์ž๊ต์œก/์žฌ์ง์ž๊ต์œก/์Šคํ”„๋ง๊ต์œก/๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๊ต์œก์ถ”์ฒœ)#13.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyB...
ย 
20140122 techdays mini ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ
20140122 techdays mini  ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ20140122 techdays mini  ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ
20140122 techdays mini ์•ฑ ๊ฐœ๋ฐœ ์„ธ๋ฏธ๋‚˜(3) - ์„ผ์„œํ™œ์šฉ ์•ฑ ๊ฐœ๋ฐœ
ย 
Kotlin with fp
Kotlin with fpKotlin with fp
Kotlin with fp
ย 
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)
03.์‹คํ–‰ํ™˜๊ฒฝ ๊ต์œก๊ต์žฌ(๋ฐฐ์น˜์ฒ˜๋ฆฌ)
ย 
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ jb)
ย 
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉVert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ
Vert.x ์„ธ๋ฏธ๋‚˜ ์ด์ง€์›_๋ฐฐํฌ์šฉ
ย 
Swift3 subscript inheritance initialization
Swift3 subscript inheritance initializationSwift3 subscript inheritance initialization
Swift3 subscript inheritance initialization
ย 
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming
GDG 2014 - RxJava๋ฅผ ํ™œ์šฉํ•œ Functional Reactive Programming
ย 
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...
#19.์Šคํ”„๋งํ”„๋ ˆ์ž„์›Œํฌ & ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค (Spring Framework, MyBatis)_๊ตญ๋น„์ง€์›ITํ•™์›/์‹ค์—…์ž/์žฌ์ง์žํ™˜๊ธ‰๊ต์œก/์ž๋ฐ”/์Šคํ”„๋ง/...
ย 
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?
[143] Modern C++ ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•ด?
ย 
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC
[์Šคํ”„๋ง ์Šคํ„ฐ๋”” 3์ผ์ฐจ] @MVC
ย 
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด
[React-Native-Seoul] React-Native ์ดˆ์‹ฌ์ž๋ฅผ ์œ„ํ•œ ์‹ค์Šต์œ„์ฃผ์˜ ๊ฐ„๋‹จํ•œ ์†Œ๊ฐœ ๋ฐ ๊ตฌํ˜„๋ฒ• ์•ˆ๋‚ด
ย 
Unity5 ์‚ฌ์šฉ๊ธฐ
Unity5 ์‚ฌ์šฉ๊ธฐUnity5 ์‚ฌ์šฉ๊ธฐ
Unity5 ์‚ฌ์šฉ๊ธฐ
ย 
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)
Surface flingerservice(์„œํ”ผ์Šค ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ์ถœ๋ ฅ ์š”์ฒญ)
ย 
Airflow๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌ
Airflow๋ฅผ ์ด์šฉํ•œ  ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌAirflow๋ฅผ ์ด์šฉํ•œ  ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌ
Airflow๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ Workflow ๊ด€๋ฆฌ
ย 
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €
Startup JavaScript 6 - ํ•จ์ˆ˜, ์Šค์ฝ”ํ”„, ํด๋กœ์ €
ย 
Python ์ดํ•ดํ•˜๊ธฐ 20160815
Python ์ดํ•ดํ•˜๊ธฐ 20160815Python ์ดํ•ดํ•˜๊ธฐ 20160815
Python ์ดํ•ดํ•˜๊ธฐ 20160815
ย 
Start IoT with JavaScript - 6.ํ•จ์ˆ˜
Start IoT with JavaScript - 6.ํ•จ์ˆ˜Start IoT with JavaScript - 6.ํ•จ์ˆ˜
Start IoT with JavaScript - 6.ํ•จ์ˆ˜
ย 
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ
7๊ฐ€์ง€ ๋™์‹œ์„ฑ ๋ชจ๋ธ - 3์žฅ. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ
ย 

Side Effect in Compose (Android Jetpack)

  • 2. Side-effect ๋ž€? โ€ข ISO/IEC 14882 โ€ข Accessing an object designated by a volatile lvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. โ€ข ์‰ฝ๊ฒŒ ๋งํ•ด์„œ โ€ข ์‹คํ–‰ ์ค‘์— ์–ด๋–ค ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด์„œ ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜๋Š” ํ–‰์œ„ (๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ฑ I/O, ๊ฐ์ฒด ๋ณ€๊ฒฝ ๋“ฑ) โ€ข X = 3 + 4
  • 3. Side-effect โ€ข Composable Scope ์™ธ๋ถ€์—์„œ ์ผ์–ด๋‚˜ ๋Š” ์•ฑ์˜ ์ƒํƒœ ๋ณ€ํ™” โ€ข ์ด์ƒ์ ์ธ Composable ์€ side-effect ์— ๋Œ€ํ•ด์„œ free ํ•ด์•ผ ํ•จ โ€ข ํ•˜์ง€๋งŒ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์ ์ ˆํ•œ ์‚ฌ์šฉ์ด ํ•„์š” โ€ข Ex) ์ผํšŒ์„ฑ ์ด๋ฒคํŠธ, ํŠน์ • ์กฐ๊ฑด ์‹œ ๋‹ค๋ฅธ ํ™”๋ฉด์œผ๋กœ Navigate ๋“ฑ โ€ข ๋ณดํ†ต Controlled Env. ์—์„œ ์‹คํ–‰ in Compose
  • 4. Effect API ์ปดํฌ์ฆˆ์—์„œ Side-effect ๊ฐ€ ํ•„์š”ํ•  ๋•Œ โ€ข ๊ธฐ๋ณธ์ ์ธ Compose ์˜ ์ปจ์…‰์€ side-effect ์— ๋Œ€ํ•ด Free ํ•จ์„ ์ „์ œ โ€ข Effect API ๋ฅผ ์‚ฌ์šฉ : ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„์—์„œ side-effect ๋ฅผ ์‹คํ–‰ โ€ข Effect ๋Š” Composable ํ•จ์ˆ˜์ด๋ฉฐ UI ๋ฅผ emit ํ•˜์ง€ ์•Š์Œ https://developer.android.com/jetpack/compose/mental-model
  • 5. ๐Ÿš€LaunchedEffect โ€ข suspend ํ•จ์ˆ˜๋ฅผ Composable ๋‚ด ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ โ€ข ์ปดํฌ์ง€์…˜ ์ง„์ž… ์‹œ ์ „๋‹ฌ๋œ ๋ธ”๋ก์„ ์ฝ”๋ฃจ ํ‹ด์œผ๋กœ ์‹คํ–‰ โ€ข Ex) Scaffold ์•ˆ์—์„œ Snackbar suspend ํ•จ์ˆ˜๋ฅผ Composable Scope ์—์„œ ์‹คํ–‰ ์‹œํ‚ค๊ธฐ @Composable fun MyScreen( state: UiState<List<Movie>>, scaffoldState: ScaffoldState = rememberScaffoldState() ) { if (state.hasError) { LaunchedEffect(scaffoldState.snackbarHostState) { scaffoldState.snackbarHostState.showSnackbar( message = "Error message", actionLabel = "Retry message" ) } } Scaffold(scaffoldState = scaffoldState) { /* ... */ } }
  • 6. rememberCoroutineScope โ€ข vs `LaunchedEffect` โ€ข composable ์˜์—ญ์ด ์•„๋‹Œ ๊ณณ์—์„œ ์ฝ” ๋ฃจํ‹ด์„ ์ˆ˜ํ–‰ โ€ข Composition ์„ ๋ฒ—์–ด๋‚˜๋ฉด ์ž๋™์œผ๋กœ ์ทจ์†Œ๋˜๋Š” ์Šค์ฝ”ํ”„ โ€ข Composable ํ•จ์ˆ˜์ด๋ฉฐ, ํ˜ธ์ถœ๋œ Composition ์ง€์ ์— bind ๋˜๋Š” CoroutineScope ๋ฅผ ๋ฐ˜ํ™˜ composable ์™ธ๋ถ€์—์„œ์˜ ์ฝ”๋ฃจํ‹ด @Composable fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) { // Composition-aware scope val scope = rememberCoroutineScope() Scaffold(scaffoldState = scaffoldState) { Column { /* ... */ Button( onClick = { // Create a new coroutine in the event handler to show a snackbar scope.launch { scaffoldState.snackbarHostState.showSnackbar("Something happened!") } } ) { Text("Press me") } } } }
  • 7. โญ rememberUpdatedState โ€ข ์ƒํƒœ ๊ฐ’์ด ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ ์žฌ์‹คํ–‰๋˜๋ฉด ์•ˆ ๋˜๋Š” ํ•˜๋‚˜์˜ effect ์•ˆ์—์„œ ๊ฐ’์„ ์ฐธ์กฐ ํ•˜๋Š” ๋ฐฉ๋ฒ• โ€ข LaunchedEffect ์˜ key ๋กœ ์ƒ์ˆ˜๊ฐ’ ์„ ์„ค์ •ํ•˜์—ฌ ํ˜ธ์ถœ๋œ ๊ณณ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ฅด๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ โ€ข rememberUpdatedState ๋ฅผ ์‚ฌ์šฉ ํ•˜์—ฌ ์ƒ์œ„ ์ปดํฌ์ €๋ธ”์˜ ์ตœ์‹  ์ƒํƒœ ๊ฐ’์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ ์ตœ์‹ ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ๋ฐ›๋Š” State @Composable fun LandingScreen(onTimeout: () -> Unit) { // This will always refer to the latest onTimeout function that // LandingScreen was recomposed with val currentOnTimeout by rememberUpdatedState(onTimeout) // Create an effect that matches the lifecycle of LandingScreen. // If LandingScreen recomposes, the delay shouldn't start again. LaunchedEffect(true) { delay(SplashWaitTimeMillis) currentOnTimeout() } /* Landing screen content */ }
  • 8. DisposableEffect โ€ข key ์˜ ๋ณ€๊ฒฝ or Composable ์ด Composition ์„ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ clean- up ์ด ํ•„์š”ํ•œ side effect. โ€ข key ๋“ฑ์ด ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ dispose ๋กœ ์ •๋ฆฌ ํ•ด์ฃผ๋ฉฐ, ๋‹ค์‹œ ์‹คํ–‰ โ€ข Ex) lifecycleOwner ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ์ƒˆ ๋กœ์šด lifecycleOwner ๋กœ ์‹คํ–‰, event observer ๋“ฑ๋ก/ํ•ด์ œ ๋“ฑ. โ€ข ๋ฐ˜๋“œ์‹œ, ๋‚ด๋ถ€ ๋งˆ์ง€๋ง‰ ์ ˆ๋กœ `onDispose` ๋ฅผ ์ž‘์„ฑ ํ•ด์•ผํ•จ. ๋ฆฌ์†Œ์Šค Clean-up ์ด ํ•„์š”ํ•œ Effect @Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // Safely update the current lambdas when a new one is provided val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) // If `lifecycleOwner` changes, dispose and reset the effect DisposableEffect(lifecycleOwner) { // Create an observer that triggers our remembered callbacks // for sending analytics events val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } // Add the observer to the lifecycle lifecycleOwner.lifecycle.addObserver(observer) // When the effect leaves the Composition, remove the observer onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* Home screen content */ }
  • 9. SideEffect โ€ข Compose ์ƒํƒœ๋ฅผ non-Compose ์ƒํƒœ์— ๊ฒŒ์‹œ โ€ข Composition์ด ์™„๋ฃŒ๋˜๊ณ  ํ˜ธ์ถœ๋จ โ€ข Ex) FirebaseAnalytics ๋ฅผ ํ˜„์žฌ ์‚ฌ์šฉ ์ž์˜ userType ์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜์—ฌ ๋ฉ” ํƒ€ ๋ฐ์ดํ„ฐ๋กœ ์ฒจ๋ถ€ โ€ข ์ฐธ๊ณ ) return ๊ฐ’์„ ๊ฐ–๋Š” Composable ์€ ์ผ๋ฐ˜ kotlin ํ•จ์ˆ˜์™€ ๋™์ผํ•œ naming ๊ทœ์น™์„ ์‚ฌ์šฉ Publish Compose State ๐Ÿ‘‰ non-Compose State @Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { /* ... */ } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
  • 10. produceState โ€ข Composition ์œผ๋กœ ๊ฐ’์„ ๋ณ€ํ™˜๋œ State ๋กœ push ํ•˜๋Š” Coroutine ์„ Launch โ€ข non-Compose State ๋ฅผ Compose State ๋กœ ๋ณ€ํ™˜ ํ•˜๊ณ ์ž ํ•  ๋•Œ (Flow, LiveFata ๋˜๋Š” RxJava ๋“ฑ์˜ ๊ตฌ๋… ๊ธฐ๋ฐ˜ ์ƒํƒœ๋“ค) โ€ข ์ƒ์‚ฐ์ž(producer) ๋Š” Composition ์— ์ง„์ž… ํ•˜๋ฉด ์‹œ์ž‘๋˜๊ณ , Composition ์—์„œ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ ์ทจ์†Œ๋œ๋‹ค. โ€ข ๋ฐ˜ํ™˜๋œ State๋Š” ํ•ฉ์ณ์ง€๋ฏ€๋กœ(Conflates), ๋™์ผ ๊ฐ’์ด ์„ค์ •๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” re-composition ์ด ํŠธ๋ฆฌ๊ฑฐ๋˜์ง€ ์•Š์Œ Convert non-Compose State ๐Ÿ‘‰ Compose State // produceState ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ• @Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository ): State<Result<Image>> { // Creates a State<T> with Result.Loading as initial value // If either `url` or `imageRepository` changes, the running producer // will cancel and will be re-launched with the new inputs. return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { // In a coroutine, can make suspend calls val image = imageRepository.load(url) // Update State with either an Error or Success result. // This will trigger a recomposition where this State is read value = if (image == null) { Result.Error } else { Result.Success(image) } } }
  • 11. produceState โ€ข Flow ๋ฅผ State ๋กœ ๋ณ€๊ฒฝ ์‹œ produceState ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ โ€ข LiveData ์˜ ๊ฒฝ์šฐ observer ๋ฅผ ๋ถ™์ด๊ณ  ๋–ผ๋Š” ์ž‘์—…์„ ์œ„ํ•ด DisposableEffect ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ Convert non-Compose State ๐Ÿ‘‰ Compose State @Composable fun <T : R, R> Flow<T>.collectAsState( initial: R, context: CoroutineContext = EmptyCoroutineContext ): State<R> = produceState(initial, this, context) { if (context == EmptyCoroutineContext) { collect { value = it } } else withContext(context) { collect { value = it } } } @Composable fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> { val lifecycleOwner = LocalLifecycleOwner.current val state = remember { mutableStateOf(initial) } DisposableEffect(this, lifecycleOwner) { val observer = Observer<T> { state.value = it } observe(lifecycleOwner, observer) onDispose { removeObserver(observer) } } return state }
  • 12. โญDerivedStateOf โ€ข ํŠน์ • ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ State Object ๋กœ ๋ถ€ ํ„ฐ ๊ณ„์‚ฐ ๋˜์—ˆ๊ฑฐ๋‚˜, ํŒŒ์ƒ๋œ ๊ฒฝ์šฐ ์‚ฌ์šฉ โ€ข ๊ณ„์‚ฐ์— ์‚ฌ์šฉ๋œ ์ƒํƒœ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ณ€๊ฒฝ ๋  ๋•Œ๋งˆ๋‹ค ์—ฐ์‚ฐ์ด ๋ฐœ์ƒ ํ•จ์„ ๋ณด์žฅ โ€ข ํ•ต์‹ฌ: UI ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋ ค๋Š” ๊ฒƒ๋ณด๋‹ค State ๋˜๋Š” key ๊ฐ€ ๋” ๋งŽ์ด ๋ณ€๊ฒฝ ๋  ๋•Œ โ€ข ๋งˆ์ง€๋ง‰์— ๋” ์ž์„ธํžˆ..! ํ•˜๋‚˜ ๋˜๋Š” ์—ฌ๋Ÿฌ ์ƒํƒœ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋ณ€ํ™˜ // Calculate high priority tasks only when the todoTasks or highPriorityKeywords // change, not on every recomposition val highPriorityTasks by remember(highPriorityKeywords) { derivedStateOf { todoTasks.filter { it.containsWord(highPriorityKeywords) } } } @Composable fun TodoList(highPriorityKeywords: List<String> = listOf("Review", "Unblock", "Compose")) { val todoTasks = remember { mutableStateListOf<String>() } val highPriorityTasks by remember(highPriorityKeywords, derivedStateOf) { todoTasks.filter { it.containsWord(highPriorityKeywords) } } Box(Modifier.fillMaxSize()) { LazyColumn { items(highPriorityTasks) { /* ... */ } items(todoTasks) { /* ... */ } } /* Rest of the UI where users can add elements to the list */ } }
  • 13. snapshotFlow โ€ข State<T> ๊ฐ์ฒด๋ฅผ cold Flow ๋กœ ๋ณ€๊ฒฝ โ€ข collected ๋˜๋ฉด ๋ธ”๋ก์„ ์‹คํ–‰, ์ฝ์–ด์ง„ State ๊ฐ์ฒด ์ƒํƒœ๋ฅผ emit โ€ข value ๊ฐ€ ์ด์ „๊ณผ ๋™์ผํ•˜๋ฉด emit ํ•˜์ง€ ์•Š์Œ โ€ข Ex) ๋ชฉ๋ก์˜ ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์„ ์ง€๋‚˜์„œ ์Šคํฌ ๋กค ๋˜๋Š” ๊ฒฝ์šฐ (๋ณ€๊ฒฝ ์‹œ ๋งˆ๋‹ค ํ•œ๋ฒˆ๋งŒ ์ฒ˜๋ฆฌ) โ€ข ๋˜ํ•œ Flow ์—ฐ์‚ฐ์ž์˜ ์ด์ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ๋จ. Compose State ๐Ÿ‘‰ Flow val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } LaunchedEffect(listState) { snapshotFlow { listState. fi rstVisibleItemIndex } .map { index -> index > 0 } .distinctUntilChanged() . fi lter { it == true } .collect { MyAnalyticsService.sendScrolledPastFirstItemEvent() } }
  • 14. Restarting Effect โ€ข Effect API ํ•จ์ˆ˜ ๋ช‡๋ช‡์€ ๊ฐ€๋ณ€ ์ธ์ž๋กœ key ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ โ€ข key ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์‹คํ–‰์ค‘์ธ effect ๋ฅผ cancle ๋ฐ ์ƒˆ๋กœ์šด effect ๋ฅผ ์‹คํ–‰ โ€ข ์ผ๋ฐ˜์ ์œผ๋กœ effect ๋ธ”๋ก์— ์‚ฌ์šฉ๋˜๋Š” ๊ฐ€๋ณ€ ๋ฐ ๋ถˆ๋ณ€ ๋ณ€์ˆ˜๋Š” effect api์— ํ‚ค๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ โ€ข ๋˜๋Š” ๋ณ€์ˆ˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด effect ๊ฐ€ ์žฌ์‹œ์ž‘๋˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” rememberUpdatedState ๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ์‚ฌ์šฉ ํ•ด์•ผ ํ•จ. Effect ๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ ์‹œํ‚ค๊ธฐ EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block } @Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // These values never change in Composition val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> /* ... */ } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } }
  • 15. ๐Ÿ”‘Constants as keys โ€ข Effect ์˜ key ๋กœ ์ƒ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ˜ธ์ถœ ์ง€์ ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ฅด๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. โ€ข โš  ํ•˜์ง€๋งŒ LaunchedEffect(true) ๊ฐ™์€ ์ฝ”๋“œ๋Š” while(true) ๋งŒํผ์ด๋‚˜ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•จ. (๊ผญ ํ•„์š”ํ•œ ์ ํ•ฉํ•œ ๊ฒฝ์šฐ๋งŒ ์‚ฌ์šฉ ํ•˜์ž) Effect ์˜ key ๋กœ ์ƒ์ˆ˜ ์‚ฌ์šฉ LaunchedEffect(true) { delay(SplashWaitTimeMillis) currentOnTimeout() }
  • 16. DerivedStateOf ์ข€ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ
  • 20. DerivedStateOf ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ ํ•ฉํ•œ ์‚ฌ๋ก€ โ€ข ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ ๊ฒฐ๊ณผ ์‚ฌ์ด์— ๋ณ€๋™์ด ํฐ ๊ฒฝ์šฐ โ€ข ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์‹œ ์‚ฌ๋ก€ โ€ข ์Šคํฌ๋กค ๊ฐ’์ด ์ž„๊ณ„๊ฐ’์„ ๋„˜์—ˆ๋Š”์ง€ ๊ด€์ฐฐ (scrollPosition > 0) โ€ข ๋ฆฌ์ŠคํŠธ์˜ ์•„์ดํ…œ์ด ์ž„๊ณ„๊ฐ’ ๋ณด๋‹ค ํฐ์ง€ (item > 0) โ€ข format ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (username.isValid())
  • 21. ๐Ÿ™‹FAQ โ€ข Composable ๋‚ด๋ถ€์—์„œ๋Š” ๋ณดํ†ต re- composition ์‹œ ์œ ์ง€ํ•ด์•ผ ํ•˜๋ฏ€๋กœ remember ๋กœ ๋ž˜ํ•‘ โ€ข ๋˜๋Š” ViewModel ๋“ฑ ํ™œ์šฉ DerivedStateOf ๋ฅผ remember ํ•ด์•ผ ํ•˜๋‚˜์š”? @Composable fun MyComp() { // We need to use remember here to survive recomposition val state = remember { derivedStateOf { โ€ฆ } } } class MyViewModel: ViewModel() { // We don't need remember here (nor could we use it) because the ViewModel is // outside of Composition. val state = derivedStateOf { โ€ฆ } }
  • 22. ๐Ÿ™‹FAQ โ€ข remember(key) ์™€ derivedStateOf ์˜ ์ฐจ์ด๋Š” re-composition ์˜ ํšŸ์ˆ˜์™€ ๊ด€๋ จ์žˆ์Œ โ€ข Case 1 โ€ข ๋ถˆํ•„์š”ํ•œ re-composition์„ ๋ฒ„ํผ๋ง โ€ข Case 2 โ€ข ์ž…๋ ฅ=์ถœ๋ ฅ=re-composition remember(key) ์™€ derivedStateOf ์˜ ์ฐจ์ด์ ? val result = remember(state1, state2) { calculation(state1, state2) } val result = remember { derivedStateOf { calculation(state1, state2) } } val isEnabled = lazyListState.firstVisibileItemIndex > 0 val isEnabled = remember { derivedStateOf { lazyListState.firstVisibleItemIndex > 0 } } Case 1 Case 2 val output = remember(input) { expensiveCalculation(input) }
  • 23. ๐Ÿ™‹FAQ โ€ข DerivedStateOf ๋Š” Compose State ๊ฐ์ฒด๋ฅผ ์ฝ์„ ๋•Œ๋งŒ ์—…๋ฐ์ดํŠธ ๋จ โ€ข DerivedStateOf ๊ฐ€ ์ƒ์„ฑ ๋  ๋•Œ ๋‚ด๋ถ€ ์—์„œ ์ฝ์€ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ณ€์ˆ˜๋Š” ์ดˆ๊ธฐ ๊ฐ’์„ ์บก์ฒ˜ํ•˜๊ฒŒ ๋จ โ€ข ์—ฐ์‚ฐ ์‹œ ์ด๋Ÿฐ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด remember์˜ key ๋กœ ์ œ๊ณต ํ•ด์•ผ ํ•จ remember(key)์™€ DerivedStateOf ๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์•ผํ•  ๋•Œ? @Composable fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) { // There is a bug here val isEnabled by remember { derivedStateOf { lazyListState. fi rstVisibleItemIndex > threshold } } Button(onClick = { }, enabled = isEnabled) { Text("Scroll to top") } } https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
  • 24. ๐Ÿ™‹FAQ โ€ข remember(key) ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๊ฒฐ remember(key)์™€ DerivedStateOf ๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์•ผํ•  ๋•Œ? @Composable fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) { val isEnabled by remember(threshold) { derivedStateOf { lazyListState. fi rstVisibleItemIndex > threshold } } Button(onClick = { }, enabled = isEnabled) { Text("Scroll to top") } } https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
  • 25. ๐Ÿ™‹FAQ โ€ข ๊ฒฐ๊ณผ๋ฅผ ์—ฐ์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๋‹ค์ค‘ ์ƒํƒœ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ โ€ข ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ โ€ข ๊ฒฐ๋ก  : UI update < state, key update ์‹œ์— ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ ์—ฌ๋Ÿฌ ์ƒํƒœ๋ฅผ ํ•ฉ์น˜๊ธฐ ์œ„ํ•ด DerivedStateOf ๋Š” ์ ํ•ฉํ•œ๊ฐ€? var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } // This derivedStateOf is redundant val fullName = remember { derivedStateOf { "$firstName $lastName" } } var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullName = "$firstName $lastName"
  • 26. References โ€ข https://developer.android.com/jetpack/compose/side-effects โ€ข Android UI Development with Jetpack Compose, Thomas Kunneth โ€ข https://developer.android.com/jetpack/compose/mental-model โ€ข https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use- derivedstateof-63ce7954c11b โ€ข https://hyeonk-lab.tistory.com/43 โ€ข https://romannurik.github.io/SlidesCodeHighlighter/ โ€ข https://tourspace.tistory.com/412 โ€ข
  • 28. Q&A