Bottom Navigation Bar UI with Compose - (1)
Bottom Navigation Bar State with Compose - (2) <- 현재 글입니다.
안녕하세요 점냥입니다:)
이번 글에서는 지난 글에 이어서 Bottom Navigation Bar State 관리에 대해서 다뤄보겠습니다.
지난번에 만들었던 Bottom Navigation Bar를 다들 실행해보셨나요?
결과로만 말씀드리자면 클릭되는 인터렉션은 존재하지만 UI의 변화는 찾아볼 수 없습니다. 어찌 보면 당연하죠. 기존 뷰 시스템에서도 Listener로 콜백을 등록해주고 그에 따른 처리를 해주어야 했으니까요. 그렇다면 Compose는 어떻게 작업해야 할까요? 방법은 Compose의 State 처리입니다.
State
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
Compose는 선언형 UI로 XML처럼 뷰가 자동으로 업데이트되지 않습니다. 예를 들어 위 코드에서 OutlinedTextField는 EditText처럼 사용자의 텍스트 입력을 받는 위젯임에도 불구하고 실행을 하게 되면 아무런 변화가 없는 것을 확인할 수 있습니다. 그 이유는 텍스트 값을 표현하는 value라는 매개변수의 값 즉, State가 변경되지 않기 때문입니다.
Compose는 주어진 State를 UI로 표현해주는 것입니다.
OutlinedTextField는 최초 Compostion 시 ""라는 값을 가진 String 객체(State)를 가지고 뷰를 그리게 됩니다. ReComposition이 일어나게 하려면 참조하고 있는 값, 즉 State가 변경되어야 하는 데 immutable String 객체는 값이 변경될 수가 없겠죠. 그래서 우리는 mutableState 객체를 사용하여 State를 관리해야 합니다.
mutableState
interface MutableState<T> : State<T> {
override var value: T
}
mutableState란 이름 그대로 변경 가능한 상태입니다.
내부에 value 변수를 하나 가지고 있으며, 상태에 대한 변경 알림을 compose에 전달해주는 역할을 수행합니다.
MutableState kotlin 파일에서 명시적으로 보면 어떻게 compose에게 값을 전달하는지 알기 쉽지 않은데,
디컴파일해보면 발견할 수 있습니다. 관련 글로 좋은 블로그가 있어서 링크를 달아주고 이 문단은 마무리하겠습니다.
[Android] Jetpack Compose의 magic 파해치기 (tistory.com)
enum class MainTab {
DRAW, STORY, SETTING
}
@Composable
fun MainNavigation(modifier: Modifier = Modifier) {
var isSelectedTab by remember { mutableStateOf(MainTab.DRAW)}
BottomNavigation(
modifier = modifier,
backgroundColor = colorResource(id = R.color.colorPrimary)
) {
MainNavigationItem(
iconRes = R.drawable.ic_draw,
labelRes = R.string.menu_draw,
selected = isSelectedTab == MainTab.DRAW,
onClick = { isSelectedTab = MainTab.DRAW }
)
MainNavigationItem(
iconRes = R.drawable.ic_story,
labelRes = R.string.menu_story,
selected = isSelectedTab == MainTab.STORY,
onClick = { isSelectedTab = MainTab.STORY }
)
MainNavigationItem(
iconRes = R.drawable.ic_settings,
labelRes = R.string.menu_setting,
selected = isSelectedTab == MainTab.SETTING,
onClick = { isSelectedTab = MainTab.SETTING }
)
}
}
mutableState는 간단하게 위처럼 사용할 수 있습니다. 만약 위처럼 Composable 내에서 State를 관리하려면 remember를 사용해야 합니다.
remember
키워드는 Composable에서 특정 값을 보존하기 위해 사용합니다. 예를 들어 함수 내부에서 선언된 변수들은 모두 스택 메모리에 저장되었다가 함수가 종료되면 반환됩니다. 하지만 A라는 함수를 10번 호출했을 때 매번 내부에서 값을 선언하는 것이 아니라 최초의 한 번만 초기화하고 싶다면 어떻게 할까요? 아쉽게도 함수 자체적으로는 해결할 수 없습니다. 전역 변수로 선언하거나 클래스 내 함수라면 클래스 변수로 선언하는 등 외부에 의존해야 합니다.
하지만 Compose는 remember 키워드를 통해 최초 Composable 구문이 실행될 때 값을 초기화하고 State가 변경되어 ReComposition이 발생할 때 이전에 초기화된 변수를 재활용합니다.
위치가 하단이 아닌 상단에 위치한 것을 제외하면 제대로 잘 동작하네요!
그런데 또 한 가지 아쉬운 점이 있습니다. Tab를 누르면 특정 화면으로 이동해야 하는 데 현재 코드에서는 어떻게 구현해야 할 수 있을 까요? 가장 쉽게 떠오르는 방법은 콜백 등의 이벤트를 람다로 또 넘겨받아 전달하는 방법도 있겠네요.
Compose는 State hoisting이라는 개념을 통해 가능한 최 상위에 State를 관리하는 방식을 추천합니다. 현재 Android 트렌드로 자리 잡은 Jetpack Viewmodel에 State를 관리할 수 있는데요. 우리가 이전 mvvm 아키텍처를 구현하기 위해 사용했던 LiveData, StateFlow 등을 mutableState로 변환하는 작업들이 제공해주고 있습니다. 추후 관련 글도 포스팅해보겠습니다!
참고
'Android > Common' 카테고리의 다른 글
[Android] AAC ViewModel에서 Context 접근하는 방법 (0) | 2022.10.08 |
---|---|
[Android] EventBus (0) | 2022.08.28 |
[Android] ViewTree 내에 동일한 ID을 가진 위젯이 존재할 경우 UI 상태 저장 오류 해결 방법 - 2 (0) | 2022.05.13 |
[Android] Android에서 제공해주는 UI View 상태 저장 - 1 (0) | 2022.05.01 |
[Android] Paging3 + Admob Native Ad (0) | 2021.11.01 |