|

FragmentTransaction commit vs commitNow 차이: 안드로이드에서 타이밍 문제를 왜 자주 만나게 될까

FragmentTransaction commit과 commitNow 차이를 설명하는 안드로이드 대표 이미지
이 주제의 핵심은 더 빠른 commit이 아니라 언제 transaction 결과를 확정할 것인가다

FragmentTransaction commit vs commitNow 차이는 Fragment를 실무에서 다루다 보면 반드시 한 번은 만나게 됩니다. 결론부터 말하면 commit()은 예약된 비동기 commit이고 commitNow()는 즉시 실행되는 동기 commit입니다.

이번 글에서는 왜 commit이 바로 반영되지 않는지, commitNow가 언제 의미가 있고 왜 제약이 많은지, executePendingTransactions와는 무엇이 다른지, state loss 문제는 어떻게 이해해야 하는지 단계적으로 정리하겠습니다.

FragmentTransaction commit vs commitNow 차이: 안드로이드에서 타이밍 문제를 왜 자주 만나게 될까 핵심 요약 카드
핵심 판단 기준을 먼저 잡고 읽으면 본문이 훨씬 쉬워집니다.

먼저 결론

  • commit()은 transaction을 즉시 실행하지 않고 main thread에 예약한다
  • commitNow()는 호출이 끝나기 전에 transaction을 동기 실행한다
  • commitNow()는 addToBackStack()과 함께 쓸 수 없다
  • commit() 뒤 executePendingTransactions()와 commitNow()는 같은 의미가 아니다
  • state saved 이후 commit 계열 메서드는 예외와 state loss 위험을 같이 봐야 한다

한 줄로 줄이면 대부분의 화면 전환은 비동기 예약 모델로 이해하고, 정말 순서가 즉시 확정되어야 하는 좁은 장면에서만 commitNow를 검토한다고 보면 됩니다.


commit vs commitNow에서 먼저 봐야 할 것: commit은 왜 바로 반영되지 않을까

공식 API reference에 따르면 commit()은 transaction을 즉시 실행하지 않고 main thread가 다음에 준비되었을 때 수행될 work로 스케줄합니다. 그래서 commit 직후 fragment가 완전히 붙어 있을 것이라고 기대하면 타이밍 문제를 만나기 쉽습니다.

supportFragmentManager.beginTransaction()
    .replace(R.id.container, DetailFragment())
    .commit()

val fragment = supportFragmentManager.findFragmentById(R.id.container)

이 코드는 문법상 문제는 없어 보여도, commit의 예약 모델을 잊으면 결과를 너무 이르게 읽는 실수를 만들 수 있습니다.


commitNow는 무엇이 다른가

commitNow()는 transaction을 동기적으로 커밋합니다. 공식 문서는 호출이 반환되기 전에 추가된 fragment가 host의 lifecycle state까지 초기화되고, view를 관리하는 fragment는 view가 생성되고 attach될 수 있다고 설명합니다. 즉시 순서 보장이 필요할 때만 의미가 있습니다.

supportFragmentManager.beginTransaction()
    .add(R.id.container, ChildFragment(), "child")
    .commitNow()

val child = supportFragmentManager.findFragmentByTag("child") as ChildFragment

왜 commitNow를 기본값처럼 쓰면 안 될까

commitNow()는 더 강한 ordering guarantee를 주는 대신 제약도 강합니다. 일반적인 화면 전환은 사용자 입력, back stack, lifecycle 흐름과 함께 움직이기 때문에 비동기 예약 모델인 commit()이 더 자연스러운 경우가 많습니다. commitNow()를 기본값처럼 쓰면 설계 단순화보다 제약만 늘어날 수 있습니다.


addToBackStack과 같이 못 쓰는 이유

supportFragmentManager.beginTransaction()
    .replace(R.id.container, DetailFragment())
    .addToBackStack(null)
    .commitNow() // IllegalStateException

공식 문서는 commitNow()로 커밋한 transaction은 back stack에 추가될 수 없다고 설명합니다. addToBackStack()을 이미 호출했다면 IllegalStateException이 발생합니다. 핵심은 동기 실행이 다른 비동기 transaction들과의 ordering guarantee를 깨뜨릴 수 있다는 점입니다.


executePendingTransactions와는 무엇이 다른가

공식 문서는 commit() 후 executePendingTransactions()를 호출하는 것보다 commitNow()가 더 바람직하다고 설명합니다. 이유는 executePendingTransactions()가 현재 pending transaction 전체를 처리하려고 시도하는 부작용이 있기 때문입니다. 즉, 둘은 비슷해 보여도 범위와 의도가 다릅니다.


state loss는 어떻게 이해해야 할까

commit()과 commitNow() 모두 activity가 state를 저장하기 전에만 안전하게 커밋할 수 있습니다. 그 이후에는 예외가 날 수 있고, state loss 허용 메서드를 쓰면 예외 대신 UI 상태 유실 가능성을 감수하게 됩니다. 그래서 allowingStateLoss 계열 메서드는 일반 권장 패턴이 아니라 trade-off를 감수하는 선택으로 이해해야 합니다.


실전 체크리스트

  1. 이 transaction은 즉시 끝나 있어야 하나
  2. back stack에 남겨야 하나
  3. transaction 직후 fragment나 view 상태를 반드시 읽어야 하나
  4. pending 전체를 밀어도 되는 상황인가
  5. 이미 state saved 이후는 아닌가
  6. 예외를 피하려고 state loss 허용 메서드로 도망가고 있지는 않은가

정리

FragmentTransaction에서 commit과 commitNow의 차이는 문법보다 타이밍 모델에 있습니다. 마지막으로 핵심만 다시 적으면 commit은 예약된 비동기 commit이고, commitNow는 back stack 없이 순서를 즉시 확정해야 하는 제한된 장면에서만 검토하는 동기 commit입니다.

공식 참고 문서는 FragmentTransaction API reference입니다.

함께 보면 좋은 글로는 Fragment Result API 쉽게 이해하기, Navigation Component와 FragmentManager 차이가 있습니다.

세부 제약은 Android Developers FragmentTransaction API reference를 같이 보는 편이 가장 정확합니다.

함께보면 좋은 글