12
14

@Composable
@NonRestartableComposable
@ExplicitGroupsComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(
    effect: () -> Unit
) {
    currentComposer.recordSideEffect(effect)
}

Compose์—์„œ SideEffect๋Š” ์ปดํฌ์ €๋ธ” ํ•จ์ˆ˜์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๋ชจ๋“  ๋™์ž‘์„ ์˜๋ฏธํ•œ๋‹ค. DB ์—…๋ฐ์ดํŠธ, api call, shared preferences, datatstore ๊ฐ™์€ UI ์ž‘์—…์ด ์•„๋‹Œ ๋™์ž‘๋“ค์ด ์—ฌ๊ธฐ์— ํ•ด๋‹น๋œ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ์—์„œ ๋ฒˆ์—ญ์„ ๋ถ€์ˆ˜ํšจ๊ณผ๋ผ๊ณ  ํ•ด๋†จ๋Š”๋ฐ ๊ทธ๋ƒฅ SideEffect๊ฐ€ ๋” ๋‚ซ๋‹ค.

 

๋™์ž‘์›๋ฆฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ ์ „์—, SideEffect๋Š” ์ˆœ์ˆ˜ํ•จ์ˆ˜ ๊ฐœ๋…๊ณผ ๊ฐ™์ด ๋Œ์•„๋‹ค๋‹ˆ๋Š”๋ฐ ์ด๊ฑธ ๋จผ์ € ์งš๊ณ  ๊ฐ€์ž. 

๊ฐ™์€ ์ž…๋ ฅ์— ๋Œ€ํ•ด ํ•ญ์ƒ ๊ฐ™์€ ์ถœ๋ ฅ์„ ์ƒ์„ฑํ•˜๊ณ  SideEffect๊ฐ€ ์—†๋Š” ๊ฑธ ์ˆœ์ˆ˜ํ•จ์ˆ˜๋ผ๊ณ  ํ•œ๋‹ค. ๊ทธ๊ฒŒ Composable์ธ๋ฐ, ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž. 

@Composable
fun UserCard(user: User) {  // ๊ฐ™์€ User ๊ฐ์ฒด์— ๋Œ€ํ•ด ํ•ญ์ƒ ๋™์ผํ•œ UI๋ฅผ ์ƒ์„ฑ
    Column {
        Text(user.name)
        Text(user.email)
    }
}

@Composable
fun UserCard(user: User) {
    Column {
        Text(user.name)
        Text(user.email)
        LaunchedEffect(Unit) {
            database.logUserView(user.id)  // SideEffect
        }
    }
}

UI ์ž‘์—… ์ด์™ธ์˜ ๋™์ž‘์ด ๋งค ํ˜ธ์ถœ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ถœ๋ ฅ์„ ์ƒ์„ฑํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๋‘๋ฒˆ์งธ UserCard๋Š” ๋น„์ˆœ์ˆ˜ํ•จ์ˆ˜๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค. ๊ทผ๋ฐ ๋ฌด์กฐ๊ฑด ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. UI๋Š” ๋™์ผํ•œ๊ฑธ ์ถœ๋ ฅํ•˜์ง€๋งŒ ์ง€๊ธˆ ์ฝ”๋“œ์ฒ˜๋Ÿผ logging์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ api call์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ ํ•  ๋•Œ๋Š” SideEffect๋ฅผ ์•ˆ ์“ธ์ˆ˜๊ฐ€ ์—†๋Š”๋ฐ, ์ด๊ฑธ Composition๊ณผ SideEffect์˜ ์‹คํ–‰์„ ๋ถ„๋ฆฌํ•˜๋ฉด ํ•ด๊ฒฐ๋œ๋‹ค.

@Composable
fun MyScreen() {
    var stateCnt by remember { mutableStateOf(0) }
    
    Column {
        Text("Count: $stateCnt")
        Button(onClick = { stateCnt++ }) {
            Text("์ฆ๊ฐ€")
        }
    }
    
    LaunchedEffect(stateCnt) {
        println("์ฆ๊ฐ€ํ–ˆ์–ด์š” $stateCnt")
    }
}

