Architecture components: how does the ViewModelProvider know which constructor to call?

I have been reading about the new architecture components that have been introduced by Android and I cannot figure out how this works:

ViewModelProviders.of(Activity).get(Class)

      

I originally thought it was calling the default constructor and returning a ViewModel object, which you then create with, for example. init () method according

public class UserProfileViewModel extends ViewModel {
    private String userId;
    private User user;

    public void init(String userId) {
        this.userId = userId;
    }
    public User getUser() {
        return user;
    }
}

      

Snippet taken from the guide: https://developer.android.com/topic/libraries/architecture/guide.html

However, later in the manual, there is this snippet:

public class UserProfileViewModel extends ViewModel {
    private LiveData<User> user;
    private UserRepository userRepo;

    @Inject // UserRepository parameter is provided by Dagger 2
    public UserProfileViewModel(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    public void init(String userId) {
        if (this.user != null) {
            // ViewModel is created per Fragment so
            // we know the userId won't change
            return;
        }
        user = userRepo.getUser(userId);
    }

      

So how does the ViewModelProvider know to call the provided constructor? Or does he see that there is only 1 constructor and calls? For example, if there were 2 constructors, what would happen?

I tried to break through the code and I found:

@Override
        public <T extends ViewModel> T create(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);
        }

      

Inside the class DefaultFactory

inside ViewModelProviders.java

. However, this confused me even more. How does it work getConstructor(Application.class)

when objects ViewModel

don't have a constructor that takes the application as an argument?

+3


source to share


1 answer


The snippet has a condition that checks if modelClass is of type AndroidViewModel

(inherits ViewModel

) that the constructor takes a parameter Application

. This is more like an exceptional case that reserves Factory

to find a constructor that matches certain parameters. This provider looks for a constructor that matches the arguments of the provider when it is created:

public class ViewModelParameterizedProvider {

    private AtomicBoolean set = new AtomicBoolean(false);

    private ViewModelStore viewModelStore = null;


    static ViewModelParameterizedProvider getProvider() {
        return new ViewModelParameterizedProvider();
    }

    @MainThread
    public static ViewModelProvider ofSupportFragment(Fragment fragment, Object... params) {
        return getProvider().of(fragment).with(params);
    }

    @MainThread
    public static ViewModelProvider ofActivity(FragmentActivity fragmentActivity, Object... params) {
        return getProvider().of(fragmentActivity).with(params);
    }

    @MainThread
    public static ViewModelProvider ofFragment(android.app.Fragment fragment, Object... params) {
        return getProvider().of(fragment).with(params);
    }

    private ViewModelParameterizedProvider of(Fragment fragment) {
        checkForPreviousTargetsAndSet();
        viewModelStore = ViewModelStores.of(fragment);
        return this;
    }

    private ViewModelParameterizedProvider of(android.app.Fragment fragment) {
        FragmentActivity fragAct = (FragmentActivity) fragment.getActivity();
        return of(fragAct);
    }

    private ViewModelParameterizedProvider of(FragmentActivity activity) {
        checkForPreviousTargetsAndSet();
        viewModelStore = ViewModelStores.of(activity);
        return this;
    }


    private ViewModelProvider with(Object... constructorParams) {
        return new ViewModelProvider(viewModelStore, parametrizedFactory(constructorParams));
    }


    private void checkForPreviousTargetsAndSet() {
        if (set.get()) {
            throw new IllegalArgumentException("ViewModelStore already has been set. Create new instance.");
        }
        set.set(true);
    }

    private ViewModelProvider.Factory parametrizedFactory(Object... constructorParams) {
        return new ParametrizedFactory(constructorParams);
    }


    private final class ParametrizedFactory implements ViewModelProvider.Factory {
        private final Object[] mConstructorParams;

        ParametrizedFactory(Object... constructorParams) {
            mConstructorParams = constructorParams;
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            if (modelClass == null) {
                throw new IllegalArgumentException("Target ViewModel class can not be null")
            }
            Log.w("ParametrizedFactory", "Don't use callbacks or Context parameters in order to avoid leaks!!")
            try {
                if (mConstructorParams == null || mConstructorParams.length == 0) {
                    return modelClass.newInstance();
                } else {
                    Class<?>[] classes = new Class<?>[mConstructorParams.length];
                    for (int i = 0; i < mConstructorParams.length; i++) {
                        classes[i] = mConstructorParams[i].getClass();
                    }
                    return modelClass.getConstructor(classes).newInstance(mConstructorParams);
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

      



Here is the kotlin version . Here's more on the topic

+2


source







All Articles