문제
- viewModel에서 LiveData를 선언하고
- Fragment에서 observe를 할 때 LifecycleOwner 인자에 this(fragment)를 넘긴다
- A fragment에서 B fragment로 replace한다(addToBackStack 설정 - 폐기되지 않고 뒤로가기 누르면 A로 돌아감)
- 뒤로가기를 통해 A fragment로 돌아가서 observe하고 있는 변수에 대해 이벤트를 발생시키면 2번 trigger 된다
https://developer.android.com/guide/fragments/lifecycle
Activity와 Fragment의 생명주기
출처 : The Android Lifecycle cheat sheet — part III : Fragments
Fragment는 하나의 Activity안에서 여러개의 Fragment를 동작할 수 있으며 Activity의 생명주기를 추종하며 보다 복잡한 생명주기를 갖습니다.
Fragment의 생명주기는 Attach 되는 Activity의 생명주기에 영향을 받습니다. 예를 들어, Activity가 일시 정지되면 그 안에 모든 Fragment도 일시 정지되며 Activity가 파괴될 때 모드 Fragment도 마찬가지로 파괴됩니다. Activity가 실행 중인 동안에는 Fragment를 추가 또는 제거를 할 수 있습니다. 이처럼 Fragment는 Fragment 자체의 생명주기를 갖기도 합니다.
Fragment에서의 LiveData 사용
class MainViewModel : ViewModel() {
private val _isUpdate = MutableLiveData<Boolean>(false)
val isUpdate: LiveData<Boolean>
get() = _isUpdate
// _isUpdate를 Private로 선언 후
// isUpdate를 따로 선언해주는 이유는 LiveData를 내부에서만 접근할 수 있도록 하기위함
// viewModel 클래스에서만 _isUpdate에 접근 가능하다
var test = MutableLiveData<String>("test")
fun setIsUpdate(b: Boolean) {
_isUpdate.postValue(b)
}
}
class FirstFragment : Fragment() {
private val viewModel: MainViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.isUpdate.observe(this) {
Log.d("로그", "[onChanged]: " + hashCode());
}
}
간단한 viewModel과 Fragment 코드이며 Fragment에서 this를 넘기며 observe하고 있습니다.
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner,
ActivityResultCaller {
....
LiveData를 observe시 사용되는 첫번째 파라미터는 LifecycleOwner 인터페이스이며, Fragment는 LifecycleOwner를 구현하고 있으므로 this로 넘길 수 있습니다.
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
...
Observe 두번 trigger
binding.btnFragment1.setOnClickListener {
supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, firstFragment)
addToBackStack(null)
commit()
}
}
binding.btnFragment2.setOnClickListener {
supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, secondFragment)
addToBackStack(null)
commit()
}
}
위 화면 1 Fragment -> 2 Fragment -> 뒤로가기 -> 1 Fragment -> 버튼클릭(setIsUpdate() 호출)
옵저빙을 중복으로 하고 있음을 알 수 있다.
문제가 되는 곳은 바로
viewModel.isUpdate.observe(this) {
위 코드는 LiveData observe의 LifecycleOwner에 해당하는 파라미터로 Fragment 자신을 넘기는 코드입니다. 다음으로 또 다른 Activity와 Fragment의 Lifecycle의 흐름을 살펴보겠습니다.
출처 : The Android Lifecycle cheat sheet — part III : Fragments
Activity or Fragment가 Resumed 되었을 때 흐름입니다. Fragment가 Destroyed 되지 않고 onCreateView가 여러 번 호출 될 수 있음을 확인할 수 있습니다.
Fragment 의 Lifecycle과 Fragment View Lifecycle
Fragment Lifecycle <-> Fragment View Lifecycle은 다르다.
- Fragment Lifecycle : Create ~ Destroy
- Fragment View Lifecycle : CreateView ~ DestroyView
주의깊게 봐야할 것은 View Lifecycle은 onDestroyView()에서 DESTROYED 된다는 점
Fragment에서의 LiveData 올바르게 사용하기
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.isUpdate.observe(viewLifecycleOwner) {
Log.d("로그", "[onChanged]: " + hashCode());
// TODO
}
위와 같이 사용한다면 replace될 때 onDestroyView에서 View Lifecycle은 폐기되기 때문에 중복으로 Observe하는 가능성을 배제시켜 준다.
꼭 onViewCreated에서 Observe 해야 하나요?
사실 onCreateView에서 사용해도 문제가 없다고 생각합니다.
void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mPerformedCreateView = true;
mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
...
위처럼 onCreateView가 불리기 전에 viewLifecycleOwner가 생성되기에 onCreateView에서 viewLifecycleOwner를 사용할 수 있습니다. 하지만 onCreate에서는.. 안되겠죠?
결론
- Fragment에서 LiveData를 사용할 때 this 대신 viewLifecycleOwner를 사용하자
- onCreate() 대신 onCreateView or onViewCreated에서 observe 하자
참고
https://uchun.dev/caution-when-using-a-fragment-viewLifecycleOwner/https://pluu.github.io/blog/android/2020/01/25/android-fragment-lifecycle/