안드로이드 onBackPressed() Deprecated 대응: OnBackPressedDispatcher 완벽 전환 가이드

Prompt Architect 편집팀 · 2026-06-18 · 7분

TL;DR — API 33부터 deprecated된 onBackPressed()를 OnBackPressedDispatcher와 OnBackPressedCallback으로 안전하게 전환하는 방법을 단계별 예제와 실무 주의사항까지 정리했습니다.

안드로이드 앱을 유지보수하다 보면 어느 순간 onBackPressed() 메서드에 취소선이 그어지고, IDE가 "이 메서드는 deprecated 되었습니다"라는 경고를 띄우는 경험을 하게 됩니다. 빌드는 여전히 되지만, 타깃 SDK를 33(Android 13) 이상으로 올리는 순간 이 경고는 무시하기 어려운 신호가 됩니다. 이 글에서는 왜 onBackPressed()가 사라지는 방향으로 가고 있는지, 그리고 권장 방식인 OnBackPressedDispatcher로 어떻게 매끄럽게 전환하는지를 실무 관점에서 정리합니다.

1. 문제 상황

기존에는 사용자가 뒤로 가기 버튼을 눌렀을 때의 동작을 제어하려면 Activity에서 onBackPressed()를 오버라이드하는 것이 일반적이었습니다.

// 과거의 전형적인 방식 — 이제는 권장되지 않음
override fun onBackPressed() {
    if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
        // 서랍이 열려 있으면 닫기만 하고 화면은 유지
        drawerLayout.closeDrawer(GravityCompat.START)
    } else {
        // 그 외에는 기본 뒤로 가기 동작
        super.onBackPressed()
    }
}

이 코드는 Android 13(API 33) 이상을 타깃으로 잡으면 deprecated 경고를 받습니다. 동작 자체가 즉시 멈추는 것은 아니지만, 향후 OS 버전에서 예측 가능한 뒤로 가기(predictive back gesture) 같은 새 기능과 충돌하거나 정상적으로 연동되지 않을 수 있습니다.

2. 왜 deprecated 되었나

구글이 onBackPressed()를 단계적으로 폐기하는 이유는 크게 두 가지로 정리할 수 있습니다.

  • 수명 주기(Lifecycle) 인지 부족: 기존 방식은 Activity 단위로만 동작을 가로챘기 때문에, 여러 Fragment나 컴포넌트가 각자 뒤로 가기 동작을 원할 때 깔끔하게 책임을 나누기 어려웠습니다. 어느 화면이 활성 상태인지에 따라 동작을 켜고 끄는 작업이 수동적이고 오류가 잦았습니다.
  • 예측 가능한 뒤로 가기 대응: Android 13부터 도입된 predictive back gesture(뒤로 가기 제스처를 하는 동안 다음 화면을 미리 보여주는 기능)는 시스템이 "이 화면이 뒤로 가기를 가로챌 것인지"를 미리 알아야 합니다. OnBackPressedDispatcher 기반 콜백은 이 정보를 시스템에 제공할 수 있지만, 직접 오버라이드한 onBackPressed()는 그렇지 못합니다.

즉, 단순한 메서드 교체가 아니라 뒤로 가기 처리 모델 자체를 더 선언적이고 생명 주기 친화적으로 바꾸는 전환입니다.

3. 핵심 개념: 역할의 분리

새 방식은 두 개의 구성 요소로 나뉩니다.

  • OnBackPressedCallback: "뒤로 가기를 눌렀을 때 무엇을 할지"를 담는 콜백입니다. 생성자에 전달하는 boolean 값으로 이 콜백의 활성화 여부를 제어합니다.
  • OnBackPressedDispatcher: 등록된 여러 콜백을 관리하고, 뒤로 가기 이벤트가 발생했을 때 어떤 콜백을 실행할지 결정하는 디스패처입니다. Activity/Fragment 어디서든 onBackPressedDispatcher로 접근할 수 있습니다.

여러 콜백이 등록되어 있으면 LIFO(나중에 등록된 것이 먼저) 순서로 처리됩니다. 즉, 가장 최근에 추가된 활성 콜백이 우선권을 가집니다.

4. 단계별 전환 방법

  1. OnBackPressedCallback 객체를 생성하고 활성화 여부(true/false)를 정합니다.
  2. handleOnBackPressed()를 오버라이드하여 원하는 동작을 작성합니다.
  3. onBackPressedDispatcher.addCallback(lifecycleOwner, callback)으로 등록합니다. 이때 LifecycleOwner를 함께 넘기면 해당 컴포넌트의 생명 주기에 맞춰 콜백이 자동으로 정리됩니다.

5. 실전 코드 예제

5-1. Activity에서 사용

class CheckoutActivity : AppCompatActivity() {

    // 활성 상태(true)로 시작하는 뒤로 가기 콜백 정의
    private val backCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // 결제 화면에서 바로 닫지 않고 확인 다이얼로그를 띄운다
            showExitConfirmDialog()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_checkout)

        // 생명 주기 소유자(this)와 함께 콜백을 디스패처에 등록
        onBackPressedDispatcher.addCallback(this, backCallback)
    }

    private fun showExitConfirmDialog() {
        AlertDialog.Builder(this)
            .setMessage("작성 중인 내용이 사라집니다. 나가시겠어요?")
            .setPositiveButton("나가기") { _, _ ->
                // 콜백을 잠시 끄고 시스템 기본 동작으로 위임
                backCallback.isEnabled = false
                onBackPressedDispatcher.onBackPressed()
            }
            .setNegativeButton("계속 작성", null)
            .show()
    }
}