์ปดํฌ์ €๋ธ”์— ์—ฌ์ „ํžˆ ์œ„์น˜ํ•˜์ง€๋งŒ, UI๋ฅผ ๊ทธ๋ฆฌ๋Š” ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ์ปดํฌ์ง€์…˜์ด ์™„๋ฃŒ๋œ ๋’ค์— SideEffect๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ํŒจํ„ด์ด๋‹ค.

์ด๋Ÿฌ๋ฉด `LaunchedEffect`๋กœ ๋“ฑ๋ก๋˜์—ˆ์ง€๋งŒ, `stateCnt`๊ฐ€ ๋ณ€ํ™”ํ•  ๋•Œ๋งŒ print๋ฌธ์ด ์ถœ๋ ฅ๋œ๋‹ค. ์ด๋Ÿฌ๋ฉด UI๋Š” ํ•ญ์ƒ ๊ฐ™์€ ์ž…๋ ฅ์— ๋Œ€ํ•ด ๊ฐ™์€ ์ถœ๋ ฅ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ˆœ์ˆ˜ํ•จ์ˆ˜๋ฅผ ์ง€ํ‚จ ๊ฒƒ์ด๋‹ค.

 

์ด์ „ ๊ฒŒ์‹œ๊ธ€์—์„œ ์ผ๋˜ recomposition์„ ๊ฒฐํ•ฉํ•ด๋ณด๋ฉด, stateCnt๊ฐ€ onClick์œผ๋กœ ์ธํ•ด ์ฆ๊ฐ€ํ•˜๊ณ , Text๊ฐ€ ๊ทธ์—๋”ฐ๋ผ recomposition๋˜๊ณ , ๊ทธ๋Ÿฌ๊ณ  ๋‚˜์„œ LaunchedEffect๊ฐ€ ํ˜ธ์ถœ๋ผ์„œ print๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

SideEffect ์ข…๋ฅ˜๋Š” ํฌ๊ฒŒ 3๊ฐ€์ง€๋กœ ์žก์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค. 

  • `SideEffect`: ๋งค ๋ฆฌ์ปดํฌ์ง€์…˜๋งˆ๋‹ค ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋™๊ธฐ ์ž‘์—…
  • `LaunchedEffect`: ๋น„๋™๊ธฐ ์ž‘์—…์ด๋‚˜ ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜์˜ ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—…
  • `DisposableEffect`: ์ •๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ

SideEffect ๋ถ€ํ„ฐ ๋ณด์ž. 

@Composable
@NonRestartableComposable
@ExplicitGroupsComposable
@OptIn(InternalComposeApi::class)
fun SideEffect(
    effect: () -> Unit
) {
    currentComposer.recordSideEffect(effect)
}

Composer์— effect๋ฅผ ๊ธฐ๋กํ•˜๊ธฐ๋งŒ ํ•˜๊ณ , ์ด effect๋Š” ์ปดํฌ์ง€์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ํ›„์— ์‹คํ–‰๋œ๋‹ค. ๋ชจ๋“  ๋ฆฌ์ปดํฌ์ง€์…˜๋งˆ๋‹ค ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งŽ์€ recomposition์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” SideEffect๋Š” LaunchedEffect๋กœ ๊ด€๋ฆฌํ•ด์•ผ๋œ๋‹ค.

 

LaunchedEffect๋Š” ๊ฐ€์žฅ ๋ณต์žกํ•œ SideEffect๋‹ค. 

internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        // This should never happen but is left here for safety
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    override fun onAbandoned() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }
}

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

