DialogFragment
를 dismiss
하는 방법은 3가지가 존재한다. dismiss
, dismissAllowingStateLoss
, dismissNow
가 그것이다.
DialogFragment
는 Fragment
의 하위 타입이기 때문에 FragmentTransaction
을 통해 추가되고 제거된다.
dismiss
, dismissAllowingStateLoss
, dismissNow
는 FragmentTransaction
을 수행하기 위해 각각 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
을 사용해도 트랜잭션이 즉시 실행되지는 않는다. 그 대신 가능할 때 해당 트랜잭션을 수행하라고 메인 스레드에 예약해둔다. 필요할 경우 commitNow
를 사용하면 즉각적으로 UI 스레드에서 프래그먼트 트랜잭션을 수행한다.
이러한 이유로 commitNow
는 addToBackStack
과 함께 사용할 수 없다. 비슷하게 하기 위해서는 commit
을 호출한 후 executePendingTransactions
를 통해 수행되어야 할 트랜잭션을 즉시 수행하도록 하면 된다. 이 방식을 사용하면 addToBackStack
을 활용할 수 있다. 그렇지만 대부분의 경우 commit
을 사용하면 된다.
이제 commit
, commitNow
, 그리고 commitAllowingStateLoss
에 대해 알아보자.
public abstract int commit()
트랜잭션을 예약해둔다. 위에서 설명한 것처럼 즉시 수행되는 것이 아닌, 메인 스레드가 준비되었을 때 수행되도록 예약된다.
이 메서드를 통해 트랜잭션을 수행하려면 액티비티가 자신의 상태를 저장하기 이전에 수행해야 한다. 그 이후에 수행될 경우 익셉션이 발생한다.
이는 커밋 이후의 상태가 유실될 수 있기 때문이다. 액티비티가 상태 복원을 할 때 저장된 상태를 사용해야 하는데, 상태를 저장한 이후에 커밋이 수행됐기 때문이다. 만약 상태가 유실되어도 괜찮다면 commitAllowStateLoss
를 사용할 수 있다.
addToBackStack
을 통해 추가된 트랜잭션의 ID(백스택 내부에서 사용되는 ID)를 반환한다. 추가되지 않았다면 음수를 반환한다.
위에서 설명한 것처럼 액티비티 상태 저장 이후 커밋을 수행해야 할 때 사용한다. 이 커밋은 나중에 액티비티가 상태를 복원할 때 유실될 수 있다.
따라서 사용자에게 UI 상태가 예기치 않게 바뀌어도 괜찮을 때에만 사용해야 한다.
동기적으로 commit
한다. 이 메서드를 통해 추가된 프래그먼트는 호스트 액티비티의 생명주기 상태까지 초기화되고, 이 메서드를 통해 제거된 프래그먼트는 이 메서드가 끝나기 전까지 완전히 제거된다.
이 방식으로 트랜잭션을 수행하면 프래그먼트는 호스트 생명주기를 관찰하는 호스트 전용의 캡슐화된 구성요소로 추가된다. 따라서 프래그먼트가 언제 초기화되고 준비되는지 순서가 보장된다. 뷰를 가진 프래그먼트들은 뷰들도 바로 생성되어 부착된다.
commit
후 executePendingTransactions
를 호출하는 것보다 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() 하면 예외) | ❌ 안 됨 | 뷰가 즉시 필요할 때 | 자식 프래그먼트를 삽입한 후 해당 프래그먼트를 바로 참조해야 할 경우, 테스트에서 프래그먼트를 즉시 붙일 경우 등 |
Fragment transactions | App architecture | Android Developers
FragmentTransaction | API reference | Android Developers