5

I'm trying to learn MVVM to make my app's architecture more clean. But I'm having a hard time grasping how to create a "Domain" layer for my app.

Currently this is how the structure of my project is looking:

My View is the activity. My ViewModel has a public method that the activity can call. Once the method in the ViewModel is called, it calls a method in my Repository class which performs a network call, which then returns the data back to the ViewModel. I then update the LiveData in the ViewModel so the Activity's UI is updated.

This is where I'm confused on how to add a Domain layer to the structure. I've read a lot of Stackoverflow answers and blogs about the Domain layer and they mostly all tell you to remove all the business logic from the ViewModel and make a pure Java/Kotlin class.

So instead of

View --> ViewModel --> Repository

I would be communicating from the ViewModel to the Domain class and the Domain class would communicate with the Repository?

View --> ViewModel --> Domain --> Repository

I'm using RxJava to make the call from my ViewModel to the Repository class.

@HiltViewModel
public class PostViewModel extends ViewModel {

    private static final String TAG = "PostViewModel";

    private final List<Post>                  listPosts              = new ArrayList<>();
    private final MutableLiveData<List<Post>> getPostsLiveData       = new MutableLiveData<>();
    private final MutableLiveData<Boolean>    centerProgressLiveData = new MutableLiveData<>();
    private final MainRepository              repository;

    @Inject
    public PostViewModel(MainRepository repository) {
        this.repository = repository;
        getSubredditPosts();
    }

    public void getSubredditPosts() {
        repository.getSubredditPosts()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Response>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                        centerProgressLiveData.setValue(true);
                    }

                    @Override
                    public void onNext(@NonNull Response response) {
                        Log.d(TAG, "onNext: Query called");
                        centerProgressLiveData.setValue(false);
                        listPosts.clear();
                        listPosts.addAll(response.getData().getChildren());
                        getPostsLiveData.setValue(listPosts);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Log.e(TAG, "onError: getPosts", e);
                        centerProgressLiveData.setValue(false);
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }
public class MainRepository {

    private final MainService service;

    @Inject
    public MainRepository(MainService service) {
        this.service = service;
    }

    public Observable<Response> getSubredditPosts() {
        return service.getSubredditPosts();
    }
}

Could someone please give me an example of how I could do it? I'm quite lost here

Jesse
  • 3,243
  • 1
  • 22
  • 29
DIRTY DAVE
  • 2,523
  • 2
  • 20
  • 83
  • As far as I know the Domain layer is usually comprised of use cases and entities. So the missing link seems to be use cases. So your ViewModel shouldn't call repositories directly it should call use cases. The use cases should contain business logic and should access repositories and process the data coming from the repositories. – Razvan S. Dec 14 '21 at 18:38
  • The main problem I'm running to is I don't know how to design the ```UseCase``` class with RxJava so it interacts with both the ```ViewModel``` and ```Repository``` class – DIRTY DAVE Dec 14 '21 at 22:13

2 Answers2

3

I had a hard time while trying to figure out the domain layer. The most common example of it is the use case.

Your viewmodel won't communicate directly to the repository. As you said, you need viewmodel 》domain 》repository.

You may think of a usecase as a abstraction for every repository method.

Let's say you have a Movies Repository where you call a method for a movie list, another method for movie details and a third method for related movies.

You'll have a usecase for every single method.

What's the purpose of it?

Let's say you have a DetailActivity that communicate with a Detail Viewmodel. Your viewmodel doesn't need to know all the repository (what's the purpose of calling a movie list method on you Detail screen?). So, all your DetailViewModel will know is "Detail Usecase " (that calls the Detail method in repository).

Google has updated the architecture documentation few hours ago, take a look! https://android-developers.googleblog.com/2021/12/rebuilding-our-guide-to-app-architecture.html?m=1&s=09

PS: Usecase is not a special android class, you do not need to inherent any behavior (as fragment, activity, viewmodel...) it's a normal class that will receive the repository as parameter.

You'll have something like:

Viewmodel:

function createPost(post Post){
   createUseCase.create(post)
}

UseCase

function createPost(post Post): Response {
   return repository.create(post)
}
Filipe Oliveira
  • 850
  • 7
  • 13
  • ```You'll have a usecase for every single method.``` Is this really the case? If my viewmodel has 6 methods for example -- Reading, Creating, and Updating to a Remote data source and Room I would need 6 extra classes? In a real scenario I feel like there would even be more than 6 methods in a viewmodel. – DIRTY DAVE Dec 15 '21 at 01:24
  • So I would inject the repository into all 6 of those use case classes, and make the RxJava call for each one there? If that is the case, in my above code, what would I make the ```return type``` for the method ```getSubredditPosts()``` inside the ```UseCase``` class? – DIRTY DAVE Dec 15 '21 at 01:33
  • That's it. If you have 6 methods in your repository, you'll probably have 6 usecases. (Not a "follow to death" rule, you may face a scenario where you won't need 1 usecase for every method, but it's not what happens more often). It's also a chance to "re-check" your code. ViewModel doesn't need to know the datasource (local or remote). Repository does it. It seems you'll need 3 usecases: read, create and update. If you need to do it remotely and locally, it's not viewmodel concern. Viewmodel will call "createUseCase", that will call create method on repository – Filipe Oliveira Dec 15 '21 at 01:42
  • And, if you need to do locally and remotely, call remoteDataSource and LocalDataSource from your repository. The return Object is the object you return from repository. If your create method from repository returns Movies, your usecase method will return the same type. Your createUseCase method will look like "return repository.create()". – Filipe Oliveira Dec 15 '21 at 01:43
  • Edited answer with more info – Filipe Oliveira Dec 15 '21 at 01:50
  • Where would my RxJava call be -- in ViewModel, UseCase, or Repository? In my code above, I need to handle 3 of the RxJava states ```OnSubscribe, OnNext, OnError``` with LiveData to update the progressbars. This is where it's confusing me on how to structure it. Is it possible to give me an RxJava example? – DIRTY DAVE Dec 15 '21 at 01:53
1

I spent quite a bit of time trying to learn how to add a domain layer using RxJava by reading a lot of blogs and Stackoverflow answers, but all of them were missing the conversion of the response from the api call to what you'd like to display on screen (For example if the back end returns a username dave123 and you'd like to display by dave123).

I finally figured it out and the secret sauce was to use a RxJava .map() operator inside the UseCase class. I also decided to keep the RxJava call inside my ViewModel.

So in my Repository class I have a method that calls the Api and returns a type of Single<Response>. This is the raw json data the Api returns.

public class MainRepository {