์ดˆ๊ธฐํ™”๋˜๋ฉด, ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฐ›์•„ ์ƒˆ๋กœ์šด CoroutineScope๋ฅผ ๋งŒ๋“ ๋‹ค. ์ปดํฌ์ง€์…˜์— ์ง„์ž…ํ• ๋•Œ๋Š” ์ด์ „์— ์‹คํ–‰ ์ค‘์ด๋˜ ์ž‘์—…์ด ์žˆ์„๊ฒฝ์šฐ ์ทจ์†Œํ•ด๋ฒ„๋ฆฌ๊ณ  ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด์„ ์‹œ์ž‘ํ•˜์—ฌ ์ฃผ์–ด์ง„ task๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

 

remember(key1)์—์„œ๋Š” key๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ƒˆ๋กœ์šด LaunchedEffectImpl ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์ด์ „ ์ธ์Šคํ„ด์Šค์˜ ์ž‘์—…์€ ์ทจ์†Œ๋˜๊ณ , ์ƒˆ๋กœ์šด ์ž‘์—…์ด ์‹œ์ž‘๋œ๋‹ค.

 

์•ž์„œ SideEffect๋Š” ์ปดํฌ์ง€์…˜์ด ๋๋‚œ ๋’ค์— ์‹คํ–‰๋œ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ปดํฌ์ง€์…˜์—์„œ ์ œ๊ฑฐ๋  ๋•Œ LeftCompositionCancellationException์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋ฃจํ‹ด์„ ์ทจ์†Œํ•˜๊ฒŒ๋œ๋‹ค. ์ด ์˜ˆ์™ธ๋Š” stacktrace๋ฅผ ์ตœ์ ํ™”ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

 

LaunchedEffect๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, Key๋ฅผ ๊ผญ ์จ์•ผํ•œ๋‹ค.

private const val LaunchedEffectNoParamError =
    "LaunchedEffect must provide one or more 'key' parameters..."

@Deprecated(LaunchedEffectNoParamError, level = DeprecationLevel.ERROR)

key ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ•์ œํ•˜๋„๋ก ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฑธ ์ง€์ผœ์ค˜์•ผํ•œ๋‹ค. key๊ฐ€ ์—†์œผ๋ฉด ๋ฌดํ•œ ๋ฆฌ์ปดํฌ์ง€์…˜์ด ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ „์— key์—†์ด ์“ฐ๋˜๊ฑฐ๋Š” Unit์„ key๋กœ ๋„ฃ์œผ๋ฉด ๋™์ผํ•œ ํšจ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ ๊ฐ™๋‹ค.

 

DisposableEffect๋„ LauncedEffect์™€ ์œ ์‚ฌํ•œ ๋ถ€๋ถ„์ด ์žˆ์ง€๋งŒ, onDispose์•ˆ์—์„œ ํ˜ธ์ถœ๋ผ์•ผํ•œ๋‹ค.

private class DisposableEffectImpl(
    private val effect: DisposableEffectScope.() -> DisposableEffectResult
) : RememberObserver {
    private var onDispose: DisposableEffectResult? = null

    override fun onRemembered() { // ์‹ค์ œ ์‹คํ–‰๋‹จ๊ณ„
        onDispose = InternalDisposableEffectScope.effect()
    }

    override fun onForgotten() {
        onDispose?.dispose()
        onDispose = null
    }

    override fun onAbandoned() {
        // Nothing to do as [onRemembered] was not called.
    }
}

@Composable
fun DisposableEffect(
    key1: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult
) {
    remember(key1) { DisposableEffectImpl(effect) }
}

effect๊ฐ€ DisposableEffectScope๋กœ ๊ฐ์‹ธ์ ธ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, onDispose์•ˆ์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ onRemember์—์„œ ์ปดํฌ์ง€์…˜์— ๋“ค์–ด์˜ฌ ๋•Œ ํ˜ธ์ถœ๋œ๋‹ค.

 

DisposableEffectScope๋ฅผ ์ข€ ๋” ๋ด์•ผ๋œ๋‹ค.

class DisposableEffectScope {
    /**
     * Provide [onDisposeEffect] to the [DisposableEffect] to run when it leaves the composition
     * or its key changes.
     */
    inline fun onDispose(
        crossinline onDisposeEffect: () -> Unit
    ): DisposableEffectResult = object : DisposableEffectResult {
        override fun dispose() {
            onDisposeEffect()
        }
    }
}

