commit() vs commitAllowingStateLoss() vs commitNow()

Gio·2025년 5월 18일
0

서론

DialogFragmentdismiss하는 방법은 3가지가 존재한다. dismiss, dismissAllowingStateLoss, dismissNow가 그것이다.

DialogFragmentFragment의 하위 타입이기 때문에 FragmentTransaction을 통해 추가되고 제거된다.

dismiss, dismissAllowingStateLoss, dismissNowFragmentTransaction을 수행하기 위해 각각 commit, commitAllowingStateLoss, commitNow를 호출한다.

FragmentTransaction ft = getParentFragmentManager().beginTransaction();
ft.setReorderingAllowed(true);
ft.remove(this);
// allowStateLoss and immediate should not both be true
if (immediate) {
    ft.commitNow();
} else if (allowStateLoss) {
    ft.commitAllowingStateLoss();
} else {
    ft.commit();
}

commit, commitAllowingStateLoss, commitNow는 각각 무슨 차이점이 있는지 알아보자.

본론

Commit is asynchronous

commit을 사용해도 트랜잭션이 즉시 실행되지는 않는다. 그 대신 가능할 때 해당 트랜잭션을 수행하라고 메인 스레드에 예약해둔다. 필요할 경우 commitNow를 사용하면 즉각적으로 UI 스레드에서 프래그먼트 트랜잭션을 수행한다.

이러한 이유로 commitNowaddToBackStack과 함께 사용할 수 없다. 비슷하게 하기 위해서는 commit을 호출한 후 executePendingTransactions를 통해 수행되어야 할 트랜잭션을 즉시 수행하도록 하면 된다. 이 방식을 사용하면 addToBackStack을 활용할 수 있다. 그렇지만 대부분의 경우 commit을 사용하면 된다.

이제 commit, commitNow, 그리고 commitAllowingStateLoss에 대해 알아보자.

commit()

public abstract int commit()

트랜잭션을 예약해둔다. 위에서 설명한 것처럼 즉시 수행되는 것이 아닌, 메인 스레드가 준비되었을 때 수행되도록 예약된다.

이 메서드를 통해 트랜잭션을 수행하려면 액티비티가 자신의 상태를 저장하기 이전에 수행해야 한다. 그 이후에 수행될 경우 익셉션이 발생한다.

이는 커밋 이후의 상태가 유실될 수 있기 때문이다. 액티비티가 상태 복원을 할 때 저장된 상태를 사용해야 하는데, 상태를 저장한 이후에 커밋이 수행됐기 때문이다. 만약 상태가 유실되어도 괜찮다면 commitAllowStateLoss를 사용할 수 있다.

addToBackStack을 통해 추가된 트랜잭션의 ID(백스택 내부에서 사용되는 ID)를 반환한다. 추가되지 않았다면 음수를 반환한다.

commitAllowingStateLoss()

위에서 설명한 것처럼 액티비티 상태 저장 이후 커밋을 수행해야 할 때 사용한다. 이 커밋은 나중에 액티비티가 상태를 복원할 때 유실될 수 있다.

따라서 사용자에게 UI 상태가 예기치 않게 바뀌어도 괜찮을 때에만 사용해야 한다.

commitNow()

동기적으로 commit한다. 이 메서드를 통해 추가된 프래그먼트는 호스트 액티비티의 생명주기 상태까지 초기화되고, 이 메서드를 통해 제거된 프래그먼트는 이 메서드가 끝나기 전까지 완전히 제거된다.

이 방식으로 트랜잭션을 수행하면 프래그먼트는 호스트 생명주기를 관찰하는 호스트 전용의 캡슐화된 구성요소로 추가된다. 따라서 프래그먼트가 언제 초기화되고 준비되는지 순서가 보장된다. 뷰를 가진 프래그먼트들은 뷰들도 바로 생성되어 부착된다.

commitexecutePendingTransactions를 호출하는 것보다 commitNow를 호출하는 것이 권장된다. 전자는 바로 수행하고 싶은 트랜잭션 외에 남아있는 트랜잭션 모두를 수행하기 때문이다.

이 방식대로 수행된 트랜잭션들은 FragmentManager의 백 스택에 추가될 수 없다. 만약 백 스택에 추가된다면 비동기적으로 수행됐을 트랜잭션들의 순서가 깨지기 때문이다.

val ft1 = fragmentManager.beginTransaction()
ft1.add(R.id.container, FragmentA())
ft1.addToBackStack("A")
ft1.commit() // 예약됨

val ft2 = fragmentManager.beginTransaction()
ft2.add(R.id.container, FragmentB())
ft2.commitNow() // 즉시 실행됨

위와 같은 코드가 있을 때, 예상되는 순서는 FragmentA가 추가된 후 FragmentB가 추가되는 것이다. 따라서 이후 popBackStack을 하면 A로 돌아가는 것이 기대된다.

하지만 commitNow가 붙은 순간 백스택 내부에 저장된 트랜잭션들의 순서를 예상할 수 없다. 따라서 이후 popBackStack을 호출해도 어떤 작업이 수행될지가 불명확하다. 그러므로 이 메서드 호출 전 addToBackStack을 호출했다면 IllegalStateException이 발생한다.

이 메서드 또한 commit과 같은 이유로 액티비티의 상태가 저장되기 전에만 수행될 수 있다.

결론

세가지 메서드는 다음과 같이 비교할 수 있다.

메서드실행 시점백스택 추가 가능상태 저장 이후 호출 가능적절한 사용 상황상황 예시
commit()비동기 (예약됨)가능 (addToBackStack())❌ 안 됨일반적인 트랜잭션화면 전환, 탭 전환 등
commitAllowingStateLoss()비동기 (예약됨)가능 (addToBackStack())✅ 가능상태 저장 이후 트랜잭션을 수행해야 할 때액티비티가 종료될 때 다이얼로그 닫기 등
commitNow()동기 (즉시 실행)❌ 불가능 (addToBackStack() 하면 예외)❌ 안 됨뷰가 즉시 필요할 때자식 프래그먼트를 삽입한 후 해당 프래그먼트를 바로 참조해야 할 경우, 테스트에서 프래그먼트를 즉시 붙일 경우 등

REF

Fragment transactions  |  App architecture  |  Android Developers
FragmentTransaction  |  API reference  |  Android Developers

profile
틀린 부분을 지적받기 위해 업로드합니다.

0개의 댓글