Android ViewModel - Fragment가 ViewModel을 유지하는 방법
기본적으로 ViewModelProvider는 ViewModelStoreOwner를 통해 ViewModelStore를 얻고 ViewModelStore를 통해 ViewModel을 얻게 됩니다. ViewModelProvider의 내부 구조는 아래 글을 통해 확인할 수 있습니다.
2021.12.06 - [Android/Lifecycle] - Android ViewModel - Activity가 ViewModel을 유지하는 방법
Android ViewModel - Activity가 ViewModel을 유지하는 방법
private val mainViewModel by lazy { ViewModelProvider(requireActivity()).get(MainViewModel::class.java) } 기본적으로 ViewModel을 생성할 때 ViewModelProvider를 생성하고 get()함수를 통해 ViewModel을..
taetae98.tistory.com
Fragment
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner,
ActivityResultCaller {
...
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
if (getMinimumMaxLifecycleState() == Lifecycle.State.INITIALIZED.ordinal()) {
throw new IllegalStateException("Calling getViewModelStore() before a Fragment "
+ "reaches onCreate() when using setMaxLifecycle(INITIALIZED) is not "
+ "supported");
}
return mFragmentManager.getViewModelStore(this);
}
...
}
Fragment는 ViewModelStoreOwner를 상속받고 있으며 FragmentManager를 통해 ViewModelStore를 얻습니다.
FragmentManager
public abstract class FragmentManager implements FragmentResultOwner {
...
private FragmentManagerViewModel mNonConfig;
...
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
@SuppressWarnings("deprecation")
@SuppressLint("SyntheticAccessor")
void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
...
// Get the FragmentManagerViewModel
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
...
}
...
}
FragmentManager는 mNonConfig를 통해 ViewModelStore를 얻고 mNonConfig는 FragmentManagerViewModel라는 Class입니다. mNonConfig는 attachController를 통해 얻게 되며 조건에 따라 얻는 방법이 다릅니다.
1. parent가 null이 아닌 경우
@NonNull
private FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
return mNonConfig.getChildNonConfig(f);
}
final class FragmentManagerViewModel extends ViewModel {
...
private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
...
@NonNull
FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
if (childNonConfig == null) {
childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
mChildNonConfigs.put(f.mWho, childNonConfig);
}
return childNonConfig;
}
...
}
parent의 mNonConfig를 통해 FragmentManagerViewModel를 얻으며 HashMap으로 관리를 하며 Key는 Fragment의 mWho를 사용합니다
2. parent가 null이고 host가 ViewModelStoreOwner인 경우
안드로이드 공식문서에 따르면 Fragment를 host하려면 FragmentHostCallback을 implement해야 하며 Fragment는 Activity같은 어떠한 객체에도 host될 수 있다고 나와있습니다.
여기서 의문점이 생깁니다. Activity는 Context를 상속받은 클래스며 다중 상속이 안되는 Java에서 추상 클래스인 FragmentHostCallback을 상속 받을 수 없습니다. 그렇다면 어떻게 Activity는 FragmentHostCallback을 통해 Fragment를 hosting할 수 있을까요? 또한 FragmentHostCallback은 어떻게 ViewModelStoreOwner를 implement 했을까요?
FragmentManager에서 FragmentHostCallback이 제공되는 attachController 함수를 디버깅 했습니다.
public class FragmentActivity extends ComponentActivity implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
...
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
...
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
ViewModelStoreOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
FragmentOnAttachListener {
public HostCallbacks() {
super(FragmentActivity.this /*fragmentActivity*/);
}
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return FragmentActivity.this.getViewModelStore();
}
host는 HostCallbacks 클래스이며, FragmentActivity의 내부 클래스이고 FragmentActivity의 ViewModelStore를 반환하는 것을 알 수 있었습니다. 또한 FragmentActivity는 FragmentController를 가지고 있기 때문에 Fragment를 host할 수 있습니다.
즉 Activity의 ViewModelStore를 통해서 FragmentManagerViewModel를 얻습니다.
FragmentManagerViewModel
final class FragmentManagerViewModel extends ViewModel {
...
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
...
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
FragmentManagerViewModel은 ViewModel이며 Fragment의 mWho 값으로 ViewModelStore를 만들거나 가져옵니다.
마무리
1.
Fragment는 ViewModelStoreOwner를 implement 했으며 FragmentManager를 통해 ViewModelStore를 가져옵니다.
2.
FragmentManager는 mNonConfig라는 FragmentManagerViewModel를 통해 ViewModelStore를 얻으며 mNonConfig를 얻는 방법은 3가지가 있습니다.
2-1.
Fragment가 Fragment의 Host된 경우 Host한 Fragment의 mNonConfig를 통해 FragmentManagerViewModel를 얻고 ViewModelStore를 가져옵니다.
2-2.
Fragment가 ViewModelStoreOwner에 의해 Host된 경우 Host의 ViewModelStore에서 FragmentManagerViewModel을 얻고 FragmentManagerViewModel을 통해 ViewModelStore를 가져옵니다.
2-3.
그 외의 경우 자체적으로 FragmentManagerViewModel을 생성합니다.
3.
FragmentManagerViewModel은 Fragment mWho를 Key 값으로 사용하여 ViewModelStore를 저장하고 있으며 Fragment의 mWho를 통해 해당 Fragment의 ViewModelStore를 반환합니다.
즉 FragmentManagerViewModel이 ViewModelStore를 저장하고 있으며 Host가 FragmentManagerViewModel를 제공하기 때문에 ConfigureChange가 발생해도 ViewModel을 유지할 수 있습니다.