Introducing MVVM (Model-View-ViewModel) Architecture in Phimpme Android App
Introducing MVVM in Phimpme

Introducing MVVM (Model-View-ViewModel) Architecture in Phimpme Android App

Phimpme Android App an image editor app that aims to replace proprietary photographing and image apps on smartphones. It offers features such as taking photos, adding filters, editing images and uploading them to social networks. The app was using MVP(Model-View-Presenter) architecture and is now being ported to MVVM(Model-View-ViewModel) architecture.

Advantages of MVVM over MVP?

  1. The view model is lifecycle aware and only updates the UI based on the lifecycle of the activity/fragment.
  2. Separation of concerns – Not all the code under one single activity
  3. Loose coupling – Activity depends on ViewModel and ViewModel depends on the Repository and not the other way around.

MVVM?

  1. Model – Model represents the data and business logic of the app. The repository can be seen as a model in an MVVM architecture which contains login to fetch the data from an API or a remote API
  2. ViewModel – The view model creates a reference with Model/Repository and gets the data for the UI. It delivers the data to UI via observers of LiveData and also the ViewModel is lifecycle aware and respects the lifecycle of the activity such as screen rotations that don’t cause the ViewModel to be created again.
  3. View – The Activity/Fragment is the view where the data is shown to the user, the View creates a reference to the ViewModel via ViewModel provider class. Hence it listens to the ViewModel callbacks via LiveData.

