Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM with Retrofit - How to deal with a lot of LiveData in Repository?

I'm following this tutorial on using MVVM with Retrofit

https://medium.com/@ronkan26/viewmodel-using-retrofit-mvvm-architecture-f759a0291b49

where the user places MutableLiveData inside the Repository class:

public class MovieRepository {
    private static final ApiInterface myInterface;
    private final MutableLiveData<EntityMovieOutputs> listOfMovies = new MutableLiveData<>();

private static MovieRepository newsRepository;

    public static MovieRepository getInstance(){
        if (newsRepository == null){
            newsRepository = new NewsRepository();
        }
        return movieRepository;
    }

    public MovieRepository(){
        myInterface = RetrofitService.getInterface();
    }

I'm building a simple app and what I noticed is my repository class is quickly being filled with a lot of MutableLiveData objects. Is this actually the correct way to implement MVVM, LiveData, and the Repository pattern?

enter image description here

Edit1:________________________________________________

I've created an AdminLiveData object that just holds the LiveData and has getters.

enter image description here

But how would I get reference to the ViewModel inside my AdminRepo class so I can notify the LiveData inside the ViewModel when the Retrofit Network call is complete?

private AdminService  adminService;
    
public AdminRepo(Application application) {
        BaseApplication baseApplication = (BaseApplication) application;
        RetrofitClient client = baseApplication.getRetrofitClient();
        adminService = client.getRetrofit().create(AdminService.class);  

        //AdminViewModel viewModel = (AdminViewModel) .... 
        // Not sure how to get reference to the viewmodel here so I can get the 
        // LiveData object and call postValue after the retrofit calls
}

