Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: clean architecture with Room database and LiveData in DAO

I'm trying to apply clean-architecture approach to my project (Link: guide I'm currently referencing).

I'm using Room database for local storage and I want it to be the single source of data in the application - this means that all data gathered from network calls first is saved in database and only after is passed to the presenter. Room provides return of LiveData from its DAOs and this is exactly what suits my needs.

However I also want to use repositories as a single way to access data. Here's an example of repository interface in domain layer (the most abstract one):

interface Repository<T>{
    fun findByUsername(username: String) : List<T>    
    fun add(entity: T): Long
    fun remove(entity: T)    
    fun update(entity: T) : Int
}

And here I'm running into the problem - I need to get a LiveData from Room's DAO in my ViewModel and I'd like to get it using Repository implementation. But in order to achieve this I need either to:

  1. Change Repository method findByUsername to return LiveData>
  2. Or call Room's DAO directly from ViewModel skipping repository implementation completely

Both of these options have sufficient drawbacks:

  1. If I import android.arch.lifecycle.LiveData into my Repository interface than it would break the abstraction in Domain layer, as it is now depending on android architecture libraries.
  2. If I call Room's DAO directly in the ViewModel as val entities: LiveData<List<Entity>> = database.entityDao.findByUsername(username) then I'm breaking the rule that all data access must be made using Reposiotry and I will need to create some boilerplate code for synchronization with remote storage etc.

How is it possible to achieve single data source approach using LiveData, Room's DAO and Clean architecure patterns?

like image 355
Alexandr Zhurkov Avatar asked Jun 08 '18 02:06

Alexandr Zhurkov


2 Answers

When similar question is asked about using RxJava, developers usualy answer, that is ok, and RxJava now is a language part, so, you can use it in domain layer. In my opinion - you can do anything, if it helps you, so, if using LiveData don't create problems - use it, or you can use RxJava, or Kotlin coroutines instead.

like image 174
Mamykin Andrey Avatar answered Oct 19 '22 06:10

Mamykin Andrey


Technically you are running into trouble because you don't want synchronous data fetching.

 fun findByUsername(username: String) : List<T>  

You want a subscription that returns to you a new List<T> each time there is a change.

 fun findByUsernameWithChanges(username: String) : Subscription<List<T>>

So now what you might want to do is make your own subscription wrapper that can handle LiveData or Flowable. Of course, LiveData is trickier because you must also give it a LifecycleOwner.

public interface Subscription<T> {
    public interface Observer<T> {
        void onChange(T t);
    }

    void observe(Observer<T> observer);

    void clear();
}

And then something like

public class LiveDataSubscription<T> implements Subscription<T> {
    private LiveData<T> liveData;
    private LifecycleOwner lifecycleOwner;

    private List<Observer<T>> foreverObservers = new ArrayList<>();

    public LiveDataSubscription(LiveData<T> liveData) {
        this.liveData = liveData;
    }

    @Override
    public void observe(final Observer<T> observer) {
        if(lifecycleOwner != null) {
            liveData.observe(lifecycleOwner, new android.arch.lifecycle.Observer<T>() {
                 @Override
                 public void onChange(@Nullable T t) {
                      observer.onChange(t);
                 }
            });
        } else {
            Observer<T> foreverObserver = new android.arch.lifecycle.Observer<T>() {
                 @Override
                 public void onChange(@Nullable T t) {
                      observer.onChange(t);
                 }
            };
            foreverObservers.add(foreverObserver);
            liveData.observeForever(foreverObserver);
        }
    }

    @Override
    public void clear() {
        if(lifecycleOwner != null) {
            liveData.removeObservers(lifecycleOwner);
        } else {
            for(Observer<T> observer: foreverObservers) {
                liveData.removeObserver(observer);
            }
        }
    }

    public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        this.lifecycleOwner = lifecycleOwner;
    }
}

And now you can use your repository

val subscription = repository.findByUsernameWithChanges("blah")
if(subscription is LiveDataSubscription) {
    subscription.lifecycleOwner = this
}
subscription.observe { data ->
    // ...
}
like image 6
EpicPandaForce Avatar answered Oct 19 '22 07:10

EpicPandaForce