5-2. Fragment에서 사용

Fragment에서는 viewLifecycleOwner를 넘기는 것이 핵심입니다. 그래야 뷰가 파괴될 때 콜백도 함께 해제되어 메모리 누수나 중복 실행을 방지할 수 있습니다.

class EditorFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // requireActivity()의 디스패처에 Fragment 뷰의 생명 주기로 콜백 등록
        requireActivity().onBackPressedDispatcher.addCallback(
            viewLifecycleOwner,
            object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    if (hasUnsavedChanges) {
                        promptSaveBeforeLeaving()
                    } else {
                        // 변경 사항이 없으면 콜백을 끄고 정상적으로 뒤로
                        isEnabled = false
                        requireActivity().onBackPressedDispatcher.onBackPressed()
                    }
                }
            }
        )
    }
}

5-3. Jetpack Compose에서 사용

Compose 환경이라면 BackHandler 컴포저블을 쓰는 것이 가장 간결합니다. 내부적으로 동일한 디스패처를 사용합니다.

@Composable
fun FormScreen(onConfirmExit: () -> Unit) {
    var showDialog by remember { mutableStateOf(false) }

    // enabled가 true인 동안 뒤로 가기를 가로챈다
    BackHandler(enabled = true) {
        showDialog = true
    }

    if (showDialog) {
        ExitConfirmDialog(
            onDismiss = { showDialog = false },
            onConfirm = onConfirmExit
        )
    }
}

6. 흔한 실수와 엣지 케이스

  • 항상 isEnabled = true로 고정: 콜백을 항상 활성 상태로 두면 사용자가 화면을 절대 빠져나갈 수 없는 함정이 생깁니다. 조건에 따라 isEnabled를 동적으로 토글하세요.
  • LifecycleOwner를 누락: addCallback(callback)처럼 생명 주기 소유자 없이 등록하면 콜백이 자동 해제되지 않아 누수가 발생할 수 있습니다. 가능하면 항상 owner를 함께 넘깁니다.
  • Fragment에서 this를 owner로 사용: Fragment는 viewLifecycleOwner를 써야 합니다. Fragment 자체(this)를 넘기면 뷰가 재생성될 때 콜백이 중복 등록될 수 있습니다.
  • super.onBackPressed() 습관: 새 방식에서는 기본 동작으로 위임하고 싶을 때 콜백을 비활성화한 뒤 dispatcher.onBackPressed()를 호출하는 패턴을 사용합니다.
  • LIFO 순서 혼동: 여러 화면이 콜백을 등록하면 가장 마지막에 등록된 활성 콜백이 먼저 실행됩니다. 의도하지 않은 화면이 이벤트를 먹는다면 등록 순서와 활성화 상태를 점검하세요.

7. 요약

onBackPressed() 오버라이드는 더 이상 권장되지 않으며, OnBackPressedDispatcherOnBackPressedCallback 조합이 표준입니다. 핵심은 (1) 콜백으로 동작을 정의하고, (2) 생명 주기 소유자와 함께 디스패처에 등록하며, (3) isEnabled로 활성화를 제어한다는 세 가지입니다. Compose라면 BackHandler 하나로 끝납니다. 이 전환은 단순한 메서드 교체가 아니라 predictive back gesture 같은 최신 OS 기능과 호환되는 더 견고한 구조로 가는 길입니다.

AI에게 물어볼 때 (프롬프트 팁)

이 주제를 ChatGPT나 Claude 같은 AI에게 물을 때, 막연히 "onBackPressed 어떻게 바꿔?"라고 하면 오래된 답이나 일반론이 돌아오기 쉽습니다. 맥락과 제약을 명시하면 훨씬 정확한 답을 얻습니다. (이런 프롬프트 설계는 Prompt Architect의 분석 기준과도 맞닿아 있습니다.)

  • 프롬프트 예시 1 (마이그레이션 변환): "아래 Kotlin Activity 코드는 deprecated된 onBackPressed()를 오버라이드하고 있어. 이걸 OnBackPressedDispatcher + OnBackPressedCallback 방식으로 변환하고, viewLifecycleOwner 사용 여부와 isEnabled 토글 로직을 주석으로 설명해줘. 타깃 SDK는 34야. [코드 붙여넣기]"

  • 프롬프트 예시 2 (엣지 케이스 점검): "Fragment 3개를 백스택에 쌓는 구조에서 각 Fragment가 OnBackPressedCallback을 등록해. LIFO 순서 때문에 발생할 수 있는 충돌 시나리오 3가지와, 각 상황을 방지하는 코드 패턴을 표로 정리해줘."

  • 프롬프트 예시 3 (Compose 전환): "기존 XML+Activity 기반의 뒤로 가기 확인 다이얼로그 로직을 Jetpack Compose의 BackHandler로 옮기려고 해. 상태(state) 관리와 enabled 조건을 포함해서 권장 패턴을 보여주고, BackHandler를 쓸 때 흔히 하는 실수도 함께 알려줘."

이렇게 현재 코드, 타깃 SDK, 사용 환경(Activity/Fragment/Compose), 원하는 출력 형식을 함께 제시하면 AI가 추측 대신 정확한 변환을 내놓습니다.