    public void getFirstPageMembers(int offset, int limit) {
        adminService.getUsersPaginitation(offset, limit).enqueue(new Callback<List<UserInfo>>() {
            @Override
            public void onResponse(@NonNull Call<List<UserInfo>> call, @NonNull Response<List<UserInfo>> response) {
                if (response.body() != null) {
                    //firstPageLiveData.postValue(response.body());
                    //Since I create the LiveData inside the ViewModel class 
                   //instead, how do I get reference to the ViewModel's LiveData?
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<UserInfo>> call, @NonNull Throwable t) {
                //firstPageLiveData.postValue(null);
            }
        });
    }

The AdminViewModel:

public class AdminActivityViewModel extends AndroidViewModel {

    private AdminRepo repo;

    private AdminLiveData adminLiveData = new AdminLiveData();
    
    public AdminActivityViewModel(@NonNull Application application) {
        super(application);

        repo = new AdminRepo(application);
    }

How do I get reference to the AdminViewModel from inside my AdminRepo class?

like image 575
DIRTY DAVE Avatar asked Mar 04 '21 01:03

DIRTY DAVE


2 Answers

Repository and ViewModel

Repository objects in android projects should be thought of as gateways to the outer world. Communication with persistence facilities(Network, SQLite, Shared Pref) takes place in this layer. Because of that incoming data aren't supposed to conform to the android environment. For example, a string date field in incoming DTO should be converted to a date object using local date-time, you might need to save data coming from server to local DB. Additionally, other data-related tasks can be performed in this layer like caching in memory.

ViewModels represent the data that is shown in the user interface. Data in this layer should be ready to be presented on the screen. For example, a view might require data coming from two different HTTP requests, you should merge incoming data in this layer. (Still, you can separate responsibilities further. If these two requests are part of a single task or purpose, you can do this operation in the use case layer. ) From the android perspective, view models have more responsibility like preventing data from being destroyed in configuration changes. In view models, it is recommended that data should be presented to the view layer by a LiveData. It is due to fact that LiveData keeps the last state of the data and it is aware of the view layer's lifecycle (If it is used properly).

Communication between Repository and ViewModel

First of all, the repository layer mustn't be aware of the existence of any view model so you should not keep a reference of a view model in the repository layer. There are some reasons for it,

  • Most of the time, a single repository object for a set of interactions is sufficient for the whole application. If a reference for a view model is kept, it causes memory leaks.
  • If server-side API, local storage and other components that are used in the repo layer are well designed, the repository layer is less prone to changes compared to the view model.
  • The responsibility of the repository layer is fetching data from somewhere so it means that it has nothing to do with the view-related layers.

Of course, we need some kind of reference to the view model to notify it when a request is completed but we should do this implicitly in a systematic way rather than a direct reference.

Callback: It is an old fashion way of sending data back to the view model. Using callbacks extensively in your codebase causes callback hell that is not wanted. Additionally, the structured canceling mechanism is hard to implement using callbacks.

LiveData: At first glance, it seems like a great fit for this purpose but it isn't. The reasons can be listed as

  • LiveData is designed as a lifecycle-aware data holder. It is overhead for your purpose because you don't have anything to do with the view's lifecycles in this layer.
  • In most cases, data fetching operations are one-shot (like in your case) but LiveData is designed for streams.
  • It doesn't have built-in support for structured canceling.
  • From an architectural perspective, android related classes like live data should not be imported into the repository layer. Repository classes should be implemented as a simple Kotlin or java class.

In your case, it is especially bad practice because HTTP requests should not change the state of your repository object. You use LiveData as a kind of cache but there is no such requirement for this so you should avoid this. Still, if you need to use LiveData in your repo, you should either pass a MutableLiveData to your request method as a parameter so you can post the response via this LiveData or return a LiveData in your request method.

RxJava: It is one of the options. It supports one-shot requests(Single), hot streams(Subject), and cold streams(Observable). It supports structured canceling in some way(CompositeDisposable). It has a stable API and has been used commonly for years. It also makes many different network operations easier such as parallel requests, sequential requests, data manipulation, thread switching, and more with its operators.

Coroutines: It is another option and, in my opinion, the best. Although its API is not completely stable, I have used it in many different projects and I haven't seen any problem. It supports one-shot requests (suspend functions), hot streams (Channels and Stateflow), and cold streams(Flow). It is really useful in projects requiring complex data flows. It is a built-in feature in Kotlin with all operators. It supports structured concurrency in a really elegant way. It has many different operator functions and it is easier to implement new operator functions compare to RxJava. It also has useful extension functions to use with ViewModel.

To sum up, the repository layer is a gateway for incoming data, this is the first place where you manipulate data to conform requirements of your application as I have mentioned above. The operations that could be done in this layer can be listed shortly as mapping incoming data, deciding where to fetch data (local or remote source), and caching. There are many options to pass data back to class requesting the data but RxJava and Coroutines are better than others as I have explained above. In my opinion, if you are not familiar with both of them, put your effort into Coroutines.

like image 196
M.ekici Avatar answered Sep 29 '22 06:09

M.ekici


The solution you're looking for depends on how your app is designed. There are several things you can try out:

  • Keep your app modularized - as @ADM mentioned split your repository into smaller
  • Move live data out of repository - it is unnecessary to keep live data in a repository (in your case singleton) for the entire app lifecycle while there might be only a few screens that need different data.
  • That's being said - keep your live data in view models - this is the most standard way of doing it. You can take a look at this article that explains Retrofit-ViewModel-LiveData repository pattern
  • If you end up with a complicated screen and many live data objects you can still map entities into screen data representation with events / states /commands (call it as you want) which are pretty well described here. This way you have a single LiveData<ScreenState> and you just have to map your entities.

Additionally, you could use coroutines with retrofit as coroutines are recommended way now for handling background operations and have Kotlin support if you wanted to give it a try.

Also, these links might help you when exploring different architectures or solutions for handling your problem architecture-components-samples or architecture-samples (mostly using kotlin though).

like image 33
Luke Avatar answered Sep 29 '22 07:09

Luke