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,
Adapter
can deal with the model layer directly if it is easily related to the "item" layer i.e., the contents of the backing List
for the RecyclerView
. 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 (e.g., 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.