crossline ํ‚ค์›Œ๋“œ๋ฅผ ์จ์„œ, ๋žŒ๋‹ค๊ฐ€ ๋น„์ง€์—ญ ๋ฆฌํ„ด์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•ด์„œ dispose ๋กœ์ง์˜ ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค.

crossline์€ inline ํ‚ค์›Œ๋“œ๋ž‘ ๊ฐ™์ด ๋ณผ ๋•Œ ์˜๋ฏธ๊ฐ€ ์žˆ๋Š”๋ฐ, ์•„๋ž˜ ์ฝ”๋“œ๋กœ ์ดํ•ดํ•ด๋ณด์ž.

inline fun unsafeOperation(callback: () -> Unit) {
    thread {
        callback()  // ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ๋น„์ง€์—ญ ๋ฐ˜ํ™˜์€ ์œ„ํ—˜
        // return ์ด ๊ฐ€๋Šฅํ•˜๋‹ค
    }
}

inline fun safeOperation(crossinline callback: () -> Unit) {
    thread {
        callback()  // crossinline์œผ๋กœ ๋น„์ง€์—ญ ๋ฐ˜ํ™˜์ด ๊ฐ•์ œ ๊ธˆ์ง€
    }
}

inline ํ•จ์ˆ˜๋Š” ์ปดํŒŒ์ผ ์‹œ์ ์— ํ•จ์ˆ˜ ํ˜ธ์ถœ ๋ถ€๋ถ„์ด ํ•จ์ˆ˜์˜ ๋ณธ๋ฌธ์œผ๋กœ ๋Œ€์ฒด๋˜๋Š” ํ•จ์ˆ˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋น„์ง€์—ญ ๋ฐ˜ํ™˜์ด๋ž€ ๋žŒ๋‹ค๋ฅผ ํฌํ•จํ•˜๋Š” ์™ธ๋ถ€ ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๊ทธ๋ž˜์„œ inlineํ•จ์ˆ˜๋ฅผ ์“ฐ๋ฉด return ๊ฐ’์„ ํ˜ธ์ถœ๋ถ€์—์„œ ๊บผ๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ๋˜๋Š”๋ฐ, crossline์œผ๋กœ ์ด๊ฑธ ๋ง‰๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ onDispose ํ•จ์ˆ˜์—์„œ return์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.


# ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ์“ฐ๋Š” ๊ฒŒ ์ข‹์„๊นŒ?

# > `SideEffect`์˜ ๊ฒฝ์šฐ

๋จผ์ €, state๊ฐ€ ๋ณ€ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ SideEffect์— ๋„ฃ์œผ๋ฉด ์•ˆ๋œ๋‹ค. recomposition์ด ๋ฐœ์ƒํ•  ๋•Œ ๋งˆ๋‹ค SideEffect๊ฐ€ ํ˜ธ์ถœ๋˜๋Š”๋ฐ state๊ฐ€ ๋ณ€ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ SideEffect ๋‚ด๋ถ€์— ์žˆ์œผ๋ฉด ๋ฌดํ•œ ๋ฆฌ์ปดํฌ์ง€์…˜์ด ๋ฐœ์ƒํ•ด๋ฒ„๋ฆฐ๋‹ค.

@Composable
fun UserProfile(user: User) {
    var lastSeenTime by remember { mutableStateOf<Long?>(null) }
    SideEffect {
        lastSeenTime = System.currentTimeMillis()
    }

	// ์ด๊ฒŒ ๋งž๋Š” ๋ฐฉ๋ฒ•
    var lastSeenTime by remember { mutableStateOf<Long?>(null) }
    LaunchedEffect(Unit) {
        lastSeenTime = System.currentTimeMillis()
    }
}

