맞춤형 뒤로 탐색 기능 제공

사용자는 뒤로 탐색 을 사용하여 화면을 통해 뒤로 이동합니다. 대부분의 Android 기기에는 뒤로 버튼 (물리적, 소프트웨어 또는 동작 기반)이 있습니다. 일반적으로 앱에 뒤로 버튼을 추가해서는 안 됩니다. 하지만 호환성 모드의 Android Automotive OS (AAOS) 기기는 시스템 뒤로 버튼을 사용합니다. 이 버튼은 탐색을 처리하므로 직접 추가할 필요가 없습니다. 자세한 내용은 AAOS 호환성 모드를 참고하세요.

Android는 사용자가 애플리케이션을 탐색할 때 대상의 백 스택 을 유지합니다. 이를 통해 일반적으로 Android를 사용하면 뒤로 버튼을 누를 때 이전 대상으로 적절하게 이동할 수 있습니다. 하지만, 최상의 사용자 환경을 제공하기 위해 앱에서 뒤로 이동하는 동작을 자체적으로 구현해야 하는 경우도 있습니다. 예를 들어, WebView를 사용할 때는 기본 뒤로 버튼 동작을 재정의하여 사용자에게 앱의 이전 화면 대신 웹 방문 기록을 통해 뒤로 이동하도록 하는 것이 좋습니다.

Compose에서 맞춤 뒤로 탐색 구현

Jetpack Compose에서는 맞춤 뒤로 탐색을 사용하여 BackHandler 컴포저블을 처리할 수 있습니다.

탐색 Compose를 사용하는 경우 일반적으로 NavController.navigateUp() 또는 NavController.popBackStack() 을 사용하여 백 스택의 이전 화면으로 이동합니다. 하지만 BackHandler는 사용자가 시스템 뒤로 버튼을 누르거나 뒤로 동작을 사용할 때 맞춤 동작을 구현하려는 경우에 유용합니다. 예를 들어 앱에 WebView를 표시하는 경우 사용자가 시스템 뒤로 버튼을 누를 때 방문 기록을 통해 뒤로 이동하도록 허용할 수 있습니다.

기본 사용법

다음은 뒤로 이벤트를 가로채 맞춤 상태 (예: 패널 축소)를 제어하는 방법을 보여주는 예입니다.

@Composable
fun MyScreen() {
    var isExpanded by remember { mutableStateOf(false) }

    // Intercept the back button or gesture only when isExpanded is true
    BackHandler(enabled = isExpanded) {
        // Collapsing the content disables this handler, so it does not
        // receive the next back event.
        isExpanded = false
    }

    if (isExpanded) {
        Text("Expanded content. Go back to collapse it.")
    } else {
        Button(onClick = { isExpanded = true }) {
            Text("Expand")
        }
    }
}

컴포저블 트리 가로채기

컴포저블 트리의 여러 수준에서 사용 설정된 BackHandler 컴포저블이 여러 개 있는 경우 가장 안쪽에 있는 컴포저블만 뒤로 이벤트를 가로챕니다.

뷰로 맞춤 뒤로 탐색 구현

ComponentActivity는 FragmentActivity 및 AppCompatActivity의 기본 클래스로, 뒤로 버튼의 동작을 제어할 수 있습니다. getOnBackPressedDispatcher()를 호출하여 가져올 수 있는 OnBackPressedDispatcher를 사용합니다.

OnBackPressedDispatcher는 뒤로 버튼 이벤트가 하나 이상의 OnBackPressedCallback 객체에 전달되는 방식을 제어합니다. OnBackPressedCallback의 생성자는 초기 사용 설정 상태에 관한 불리언을 가져옵니다. 예를 들어 콜백이 사용 설정된 경우(isEnabled()가 true를 반환하는 경우)에만 디스패처가 콜백의 handleOnBackPressed()를 호출하여 뒤로 버튼 이벤트를 처리합니다. 사용 설정 상태는 setEnabled()를 호출하여 변경할 수 있습니다.

콜백은 addCallback 메서드를 사용하여 추가됩니다. LifecycleOwner를 가져오는 addCallback() 메서드를 사용합니다. 이렇게 하면 OnBackPressedCallback은 LifecycleOwner이 Lifecycle.State.STARTED일 때만 추가됩니다. 활동은 연결된 LifecycleOwner가 소멸될 때 등록된 콜백도 삭제하므로 메모리 누수를 방지하고 활동보다 수명이 짧은 프래그먼트 또는 기타 수명 주기 소유자에서 사용하기에 적합합니다.

다음은 콜백 구현의 예입니다.

class MyFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // This callback will only be called when MyFragment is at least Started.
        val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
            // Handle the back button event
        }

        // The callback can be enabled or disabled here or in the lambda
    }
    ...
}

addCallback()을 사용하여 여러 콜백을 제공할 수 있습니다. 이렇게 하면 콜백이 추가된 역순으로 호출됩니다. 즉, 마지막으로 추가된 콜백이 뒤로 버튼 이벤트를 처리할 기회를 가장 먼저 얻습니다. 예를 들어 one, two, three라는 세 개의 콜백을 순서대로 추가한 경우 각각 three, two, one 순서로 호출됩니다.

콜백은 책임 연쇄(Chain of Responsibility) 패턴을 따릅니다. 체인의 각 콜백은 이전 콜백이 사용 설정되지 않은 경우에만 호출됩니다. 즉, 이전 예에서 콜백 two는 콜백 three가 사용 설정되지 않은 경우에만 호출됩니다. 콜백 one은 콜백 two가 사용 설정되지 않은 경우에만 호출되며 이 패턴은 추가 콜백에도 계속됩니다.

addCallback()을 사용하여 추가된 콜백은 LifecycleOwner가 Lifecycle.State.STARTED 상태가 될 때까지 책임 연쇄에 추가되지 않습니다.

콜백의 사용 설정 상태를 변경하는 것은 임시 변경에 권장됩니다. 이렇게 하면 이전에 설명한 순서가 유지되며 이는 중첩된 여러 수명 주기 소유자에 콜백이 등록된 경우 특히 중요합니다.

콜백을 완전히 삭제하려면 remove()를 호출합니다. 콜백은 연결된 LifecycleOwner가 소멸될 때 자동으로 삭제되므로 일반적으로 필요하지 않습니다.