365連休

にわかのandroidとかの開発メモ。

Android Studio 3.6 SavedStateViewModelFactoryがリフレクションするViewModelのコンストラクタ

前書き

筆者がぽんこつなため、この記事は一部不正確な可能性があります。
ご了承ください。

 

 

前提条件

//build.gradle
        implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

 

 

SavedStateViewModelFactoryって?

画面回転時とかの再生成に対応したViewModelを生成するためのFactoryクラス

//JavaDoc

ViewModelProvider.Factory that can create ViewModels accessing and contributing to a saved state via SavedStateHandle received in a constructor. If defaultArgs bundle was passed into the constructor, it will provide default values in SavedStateHandle. If ViewModel is instance of AndroidViewModel, it looks for a constructor that receives an Application and SavedStateHandle (in this order), otherwise it looks for a constructor that receives SavedStateHandle only.

意訳:SavedStateHandleを使ってViewModelを生成するFactoryクラス。defaultArgsがコンストラクタに渡された場合、SavedStateHandleの既定値に設定する。

 

//Fragmentで生成する時のスニペット
viewModel = new ViewModelProvider(this, new SavedStateViewModelFactory( // <--ここ! Objects.requireNonNull(getActivity()).getApplication(), //@NonNull Application application this, //@NonNull SavedStateRegistryOwner owner null //@Nullable Bundle defaultArgs ) ).get(TestViewModel.class);

 

public class TestViewModel extends ViewModel {
 
    private @NonNull MutableLiveData<String> mText;
 
    public TestViewModel(@NonNull SavedStateHandle savedStateHandle) {
        mText = savedStateHandle.getLiveData("mText", "hoge");
    }
/* こちらは使用しない
    public TestViewModel() {
        mText = new MutableLiveData<>();
        mText.setValue("hoge");
    }
*/
    public @NonNull LiveData<String> getText() {
        return mText;
    }
 
}

 

ViewModelの使い方は別記事を参照する。

neet-rookie.hatenablog.com

 

 

SavedStateViewModelFactoryがリフレクションするViewModelのコンストラクタとその優先順位

AndroidViewModel.class.isAssignableFrom(your ViewModel)の場合

MyViewModel extends AndroidViewModel

  1. constructor(Application, SavedStateHandle)
  2. constructor(Application)

※通常、AndroidViewModelを継承するが、厳密には継承しなくても実装可能。

 

それ以外の場合

MyViewModel extends ViewModel

  1. constructor(SavedStateHandle)
  2. constructor()

 

 

 該当ソースコード

SavedStateViewModelFactory.java

    ・
    ・
    ・
    
    private final ViewModelProvider.AndroidViewModelFactory mFactory;
    
    ・
    ・
    ・
    
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass); //クラスが同じインタフェースかどうか
        Constructor<T> constructor;
        if (isAndroidViewModel) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }
        
        ・
        ・
        ・
    }
    
    ・
    ・
    ・
    
    private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
            SavedStateHandle.class};
    private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

    @SuppressWarnings("unchecked")
    private static <T> Constructor<T> findMatchingConstructor(Class<T> modelClass,
            Class<?>[] signature) {
        for (Constructor<?> constructor : modelClass.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (Arrays.equals(signature, parameterTypes)) {
                return (Constructor<T>) constructor;
            }
        }
        return null;
    }

 

ViewModelProvider.java

    ・
    ・
    ・
    
    /**
     * Simple factory, which calls empty constructor on the give class.
     */
    public static class NewInstanceFactory implements Factory {

        private static NewInstanceFactory sInstance;

        /**
         * Retrieve a singleton instance of NewInstanceFactory.
         *
         * @return A valid {@link NewInstanceFactory}
         */
        @NonNull
        static NewInstanceFactory getInstance() {
            if (sInstance == null) {
                sInstance = new NewInstanceFactory();
            }
            return sInstance;
        }

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

    ・
    ・
    ・
    
    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    
        ・
        ・
        ・
    
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }

 

 

 

androidx.lifecycleのライセンス解釈
転載したソースコードにはApache 2.0 licenseが適用されている。
よって、ページ内のコード転載についてはApache 2.0 licenseにおける頒布行為にあたると解釈している。