Android/Jetpack Compose

Android에서 Props Drilling을 해결해보자 with SnackBar

chattymin 2025. 3. 8. 21:39
728x90

 

이 내용을 설명하기에 앞서 Props Drilling이라는 개념을 처음 들어보는 분들이 있을수도 있다.

그래서 해당 개념에 대해서 간단하게 설명하고, 내가 Android에서 어떻게 Props Drilling을 방지했는지 이야기 해보겠다.

 

 

Props Drilling이란?

React에서 나온 개념이다.

Prop Drilling 은 props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정을 의미한다.

 

매개변수로 특정 값을 계속해서 내려주는 것이라 생각하면 된다. 아래 예시를 보면 이해가 될 것이다.

@Composable
fun test1() {
    test2 {
    	// SHOW ACTION
    }
}

@Composable
fun test2(show:Unit -> ()) {
    test3(show = show)
}

@Composable
fun test3(show:Unit -> ()) {
    test4(show = show)
}

@Composable
fun test4(show:Unit -> ()) {
    test5(show = show)
}

@Composable
fun test5(show:Unit -> ()) {
    Button(
    	onClick = show
    )
}

 

 

이게 왜 문제일까?

뭐 한개 두개 정도는 밑으로 내려줄 수 있다.

 

하지만 위 예시처럼 4개, 10개 정도를 내려준다면?

유지보수가 너무 복잡해진다.

 

해당 코드가 어디서 시작돼서 어디까지 내려가는지도 추적하기 힘들고, 함수명을 수정한다고 하면 모든 경로를 다 찾아가서 수정해야한다.

 

그렇기 때문에 과도한 props drilling은 피해야 한다.

 

 

이게 어떤 상황에서 발생했을까?

Android를 Compose로 개발하다보면 SnackBar를 사용할 때가 있다. 

이 SnackBar는 대부분 공통된 디자인으로 사용되기 때문에 mainRoute에 공통으로 넣어두고들 많이 사용한다.

 

그렇다면 이 SnackBar를 발생시키기 위한 함수가 원하는 feature까지 매개변수로 내려가는 현상이 있었다.

    val errorSnackBarHostState = remember { SnackbarHostState() }
    val onShowErrorSnackBar: (String?) -> Unit = { text ->
        coroutineScope.launch {
            errorSnackBarHostState.currentSnackbarData?.dismiss()
            val job = launch {
                errorSnackBarHostState.showSnackbar(
                    message = text ?: "오류가 발생했어요. 다시 시도해주세요.",
                )
            }
            delay(SNACK_BAR_DURATION)
            job.cancel()
        }
    }
    
Scaffold(
        content = { paddingValue ->
            Box(
                modifier = Modifier
                    .fillMaxSize()
            ) {
                NavHost(
					...
                ) {
                    homeNavGraph(
                        onShowSnackBar = onShowTextSnackBar,
                    )
			}
        }
)

fun NavGraphBuilder.homeNavGraph(
    onShowSnackBar: (String) -> Unit,
) {
    composable<Home> {
        HomeRoute(
            onShowSnackBar = onShowSnackBar,
        )
	}
}


@Composable
fun HomeRoute(
	...
    onShowSnackBar: (String) -> Unit
){
	...
    
	onShowSnackBar("드디어 실행~")
}

대략적으로 나타내기 위해 일부 생략한 부분도 있는데 이렇다.

 

navigation과 Route, Screen 등 많은 계층을 내려가다보니 관리하는데에 불편함을 느꼈고, 이를 해결하기 위해 찾아보니 이게 Props Drilling이었다.

 

 

어떻게 Props Drilling을 해결하나요?

React의 경우 전역상태 라이브러리인 redux, MobX, recoil등을 사용하거나, children 을 사용해서 해결한다고 한다.

하지만 난 React를 해본적 없으니 아닐수도 있다. 아님말고

 

Android의 경우 React에서 해결한 방법 중 하나인 전역상태를 통해 해결하고자 했다.

내가 구현한 방법은 정확히는 Android 중에서도 Compose이다.

 

 

내가 과거에 작성했던 블로그 글 중 CompositionLocal에 관한 내용이 있다.

이 도구를 사용하여 내가 겪은 문제를 해결했다.

 

 

스낵바를 호출시키는 Trigger를 만들고, 이를 CompositionLocal로 전역 관리했다.

val LocalSnackBarTrigger = staticCompositionLocalOf<(String) -> Unit> {
    error("No SnackBar provided")
}

MainScreen(){
    val errorSnackBarHostState = remember { SnackbarHostState() }
    val onShowErrorSnackBar: (String?) -> Unit = { text ->
        coroutineScope.launch {
            errorSnackBarHostState.currentSnackbarData?.dismiss()
            val job = launch {
                errorSnackBarHostState.showSnackbar(
                    message = text ?: "오류가 발생했어요. 다시 시도해주세요.",
                )
            }
            delay(SNACK_BAR_DURATION)
            job.cancel()
        }
    }

    CompositionLocalProvider(
        LocalSnackBarTrigger provides onShowErrorSnackBar,
    ) {
        Scaffold(
            content = { paddingValue ->
                NavHost(){
                	homeNavGraph()
                }
            }   
        )
    }
}

HomeScreen() {
	val snackBar = LocalSnackBarTrigger.current
	...
    
    Button(
    	onClick = {
        	snackBar("오류 발생~~")
        }
    )
}

 

이렇게 짧아졌다.

물론 지금 작성한 코드는 상당히 간략화되어있고, Navigation에서 Screen으로 한단계만 내려가지만 실제로 개발을 하다보면 더 많이 내려가는 경우도 있다. 

 

전역으로 값을 관리하게 되면서 각 경로를 내려가는 코드가 사라졌고, 원하는 feature에서 current를 받아오기만 하면된다.

이렇게 변경되며 불필요한 코드가 사라졌고, 경로 추적이 쉬워졌다.

 

해당 작업을 진행했던 PR 링크를 걸어둘테니 한번 가서 보면 이해가 쉬울 것이다.

728x90