Android Compose - 화면 회전에 따른 UI 상태 관리

Published on

개요

안드로이드 개발중에 화면 회전에 따른 적절한 UI 를 제공하는 경우가 있다. 이때 UI 상태관리가 중요한데, 화면 회전이 되면 가로 세로 비율이 변하게 되고, 이에 따라 UI 가 변경되어야 한다. 안드로이드에서는 화면이 회전되면, 액티비티가 다시 시작되고, 화면이 다시 그려지게 된다. 특별한 코딩없이는 현재 화면에 보이던 것이 초기화 되어 폰을 돌리는 것 만으로 UI 에 있는 모든 것이 사라지게 될 수 있다. 화면 회전에 따른 상태 관리를 예제를 통해 알아본다.

문제

앱에서 화면 회전에 따라 적절한 UI를 제공하기 위해, 가로 보기이거나 세로 보기에 따라 다른 UI 를 적용하였다. 최초에 세로모드만 고려해서 프로토타이핑했던 UI 는 가로모드가 필요하였고 이에 따라 가로/세로 레이아웃을 다르게 적용하였다.

예를 들면, 탭뷰에서 가로 일경우 아래에 버튼이 있다면, 세로 일 경우에는 버튼이 오른쪽에 있다. 이 경우 rememberSaveable 을 사용하여 UI 상태를 저장해도, 가로 세로에 따라 두개의 UI 상태가 생기게 된다.

해결

movableContentOf

보통 rememberSaveable 은 설정 변경(화면 회전, 화면 크기) 에 의해 사라지는 UI 상태를 저장하기 위해 사용한다. 그러나 이 rememberSaveable 은 콜 사인(호출되는 코드 위치) 에 의해 상태 맵핑을 해주는데, 가로 세로에 따라 레이아웃이 달라진다면, 콜 사인이 달라지게 되고, 이에 따라 상태 맵핑이 안되어 원복이 안된다. 이럴 경우 가로 세로에 따라 UI 상태가 각각 두개의 버전이 생기는 경우가 생긴다. 이 점을 방지하기 위해서 movableContentOf 를 사용하여 같은 내용의 컨텐츠는 같은 상태를 유지하도록 할 수 있다. 이에 대해 안드로이드의 이슈 리포트에 관련 이슈(Unnatural behaviour of rememberSaveable with chages in parent composables)가 등록되어 있었다.

사용 예

아래는 두개의 타일을 가로/세로 모드에 따라 타일을 가로로 배치할지 세로로 배치할지 결정한다. Tile1 컴포즈에 counter 라는 상태가 있다면, 가로 세로 모드에 따라 counter 가 다르게 적용되어 버린다. 가로에서 한번 클릭하여 1이된 상태는 세로로 돌리면 0으로 초기화된다. 세로에서 다시 두번 클릭하면 2로 되고, 가로로 돌리면 1인 가로에서의 상태로 된다.


@Composable
fun Tile1() {
    val counter by rememberSaveable { mutableStateOf(0) }
    Button(onClick = { counter++ }) {
        Text("Tile 1: $counter")
    }
}

@Composable
fun MyApplication() {
    if (Mode.current == Mode.Landscape) {
        Row {
           Tile1()
           Tile2()
        }
    } else {
        Column {
           Tile1()
           Tile2()
        }
    }
}

이를 방지하기 위해 movableContentOf 를 사용하여 이동 가능한 컴포즈들을 정의한다. 이에 따라 Tile1Tile2 는 같은 상태를 공유하게 된다.

@Composable
fun MyApplication() {
    val tiles = remember {
        movableContentOf {
            Tile1()
            Tile2()
        }
    }
    if (Mode.current == Mode.Landscape) {
        Row { tiles() }
    } else {
        Column { tiles() }
   }
}

References