๋”ฐ๋ผ์„œ LauncedEffect๋กœ ์ฒ˜์Œ Composition์ด ๋ ๋•Œ๋งŒ ์ ์šฉ๋˜๋„๋ก ์ œ์–ดํ•˜๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ด์šฉํ•ด์„œ state๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋˜ ํ™•์žฅ๋˜๋Š” ๊ฒŒ, ๋น„๋™๊ธฐ ์ž‘์—… ๋˜ํ•œ ๋„ฃ์–ด๋‘๋ฉด ์•ˆ๋œ๋‹ค.

์ปดํฌ์ง€์…˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋Š” ๋ฐ, ๋น„๋™๊ธฐ ์ž‘์—…์„ SideEffect์—์„œ ์ˆ˜ํ–‰ํ•œ๋‹ค๋ฉด ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๋งž์ง€ ์•Š์•„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋™๊ธฐ์ž‘์—…๋งŒ ํ•ด์•ผ๋˜๋Š”๋ฐ, ์ด ๋™๊ธฐ์ž‘์—…์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋ฉด UI ์—…๋ฐ์ดํŠธ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค!

 

๊ทธ๋ž˜์„œ ์ •๋ฆฌํ•˜์ž๋ฉด SideEffect๋Š” ๋™๊ธฐ์ž‘์—…, ๊ฐ€๋ฒผ์šด ์ž‘์—…, state๋ฅผ ์ง์ ‘ ๋ฐ”๊พธ์ง€ ์•Š๋Š” ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•ด์•ผ๋œ๋‹ค.

 

# > `LaunchedEffect, DisposableEffect`์˜ ๊ฒฝ์šฐ

LaunchedEffect์˜ ๊ฒฝ์šฐ, ํ‚ค๊ฐ€ ๋ฌด์กฐ๊ฑด ์žˆ์–ด์•ผํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ•„์š”ํ•œ ํ‚ค๋งŒ ์žˆ์–ด์•ผํ•œ๋‹ค.

LaunchedEffect(userId) {
    viewModel.loadUserData(userId)
}

๊ทธ๋ฆฌ๊ณ  ๊ฐ์ฒด๋ฅผ ํ‚ค๋กœ ์“ฐ๊ธฐ๋ณด๋‹ค๋Š” ๊ทธ ๊ฐ์ฒด์˜ key๋ฅผ remember์— ๋„ฃ์–ด์ค„ key๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๋˜ํ•œ ์•„๊นŒ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๋ด์„œ ์•Œ๊ฒ ์ง€๋งŒ, ์ด๋ฏธ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๊ฐ€ ์žˆ๊ธฐ์— ์Šค์ฝ”ํ”„๋ฅผ ๋˜ ์„ ์–ธํ•ด์ค„ ํ•„์š”๊ฐ€ ์—†๋‹ค.

val scope = rememberCoroutineScope()
LaunchedEffect(messages) {
    scope.launch { // ๋ถˆํ•„์š”ํ•œ ์ค‘์ฒฉ๋œ ์ฝ”๋ฃจํ‹ด
        processMessages(messages)
    }
}

// ๊ทธ๋ƒฅ ์“ฐ๋ฉด๋œ๋‹ค.
LaunchedEffect(messages) {
    processMessages(messages)
}

 

DisposableEffect๋„ ์œ ์‚ฌํ•˜๋‹ค. ๋‹ค๋งŒ ๋ฆฌ์†Œ์Šค ํ•ด์ œ๋ฅผ ์—ฌ๊ธฐ์„œ ํ•ด์ค„ ๋•Œ ์ผ๋ถ€๋งŒ ํ•˜์ง€๋ง๊ณ  ํ•ด์•ผ๋  ๊ฑฐ ๋‹ค ํ•ด์ œ์‹œ์ผœ์•ผ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ์—†๋‹ค.

DisposableEffect(Unit) {
    onDispose {
        locationManager.stopTracking()
        locationManager.releaseResources()
        locationManager.unregisterListeners()
    }
}

๊ทผ๋ฐ ํ•˜๋‚˜ ์˜คํ•ดํ• ๋งŒํ•œ ๋ถ€๋ถ„์ด ์žˆ๋‹ค. 

