简体   繁体   中英

ViewModel injection (inside View) with MVVM Architecture

This is how I create an Adapter with MVVM (+Databinding) and Dagger-2.11-rc2 :

Adapter:

public class ItemAdapter extends RecyclerView.Adapter<BindableViewHolder<ViewDataBinding>>{
    private static int TYPE_A = 0;
    private static int TYPE_B = 1;

    ...

    @Override
    public BindableViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_A) {
            return new ItemViewHolder(new ItemRowView(parent.getContext()).getBinding());
        }
        ...
    }

    @Override
    public void onBindViewHolder(BindableViewHolder holder, int position) {
        if (holder.getItemViewType() == TYPE_A) {
            ((ItemViewHolderBinding) holder.getBinding()).getViewModel().setItemModel(((ItemModel) getItem(position)));
        }        
        ...
    }

    private static class ItemViewHolder extends BindableViewHolder<ItemViewHolderBinding> {
        ItemViewHolder(ItemViewHolderBinding binding) {
            super(binding);
        }
    }
}

BindableViewHolder:

public abstract class BindableViewHolder<ViewBinding extends ViewDataBinding> extends RecyclerView.ViewHolder {

    private ViewBinding mBinding;

    public BindableViewHolder(ViewBinding binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public ViewBinding getBinding(){
        return mBinding;
    }
}

Since I'm using Dagger I wont be creating the ViewModels inside the Adapter instead they will be created (injected) inside their respective Android.View . And I guess it makes sense because my Adapter may have X Android.View types, those views can have Y ViewModel , etc...

BaseView:

public abstract class BaseView<ViewBinding extends ViewDataBinding, ViewModel extends BaseViewModel> extends FrameLayout {

    @Inject
    ViewModel mViewModel;
    protected ViewBinding mBinding;

    protected abstract void initBinding(final ViewBinding binding, final ViewModel viewModel);

    ...

    private void initView(Context context) {
        ViewInjection.inject(this);

        mBinding = DataBindingUtil...
        initBinding(mBinding, mViewModel);
        ...
    }
    ...
}

BaseViewModel :

public class BaseViewModel extends BaseObservable {...}

ItemRowView (or any View):

public class ItemRowView extends BaseView<ItemRowViewBinding, ItemRowViewModel> {

    @Inject
    ViewModelA mViewModelA;
    @Inject
    ViewModelB mViewModelB;
    ...

    @Override
    protected void initBinding(ItemRowViewBinding binding, ItemRowViewModel viewModel) {
        binding.setViewModel(viewModel);
        binding.setViewModelA(mViewModelA);
        binding.setViewModelB(mViewModelB);
        ...
    }
}

Now, this approach works fine with Activities, Fragments, etc, but when I use Views I have to create a ViewInjecton because Dagger doesn't have it out of the box. This is how I do it (read until you've reached "ViewInjection is pretty much a copy from other Injectors." )

My question(s) is(are): Is this a good approach? I'm I using MVVM and Dagger correctly? Is there any better way to achieve this without creating ViewInjecton (and using Dagger-2.11)?

Thanks for your time.

ps: I've used the Adapter example but this approach is the same if I want to use Views instead of Fragments. With Adapters you are restricted to Views.

There has already been some discussion about whether one should inject inside Views or not in this question .

Since I'm using Dagger I wont be creating the ViewModels inside the Adapter instead they will be created (injected) inside their respective Android.View. And I guess it makes sense because my Adapter may have X Android.View types, those views can have Y ViewModel, etc...

I personally find this a little problematic and if I was working on a team with that code I would prefer a greater degree of separation between layers. At least,

  1. There should be a clear model layer (that is retrieved from a repository or from the cloud for instance). These should be mere data objects.
  2. The Adapter can deal with the model layer directly if it is easily related to the "item" layer ie, the contents of the backing List for the RecyclerView .
  3. The ViewModel for the RecyclerView.ViewHolder should be extremely lightweight and not need injection. It should essentially be a bag of properties that easily translate into some property of the view (eg, setText() , setColor() ) and can be get/set. These can be created using the new keyword inside the onBindViewHolder method in the adapter. If this is difficult, you could extract a Factory ( ViewModelFactory ) and inject that as a dependency for your Adapter.

In short, the model data objects should be "dumb". The same goes for the ViewModel for the individual ViewHolder . The Adapter can be "intelligent" and can take tested "intelligent" dependencies (such as a ViewModelFactory if necessary) and this Adapter can be injected into your Activity or Fragment using Dagger 2.

While I agree with David's answer that this should not be done, if you still want to do this, it is possible by going through the activity:

override val activity: FragmentActivity by lazy {
    try {
        context as FragmentActivity
    } catch (exception: ClassCastException) {
        throw ClassCastException("Please ensure that the provided Context is a valid FragmentActivity")
    }
}
override var viewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)

This is discussed in more detail here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM