2
\$\begingroup\$

What I have done so far

Since I would like to rebuild a project using mvvm-pattern with the following requirements to use dagger 2 for di, RXJava for reactive, retrofit for webservices, greendao and GSON for ORM and Google's Android architecture library which seems to work great with databinding to get a clean MVVM.

For testing I created a project which has a list of items and each item has its own viewmodel to handle updates. I've used Google's sample for dependency injection.

Thats means I this tree for my files:

domain/model
domain/repository
domain/ui/datablock

The model folder stores all my pojos which extends from databindings BaseObservable. Those setters are bindable and notify for changes.

Example Pojo:

public class Group extends BaseObservable {
   @Bindable private String name;

   public void setName(String name) { 
     this.name = name;
     notifyPropertyChanged(BR.name);
   }
}

The repository directory contains several repositories which handle the data for several sources like database (greendao), memory and online (retrofit). All my viewmodels require the repository factory which shares the different repository managers as singleton using dagger (this will be a problem see below). Having just a single instance is very important since it shares the DaoSession for the database and the service for the Retrofit Client.

The UI directory contains folders for each part of the app and those view and viewmodel. In my example it's groups\GroupsFragment, groups\GroupFragment and groups\GroupsViewModel.

Both views uses the same viewmodel. Example:

public class GroupsFragment extends LifecycleFragment implements Injectable {
    private GroupsViewModel model;
    @Inject ViewModelProvider.Factory viewModelFactory;
    @Inject NavigationController navigationController;
    public View onCreateView(...) {
        model = ViewModelProviders.of(this, viewModelFactory).get(GroupsViewModel .class);
        GroupsBinding binding = DataBindingUtil.inflate(inflater, R.layout.groups, container, false, getDataBindingComponent());
        model.fetchGroups(....).subscribe(model::setGroups);
        binding.setViewModel(model);
}

Since the viewmodel needs to handle the groups and operations on a single group I've built it like that:

public class GroupsViewModel extends ViewModel {
    public ObservableArrayList<Group> groups = new ObservableArrayList<>();
    private Group currentGroup;
    private final RepositoryFactory repositoryFactory;
    @Inject GroupsViewModel (RepositoryFactory repositoryFactory) {
        this.repositoryFactory = repositoryFactory;
    }
    /**  truncated fetchGroups(), setGroups(), setCurrentGroup() method **/
}

Why do I think this is a bad idea?

Sharing viewmodels is always a great idea but in this case it may get into several issues. Those are the issues which may happen in my opinion:

  • Keeping data in memory which aren't needed. There is no need to keep the whole list in the viewmodel if I want to access the item.
  • Race conditions. Since I use RXJava to handle several threads I may get into trouble, because I have to set the current item all the time. Having listeners in the items and clicking them quickly gives wrong results.
  • Having a debounced subject for each item may get into trouble since it shares the same (RXJava) subject for each item.
  • It's very hard to read and requires lots of code into a single viewmodel.

Possible solutions

Since the attempt above doesn't seems to be very clean I have some theoretical solutions:

Solution 1:

Creating each Pojo which extends from BaseObservable and put the business logic into it. It does also mean that the Pojo needs the repository factory.

Example:

public class GroupsViewModel extends ViewModel {
  ObservableList<Group> groups = new ObservableArrayList(); 
  @Inject RepositoryFactory repo;
  public void setGroups(List<Group> groups) {
      for(Group g : groups)  g.setRepositoryFactory(repo);
  }
} 

The layout of the groupItem contains the pojo instead of the viewModel. Now I have to do repository updates in the pojo which also seems to be bad.

Solution 2:

Creating another "fake"-ViewModel which is also an Observable like this which doesn't looks good because it's just creating a new BaseObservable to handle the logic of the existing BaseObservable:

public class GroupFakeViewModelObservable extends BaseObservable {
  public final Group group;  
  public final RepositoryManager repo;
  public GroupFakeViewModelObservable(RepositoryManager repo, Group group) {
                  // handle everything in this class
         this.group = group;
         this.repo = repo;
  }
}

Solution 3:

The last idea is creating a real ViewModel and create multiple instances not using dagger/di.

Example:

public class GroupItemViewModel extends ViewModel {
    private final ObservableField<Group> group = new ObservableField<>();
    private final RepositoryFactory repositoryFactory;
    GroupItemViewModel (RepositoryFactory repositoryFactory, Grou group) {
     // since its not injectable it requires to use a constructor to get the repository and model
    }

}

and the GroupsViewModel which creates multiple instances:

public class GroupsViewModel extends ViewModel {
    ObservableList<GroupFakeViewModelObservable> groups = new ObservableArrayList(); 
    @Inject RepositoryFactory repo;

    public void setGroups(List<Group> groups) {
      for(Group g : groups)  groups.add(new GroupFakeViewModelObservable(repo, g));
     } 
} 

Final words

There is no real example which is the best way doing this. A few git examples uses the pojo to have the logic handled in the model, while others uses my attempt to mix list and single item into one viewmodel. Is there any good solution or is the architecture library not useable for this case?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.