DisposableEffect์˜ ์‹คํ–‰์‹œ์ ์€ ์–ธ์ œ์ผ๊นŒ? ํ˜„์žฌ Unit์œผ๋กœ ๊ฑธ์–ด๋‘” ๊ฑธ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค.

DisposableEffect(Unit) {
    Log.d(TAG, "Greeting: outer")
    onDispose {
        Log.d(TAG, "Greeting: inner")
    }
}

๋‹น์—ฐํžˆ outer๋Š” ๋จผ์ € ์ฐํžŒ๋‹ค. DisposableEffect๊ฐ€ Decomposition์—์„œ ํ˜ธ์ถœ๋˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, onDispose ๋‚ด๋ถ€ ์ž‘์—…์ด Decomposition์ดํ›„์— ์ผ์–ด๋‚œ๋‹ค. ์ด๊ฑธ ๊ตฌ๋ถ„ํ•ด์•ผ๋œ๋‹ค.

 

๊ทธ๋ž˜์„œ ์•„๊นŒ ๋ฆฌ์†Œ์Šค ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์ข€ ๋” ์ œ๋Œ€๋กœ ์“ฐ์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ๋‹ค.

DisposableEffect(Unit) {
    val locationManager = getLocationManager()
    locationManager.startTracking()

    onDispose {
        locationManager.stopTracking()
        locationManager.releaseResources()
        locationManager.unregisterListeners()
    }
}

๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ ์‹œ์ ๋„, ํ•ด์ œ ์‹œ์ ๋„ ๋‹ค DisposableEffect ๋‚ด๋ถ€์— ๊ฑธ์–ด๋‘ฌ์„œ ์ž๋™์œผ๋กœ ์ •๋ฆฌ๋˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

DisposableEffect๋Š” Back Handler ์“ธ ๋•Œ๋„ ์œ ์šฉํ•˜๋‹ค.

@Composable
fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
    val currentContext = LocalContext.current
    
    val dispatcher = (currentContext as? ComponentActivity)
        ?.onBackPressedDispatcher
        ?: return

    DisposableEffect(dispatcher, enabled) {
        val callback = object : OnBackPressedCallback(enabled) {
            override fun handleOnBackPressed() {
                onBack()
            }
        }
        
        dispatcher.addCallback(callback)
       
        onDispose {
            callback.remove()
        }
    }
}

ํ˜„์žฌ ์•กํ‹ฐ๋น„ํ‹ฐ์˜ context๋ฅผ ๊ฐ€์ ธ์™€์„œ dispatcher๋ฅผ ์ œ์–ดํ•˜๊ธฐ ๋•Œ๋ฌธ์— callback์„ ์„ค์ •ํ•˜๊ณ  ํ•ด์ œํ•ด์ฃผ๋Š” ์ž‘์—…์ด ํ•„์ˆ˜์ ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด key๋กœ enable, dispatcher ๋‘๊ฐœ๋ฅผ ๋ฐ›์•„ ์„ค์ •ํ•˜๋Š”๋ฐ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ enabled ๊ฐ’์„ ์™ธ๋ถ€์—์„œ ๋ฐ›์•„ on/off๋˜๋Š” ๊ฑฐ ํ•˜๋‚˜๋ž‘ ์™ธ๋ถ€์—์„œ ์ƒ์„ฑํ•œ dispatcher๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ ํ•˜๋‚˜๋‹ค.

 

ํŠธ๋ฆฌ๊ฑฐ๋กœ ์‚ฌ์šฉํ•  key๊ฐ€ ๊ทธ๋ž˜์„œ enabled ํ•˜๋‚˜๊ณ , dispatcher๋Š” crossline ๋•Œ๋ฌธ์— DisposableEffect ๋‚ด๋ถ€์—์„œ ์ƒ์„ฑํ•ด์„œ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ชปํ•˜๋‹ˆ๊นŒ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ดํ•ดํ•˜๋ฉด๋˜๊ฒ ๋‹ค.

 