Process for inclusion

  1. Add ViewModel and LiveData

    implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleVersion"

  2. Now create a class AccountViewModel – it will perform all the functioning that will drive the UI of the Account Activity. We will use LiveData for observing the data in the activity

    public class AccountViewModel extends ViewModel {
    private AccountRepository accountRepository

    = new AccountRepository();
    MutableLiveData<RealmQuery<AccountDatabase>>accountDetails = new MutableLiveData<>();//live data 

    }

  3. Create a class AccountRepository – Used to perform the DB related operations and the ViewModel will hold the instance of this repository.

    class AccountRepository {
    private Realm realm = Realm.getDefaultInstance();
    private DatabaseHelper databaseHelper = new DatabaseHelper(realm);// Fetches the details of all accounts present in database
    RealmQuery<AccountDatabase> fetchAllAccounts() {
    return databaseHelper.fetchAccountDetails();
     }
    }


  4. Now we will add the functionality in AccountViewModel to fetch accounts for the UI

    public class AccountViewModel extends ViewModel {
     final int RESULT_OK = 1;
    private AccountRepository accountRepository = new AccountRepository();
    MutableLiveData<Boolean> error = new MutableLiveData<>();
    MutableLiveData<RealmQuery<AccountDatabase>> accountDetails = new MutableLiveData<>();
    public AccountViewModel() {}
    // Used to fetch all the current logged in accounts
    void fetchAccountDetails() {
       RealmQuery<AccountDatabase> accountDetails = accountRepository.fetchAllAccounts();
    if (accountDetails.findAll().size() > 0) {
         this.accountDetails.postValue(accountDetails);
    } else {
     error.postValue(true);
    }
    }


  5. Now in the AccountActivity, we will have the reference of ViewModel and then observe the liveData error and accountDetails

    public class AccountActivity extends ThemedActivityimplements RecyclerItemClickListner.OnItemClickListener {

    private AccountViewModel accountViewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ButterKnife.bind(this);
    ActivitySwitchHelper.setContext(this);
    setSupportActionBar(toolbar);
    //fetching the viewmodel from ViewModelProviders
    accountViewModel = ViewModelProviders.of(this).get(AccountViewModel.class);
    initObserver();
    }

    private void initObserver() {
    accountViewModel.error.observe(this, value -> {
    if (value) {
     SnackBarHandler.create(coordinatorLayout, getString(no_account_signed_in)).show();
    showComplete();
    }
     });
    accountViewModel.accountDetails.observe(this, this::setUpAdapter);
    }


Hence, this completes the implementation of MVVM Architecture in the Phimpme app.

Resources 

  1. Guide to App Architecture – Android Developers Blog
  2. ViewModel Overview – Android Developers Blog
  3. LiveData Overview – Android Developers Blog

Link to the Issue: https://github.com/fossasia/phimpme-android/issues/2889
Link to the PR: https://github.com/fossasia/phimpme-android/pull/2890

Continue Reading

Smart Data Loading in Open Event Android Orga App

In any API centric native application like the Open Event organizer app (Github Repo), there is a need to access data through network, cache it for later use in a database, and retrieve data selectively from both sources, network and disk. Most of Android Applications use SQLite (including countless wrapper libraries on top of it) or Realm to manage their database, and Retrofit has become a de facto standard for consuming a REST API. But there is no standard way to manage the bridge between these two for smart data loading. Most applications directly make calls to DB or API service to selectively load their data and display it on the UI, but this breaks fluidity and cohesion between these two data sources. After all, both of these sources manage the same kind of data.

Suppose you wanted to load your data from a single source without having to worry from where it is coming, it’d require a smart model or repository which checks which kind of data you want to load, check if it is available in the DB, load it from there if it is. And if it not, call the API service to load the data and also save it in the DB there itself. These smart models are self contained, meaning they handle the loading logic and also handle edge cases and errors and take actions for themselves about storing and retrieving the data. This makes presentation or UI layer free of data aspects of the application and also remove unnecessary handling code unrelated to UI.

From the starting of Open Event Android Orga Application planning, we proposed to create an efficient MVP based design with clear separation of concerns. With use of RxJava, we have created a single source repository pattern, which automatically handles connection, reload and database and network management. This blog post will discuss the implementation of the AbstractObservableBuilder class which manages from which source to load the data intelligently

Feature Set

So, first, let’s discuss what features should our AbstractObservableBuilder class should have:

  • Should take two inputs – disk source and network source
  • Should handle force reload from server
  • Should handle network connection logic
  • Should load from disk observable if data is present
  • Should load from network observable if data is not present is disk observable

This constitutes the most basic data operations done on any API based Android application. Now, let’s talk about the implementation

Implementation

Since our class will be of generic type, we will used variable T to denote it. Firstly, we have 4 declarations globally

private IUtilModel utilModel;
private boolean reload;
private Observable<T> diskObservable;
private Observable<T> networkObservable;

 

  • UtilModel tells us if the device is connected to internet or not
  • reload tells if the request should bypass database and fetch from network source only
  • diskObservable is the database source of the item to be fetched
  • networkObservable is the network source of the same item

Next is a very simple implementation of the builder pattern methods which will be used to set these variable fields

@Inject
public AbstractObservableBuilder(IUtilModel utilModel) {
    this.utilModel = utilModel;
}

AbstractObservableBuilder<T> reload(boolean reload) {
    this.reload = reload;

    return this;
}

AbstractObservableBuilder<T> withDiskObservable(Observable<T> diskObservable) {
    this.diskObservable = diskObservable;

    return this;
}

AbstractObservableBuilder<T> withNetworkObservable(Observable<T> networkObservable) {
    this.networkObservable = networkObservable;

    return this;
}

 

UtilModel is the required dependency, and so is added as a constructor parameter.

All right, all variables are set up, now we need to create the build function to actually create the observable:

@NonNull
public Observable<T> build() {
    if (diskObservable == null || networkObservable == null)
        throw new IllegalStateException("Network or Disk observable not provided");

    return Observable
            .defer(getReloadCallable())
            .switchIfEmpty(getConnectionObservable())
            .compose(applySchedulers());
}

Reloading Logic

First of all, we check if the caller forgot to add disk or network source and throw an exception if it is actually so. Next, we use defer operator to defer the call to getReloadCallable() so that this function is not executed until this observable is subscribed. Some articles over the internet directly use combine operators from Rx to make things easy, but this lazy calling is the most efficient way to do things because no actual call will be made to observables.

Secondly, you can easily test the behaviour in unit tests, by verifying that

  • no call to the network observable was made if the data inside disk observable was present; or
  • call to network observable was made even if there was data in disk observable if the reload request was made

These tests would not have been possible if we did not employ the lazy call technique because the calls to the observables and utilModel would have been made before the subscription to this model happen, in order to create this observable eagerly.

Now, let’s see what getReloadCallable does

@NonNull
private Callable<Observable<T>> getReloadCallable() {
    return () -> {
        if (reload)
            return Observable.empty();
        else
            return diskObservable
                .doOnNext(item -> 
                    Timber.d("Loaded %s From Disk on Thread %s",
                    item.getClass(), Thread.currentThread().getName()));
    };
}

 

This function’s role is to return the disk observable if the request is not a reload call, or else return an empty observable, so that force network request happens to reload the data

So it returns a Callable which encapsulates this logic, and besides that, it also adds a log if loading from disk about the type of item loaded and the thread it was loaded on

Connection and Database Switch Logic

In the next chain of operation, we make a switchIfEmpty call to getConnectionObservable(). Because of the above reloading logic, switchIfEmpty serves 2 purpose here, it changes to API call:

  • if db does not contain data
  • If it is a reload call

The observable we switch to is returned by getConnectionObservable() and its purpose is to check if the device is connected to the internet, and if it is, to forward the network request and if it is not, then return an Error Observable.

@NonNull
private Observable<T> getConnectionObservable() {
    if (utilModel.isConnected())
        return networkObservable
            .doOnNext(item -> Timber.d("Loaded %s From Network on Thread %s",
                item.getClass(), Thread.currentThread().getName()));
    else
        return Observable.error(new Throwable(Constants.NO_NETWORK));
}

We use util model to determine if we are connected to internet and take action accordingly. As you can see, here too, we log about the data being loaded and the thread information.

Threading

Lastly, we want to ensure that all processing happens on correct threads, and for that, we call compose with an Observable Transformer to make all requests happen on I/O scheduler and the data is received on Android’s Main Thread

@NonNull
private <V> ObservableTransformer<V, V> applySchedulers() {
    return observable -> observable
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
}

And that’s all it takes to create a reactive, generic and reusable data handler for disk and network based operations. In the repository pattern we have employed in the Open Event Android Orga Application, all our data switching and handling code is delegated to it, with unit tests and integration tests testing the individual and cross component working in all cases.

If you want to learn more about other implementations, you can read these articles

Continue Reading
Close Menu