    private final MainService service;
    private final PostDao postDao;

    @Inject
    public MainRepository(MainService service, PostDao postDao) {
        this.service = service;
        this.postDao = postDao;
    }

    public Single<Response> getResponse() {
        return service.getSubredditPosts();
    }
}

Inside my GetPostsUseCase class, I'm call the getResponse() method from the MainRepository and altering the Response by performing business logic on it (the stuff I want to display on the UI. In this case I add the String "by " to the username)

And the secret or the part I had alot of trouble understanding/figuring out how to do was converting the Type inside the Single<>. I used the .map() operator to change the return type and filter the Response to a List<Post>

public class GetPostsUseCase {

    private final MainRepository mainRepository;

    @Inject
    public GetPostsUseCase(MainRepository mainRepository) {
        this.mainRepository = mainRepository;
    }

    public Single<List<Post>> getSubredditPosts(){
        return mainRepository.getResponse().map(response ->
                getPostsFromResponse(response.getData().getChildren())
        );
    }

    private List<Post> getPostsFromResponse(List<Child> listChildren) {
        List<Post> listPosts = new ArrayList<>();
        for (Child child : listChildren) {
            Post post = child.getPost();
            post.setCreatedBy("by " + post.getUsername());
            listPosts.add(post);
        }
        return listPosts;
    }
}

And this is how my ViewModel looks like

public class PostViewModel extends ViewModel {

    private static final String TAG = "PostViewModel";

    private final List<Post>                  listPosts              = new ArrayList<>();
    private final MutableLiveData<List<Post>> getPostsLiveData       = new MutableLiveData<>();
    private final MutableLiveData<Boolean>    centerProgressLiveData = new MutableLiveData<>();
    private final GetPostsUseCase             getPostsUseCase;

    @Inject
    public PostViewModel(GetPostsUseCase getPostsUseCase) {
        this.getPostsUseCase = getPostsUseCase;
        getSubredditPosts();
    }

    public void getSubredditPosts() {
        getPostsUseCase.getSubredditPosts()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<List<Post>>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                        centerProgressLiveData.setValue(true);
                    }

                    @Override
                    public void onSuccess(@NonNull List<Post> list) {
                        Log.d(TAG, "onNext: Query called");
                        centerProgressLiveData.setValue(false);
                        listPosts.clear();
                        listPosts.addAll(list);
                        getPostsLiveData.setValue(listPosts);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        centerProgressLiveData.setValue(false);
                    }
                });
    }

I couldn't find any blogposts or answers that had an example like this. Hopefully this helps anyone out there who is struggling to learn how to implement clean architecture with MVVM, Hilt, RXJava and a Domain layer.

If I did do something incorrectly or not considered clean architecture please let me know.

DIRTY DAVE
  • 2,523
  • 2
  • 20
  • 83