Android/Lifecycle

Android ViewModel - Fragment가 ViewModel을 유지하는 방법

강태종 2021. 12. 29. 15:04

 

기본적으로 ViewModelProviderViewModelStoreOwner를 통해 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);
    }    
    ...    
}

FragmentViewModelStoreOwner를 상속받고 있으며 FragmentManager를 통해 ViewModelStore를 얻습니다.

FragmentManager null이거나 Lifecycle State INITIALIZED Exception을 발생합니다. 즉 Fragment attach된 후 onCreate를 호출해야 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);
        }
        ...
    }
    ...
}

FragmentManagermNonConfig를 통해 ViewModelStore를 얻고 mNonConfigFragmentManagerViewModel라는 Class입니다. mNonConfigattachController를 통해 얻게 되며 조건에 따라 얻는 방법이 다릅니다.

 

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;
    }
    ...
}

parentmNonConfig를 통해 FragmentManagerViewModel를 얻으며 HashMap으로 관리를 하며 KeyFragmentmWho를 사용합니다

 

mWho UUID를 통해 생성되는 고유값입니다.

 

2. parent가 null이고 host가 ViewModelStoreOwner인 경우

FragmentHostCallback

안드로이드 공식문서에 따르면 Fragment를 host하려면 FragmentHostCallback을 implement해야 하며 FragmentActivity같은 어떠한 객체에도 host될 수 있다고 나와있습니다.

 

여기서 의문점이 생깁니다. Activity는 Context를 상속받은 클래스며 다중 상속이 안되는 Java에서 추상 클래스인 FragmentHostCallback을 상속 받을 수 없습니다. 그렇다면 어떻게 ActivityFragmentHostCallback을 통해 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의 내부 클래스이고 FragmentActivityViewModelStore를 반환하는 것을 알 수 있었습니다. 또한 FragmentActivityFragmentController를 가지고 있기 때문에 Fragment를 host할 수 있습니다.

 

ActivityViewModelStore를 통해서 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;
    }

FragmentManagerViewModelViewModel이며 FragmentmWho 값으로 ViewModelStore를 만들거나 가져옵니다.


마무리

1.

FragmentViewModelStoreOwner를 implement 했으며 FragmentManager를 통해 ViewModelStore를 가져옵니다.

 

2. 

FragmentManagermNonConfig라는 FragmentManagerViewModel를 통해 ViewModelStore를 얻으며 mNonConfig를 얻는 방법은 3가지가 있습니다.

 

2-1.

FragmentFragment의 Host된 경우 Host한 FragmentmNonConfig를 통해 FragmentManagerViewModel를 얻고 ViewModelStore를 가져옵니다.

 

2-2.

FragmentViewModelStoreOwner에 의해 Host된 경우 Host의 ViewModelStore에서 FragmentManagerViewModel을 얻고 FragmentManagerViewModel을 통해 ViewModelStore를 가져옵니다.

 

2-3.

그 외의 경우 자체적으로 FragmentManagerViewModel을 생성합니다.

 

3.

FragmentManagerViewModelFragment mWho를 Key 값으로 사용하여 ViewModelStore를 저장하고 있으며 FragmentmWho를 통해 해당 FragmentViewModelStore를 반환합니다.

 

FragmentManagerViewModelViewModelStore를 저장하고 있으며 Host가 FragmentManagerViewModel를 제공하기 때문에 ConfigureChange가 발생해도 ViewModel을 유지할 수 있습니다.