ben
ben

Reputation: 1161

ViewModelProviders.get(...) in base class

I'm trying to cut some of Dagger's boilerplate by moving some of my ViewModel instantiation in an abstract base class but can't find quite a good way to do this. My intent is to instantiate all my ViewModels from my base fragment for them to be ready to consume by all child fragments without having them do their own instantiation. My issue lies in retrieving the ViewModel using a generic (VM)- specifically here: .get(viewModel::class.java) . I also attempted .get(VM::class.java) that is not permitted

BaseFragment

abstract class BaseFragment<VM : ViewModel> : Fragment() {

    @Inject lateinit var viewModelFactory: ViewModelProvider.Factory
    lateinit var viewModel : VM

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(viewModel::class.java)
    }
}

ViewModelProviders.get(...) method signature

public <T extends ViewModel> T get(@NonNull Class<T> modelClass)

Is this even possible?

Upvotes: 11

Views: 7468

Answers (3)

王大大
王大大

Reputation: 71

val viewModel: T by lazy {
    ViewModelProviders.of(this).get(getTClass())
}

//获取泛型T的实际类型
private fun getTClass(): Class<T> {
    return (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>
}

Upvotes: 7

kev.26
kev.26

Reputation: 1

I had the same issue but fix it with a bit of Reflection; I really don't like to use Reflection on Android, but didn't have other option.

private void initViewModel() {
    final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    mViewModel = ViewModelProviders.of(this).get((Class<M>) types[0]);
}

Hope this is useful to someone.

Upvotes: 0

LordRaydenMK
LordRaydenMK

Reputation: 13321

If you have a BaseFragment defined like this:

public class BaseFragment<T extends ViewModel> extends Fragment {

    @Inject
    protected T viewModel;

}

You can use the ViewModelUtils class from the Architecture Components's Github browser sample to create ViewModel factories.

/**
 * Creates a one off view model factory for the given view model instance.
 */
public class ViewModelUtil {
    private ViewModelUtil() {}
    public static <T extends ViewModel> ViewModelProvider.Factory createFor(T model) {
        return new ViewModelProvider.Factory() {
            @Override
            public <T extends ViewModel> T create(Class<T> modelClass) {
                if (modelClass.isAssignableFrom(model.getClass())) {
                    return (T) model;
                }
                throw new IllegalArgumentException("unexpected model class " + modelClass);
            }
        };
    }
}

In the onActivityCreated method of the BaseFragment you can add

    @Override
    public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ViewModelProvider.Factory viewModelFactory = ViewModelUtil.createFor(viewModel);
        ViewModelProviders.of(this, viewModelFactory).get(viewModel.getClass());
    }

The code for this technique is from this blog post.

Upvotes: 5

Related Questions