Effect ํ˜•์ œ๋“ค์˜ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋ž˜๋กœ ์ดํ•ดํ•˜๋ฉด ๋œ๋‹ค.

sequenceDiagram
    participant C as Composition
    participant S as SideEffect
    participant L as LaunchedEffect
    participant D as DisposableEffect

    C->>C: Composition ์‹œ์ž‘
    C->>S: recordSideEffect
    C->>L: remember(key)
    C->>D: remember(key)
    C->>C: Composition ์™„๋ฃŒ
    C->>S: effect ์‹คํ–‰
    C->>L: onRemembered & launch
    C->>D: onRemembered & effect
    
    Note over C,D: ์ปดํฌ์ง€์…˜ ์ข…๋ฃŒ/key ๋ณ€๊ฒฝ ์‹œ
    C->>L: onForgotten & cancel
    C->>D: onForgotten & dispose

> "Composition ์‹œ์ž‘" 

์ด๋•Œ Compose runtime ์ด UI ํŠธ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค. ๋ชจ๋“  @Composable ํ•จ์ˆ˜๋“ค์ด ํ˜ธ์ถœ๋˜๋ฉด์„œ ํ™”๋ฉด์ด ๊ทธ๋ ค์ง„๋‹ค.

 

> "remember(key)" 

LaunchedEffect์™€ DisposableEffect ๋ชจ๋‘ remember๋ฅผ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฑธ ์•„๊นŒ ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ดค๋‹ค. ์ด๋•Œ key๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๊ฐ€ ์žฌ์‚ฌ์šฉ๋œ๋‹ค.

 

> "effect ์‹คํ–‰"

SideEffect์˜ ๊ฒฝ์šฐ, Composition์ด ์™„๋ฃŒ๋œ ํ›„ recordSideEffect๋กœ ๋“ฑ๋กํ–ˆ๋˜ SideEffect๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

๋‚˜๋จธ์ง€ ๋‘๊ฐœ๋Š” onRemembered์—์„œ ์‹คํ–‰์ด ๋˜๋Š”๋ฐ 

LaunchedEffect์˜ ๊ฒฝ์šฐ, onRemembered๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด์„œ ์ฝ”๋ฃจํ‹ด์ด ์‹œ์ž‘๋œ๋‹ค. ์ด์ „์— ์‹คํ–‰ ์ค‘์ด๋˜ job์ด ์žˆ๋‹ค๋ฉด ์ทจ์†Œ๋œ๋‹ค.

DisposableEffect์˜ ๊ฒฝ์šฐ, onRemembered์—์„œ effect๊ฐ€ ์‹คํ–‰๋˜๊ณ  DisposableEffectResult๊ฐ€ ์ €์žฅ๋œ๋‹ค.

 

> "์ปดํฌ์ง€์…˜ ์ข…๋ฃŒ/key ๋ณ€๊ฒฝ ์‹œ"
์ปดํฌ์ €๋ธ”์ด ์ปดํฌ์ง€์…˜์„ ์ข…๋ฃŒํ•  ๋•Œ๋‚˜ key๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ์ƒˆ๋กœ์šด effect๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์‹คํ–‰๋˜๋Š”๋ฐ, DisposableEffect๋Š” ์ด๋•Œ onDispose๋ฅผ ํƒ„๋‹ค.

 

์—ญ์‹œ ์ˆ˜๋ช…์ฃผ๊ธฐ ๊ด€๋ จ ๋‚ด์šฉ์€ ์–ด๋ ค์šด ๊ฒƒ ๊ฐ™๋‹ค!

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•

'Android ๐Ÿ–ฅ๏ธ > Compose๐Ÿ“–' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

@Immutable  (0) 2025.02.23
Composition & Recomposition ๋™์ž‘ ์›๋ฆฌ  (1) 2024.12.14
Jetpack Compose ๊ธฐ์ดˆ  (0) 2024.08.16
COMMENT