DEV Community

Marcelo Surriabre
Marcelo Surriabre

Posted on

Android RxJava Reducer with the Visitor Pattern

Introduction

It is always a good idea to work on different projects and different technologies, as it provides a good source of knowledge for different parts of components of applications. Technologies used in the Frontend do not usually use the same tools or concepts as technologies on the Backend, and that is precisely the reason why working on both is a good idea. You can incorporate some ideas from one to the other. With this experience, in this article I develop a “RxJava Reducer” that can be used in Android applications.

The code

All the app code is in the repo:

Recycler State

Main Idea

The idea is simple:

We have an activity where the user triggers some action, such as clicking on an element, and as a result we move into another activity, the destination activity, in order to show the result of that action. The actions are usually async actions (database calls, api calls, etc.) that fetch some data that is going to be displayed to the user.

This process can involve different states on the destination activity. For instance, we could have the following states:

  • Loading
  • Loaded
  • Error

We need to update the state of the destination activity according to the result of the async action.

RxJava is used for processing the async actions and to manage the subscription of the destination activity, so it can be updated with the results of the async call.

Libraries/Dependencies

The following are the main dependencies that are used:

  • RxJava for handling async calls
  • Room for database
  • Navigation component for application flow
  • ViewModel to manage UI data
  • LiveData to handle the data on the ViewModel
  • Hilt for dependency injection
  • Java 8+ (Desugaring for using Java8+ API, full lambda!)

Application architecture

Single activity app. Given the recent updates on the android environment, it is recommended to use this new architecture for apps.

I recently had the experience of migrating a multi activity application to a single activity app and it wasn’t pain free. I think most of the time went into deciding how to move code out of the activities and how to structure the application using the navigation graphs. This was an important step as I decided to jump into all new android architecture components. Meaning: single activity app + architecture components (room, livedata, viewmodel) + navigation components. Before the migration, my app only had Room database, none of the other new features.

You can read more about these concepts here: Android Architecture Components

Android Architecture Components

I have to say that I really liked the final product. Using all these tools really forces you to think in terms of the architecture of your app and how you organize the code. The result is usually a very clean code/architecture with separation of concerns which makes it easy to change/update your app in the future. But you will probably have to spend a good amount of time understanding what works best for your specific functionality in your app and also reading a lot about the different ways you can conceive what you want using these new tools.

UI/View model

The app uses a typical pattern that you can see in almost any app: Master/Detail, but it seems this has been renamed to Primary/Detail Flow. I will use the terms master/detail as you can find much more information on the web under this name.

Basically you have a master view with a list of items. And when you click on one item you go to another view that displays the specific information of that item, that is the detail view.

Mapping of concepts

There is no exact mapping between the concepts of Redux to Android but we can define some similarities according the the main function of the concepts.

1. Store

The application state resides in the ViewModel. The ViewModel is the closest in terms of the Redux store, it handles the actions, which in turn can call an API or database.

2. Action

These are defined as methods within the ViewModel. For instance, the loadItem method. Any other components in the application can call(dispatch) these methods in the ViewModel.

public class ItemsViewModelImpl extends ViewModel {
    public void loadItem(long itemId) {
        setItemDetailState(ItemDetailLoadingState.getInstance());
        removeSubscriptionFromCompositeDisposable(getItemSubscription);
        getItemSubscription = itemRepository.getItem(itemId)
                .delay(2, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        this::onItemLoaded,
                        onItemDBError(DBOperation.GET)
                );
        compositeDisposable.add(getAllItemsSubscription);
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see we use RxJava for handling the async calls. In this sense we can say that RxJava takes the place of the Thunk function.

3. Reducers

These are defined as methods within a Fragment. The ListDetailFragement. Which also acts as a subscriber of the changes. This is a main difference in respect to Redux, where a reducer is defined independently of the component that uses the result of the reduction process.

4. State

The state is modeled using Polymorhphism. There is an interface named “ItemDetailState’ which has 3 different implementations: ItemDetailErrorState, ItemDetailLoadingState, ItemDetailLoadedState. This is explained in detailed in the next section. Just keep in mind we have the following classes:

  • IItemDetailState (Interface)
  • ItemDetailErrorState
  • ItemDetailLoadingState
  • ItemDetailLoadedState

Reduction and update of the state of the application

Now here comes the fun part. Usually with Redux we would have a function with a switch statement to define the reducer. But instead of using a plain switch statement we can use one of the most powerful tools in object oriented programming: “Polymorphism”. Not only that but we can use a very “rare” pattern: “The visitor pattern”.

The main problem we have is the following:

We have a list of items in the ListMasterFragment, and we want to see the data of a specific item in the ListDetailFragment.

However we need some data to update the view of the ListDetailFragment, this is the data of a specific Item.

The data is not in the ListDetailFragment. The data is in the ItemDetailState.

The ItemDetailState is an interface representing the state of an Item in the view, therefore, we may have multiple implementations, each with different state. We need to handle each state differently.

Solution

1st Idea

Use “instanceOf”(similar to the switch statement to reduce in Redux) to determine the actual type of the ItemDetailState implementation and get the required data.

The drawback of this approach is that we would need to update the instance of with more states whenever we add a new state. Needless to say, having multiples if or switch statements is a code smell. Moreover, we can actually bypass or forget to handle a new state. We are not enforcing correct behaviour and this could lead to bugs.

2nd idea: The Visitor Pattern

We can use the Visitor Pattern to solve our problems. If you need to know more about the pattern you can read a good introduction here:

Visitor Pattern

As with all the patterns, they serve as a guideline to solve common problems. Each problem is different and you can use the patterns as you may seem appropriate. In this case we are not using all the advantages of the pattern,such as the double-dispatch, as we only need to use polymorphism at the method argument level, not the receiver level.

You can read more about the double dispatch here: Visitor Pattern - Double dispatch

The implementation of the Visitor Pattern for the reducer is done in the following steps:

  1. We start by defining the Visitor interface:
public interface ItemDetailVisitor {

    void handleItemDetailLoadingState(ItemDetailLoadingState itemDetailLoadingState);

    void handleItemDetailLoadedState(ItemDetailLoadedState itemDetailLoadedState);

    void handleItemDetailErrorState(ItemDetailErrorState itemDetailErrorState);
}
Enter fullscreen mode Exit fullscreen mode
  1. The ListDetailFragment implements this interface:
public class ListDetailFragment extends Fragment implements ItemDetailVisitor {

    @Override
    public void handleItemDetailLoadingState(ItemDetailLoadingState itemDetailLoadingState) {
        showProgressBarLoading();
    }

    @Override
    public void handleItemDetailLoadedState(ItemDetailLoadedState itemDetailLoadedState) {
        setItemDetailData(itemDetailLoadedState);
        setItemDataOnView();
        hideLoadingProgressBar();
        Snackbar.make(requireView(), R.string.toast_loaded_successfully, Snackbar.LENGTH_SHORT).show();
    }

    @Override
    public void handleItemDetailErrorState(ItemDetailErrorState itemDetailErrorState) {
        hideLoadingProgressBar();
        Toast.makeText(getActivity(), R.string.toast_error_loading_item, Toast.LENGTH_SHORT).show();
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. We subscribe to changes on the ItemDetailState, which we get using the subscription provided by RxJava. This data is handled in the ViewModel using LiveData:
    private void setItemData() {
        itemsViewModel.getItemDetailState().observe(getViewLifecycleOwner(), this::onItemDetailStateChange);
    }

    private void onItemDetailStateChange(IItemDetailState itemDetailState) {
        itemDetailState.accept(this);
    }
Enter fullscreen mode Exit fullscreen mode
  • 4. We define the abstract accept method on the IItemDetailInterface:
public interface IItemDetailState {
    void accept(ItemDetailVisitor itemDetailVisitor);
}
Enter fullscreen mode Exit fullscreen mode
  • 5. Finally we implement the accept method on the concrete classes:
// On the ItemDetailStateError
    @Override
    public void accept(ItemDetailVisitor itemDetailVisitor) {
        itemDetailVisitor.handleItemDetailErrorState(this);

// On the ItemDetailLoadedState
    @Override
    public void accept(ItemDetailVisitor itemDetailVisitor) {
        itemDetailVisitor.handleItemDetailLoadedState(this);
    }

// On the ItemDetailLoadingState
    @Override
    public void accept(ItemDetailVisitor itemDetailVisitor) {
        itemDetailVisitor.handleItemDetailLoadingState(this);
    }
Enter fullscreen mode Exit fullscreen mode

In order to understand better how the problem is solved we can take a look at the data flow of the application.

Data flow

  • An item on the list is selected. ListMasterFragment triggers the loading of the selected item in the ViewModel and moves the view to the ListDetailFragment:
    @Override
    public View.OnClickListener seeItemButtonListener(ItemsRecyclerViewAdapter.ItemViewHolder holder, Item item) {
        return view -> goToItemDetail(view, item);
    }

    private void goToItemDetail(View view, Item item) {
        itemsViewModel.loadItem(item.getId());
        NavDirections toItemDetail = ListMasterFragmentDirections.actionListMasterFragmentToListDetailFragment();
        Navigation.findNavController(view).navigate(toItemDetail);
    }
Enter fullscreen mode Exit fullscreen mode
  • The ItemsViewModel receives the call to load the item and updates the ItemDetailState to "Loading", meanwhile, it does an async call to get the item details:
    public void loadItem(long itemId) {
        setItemDetailState(ItemDetailLoadingState.getInstance());
        removeSubscriptionFromCompositeDisposable(getItemSubscription);
        getItemSubscription = itemRepository.getItem(itemId)
                .delay(2, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        this::onItemLoaded,
                        onItemDBError(DBOperation.GET)
                );
        compositeDisposable.add(getAllItemsSubscription);
    }
Enter fullscreen mode Exit fullscreen mode

The setItemDetails is as follows:

    private void setItemDetailState(IItemDetailState itemDetailState) {
        this.itemDetailStateLiveData.setValue(itemDetailState);
    }
Enter fullscreen mode Exit fullscreen mode

Basically it updates the LivaData instance variable, which will notify any subscriber of this LiveData. Remember that the ListDetailFragment is subscribed to this LiveData:

// On the ListDetailFragment

    private void setItemData() {
        itemsViewModel.getItemDetailState().observe(getViewLifecycleOwner(), this::onItemDetailStateChange);
    }

    private void onItemDetailStateChange(IItemDetailState itemDetailState) {
        itemDetailState.accept(this);
    }
Enter fullscreen mode Exit fullscreen mode

So it will call the "accept" method on the "ItemDetailStateLoading" with the ListDetailFragment as an argument. Because this method is polymorphic on the argument type, it can call any concrete implementation of the interface! In this case the concrete implementation is the ItemDetailLoadingState.

  • The ItemDetailLoadingState receives the call and calls the corresponding method on the ListDetailFragment, which implements the ItemDetailVisitor interface:
// On the ItemDetailLoadingState
    @Override
    public void accept(ItemDetailVisitor itemDetailVisitor) {
        itemDetailVisitor.handleItemDetailLoadingState(this);
    }
Enter fullscreen mode Exit fullscreen mode
  • The ListDetailFragment calls the handleItemDetailLoadingState method:
    @Override
    public void handleItemDetailLoadingState(ItemDetailLoadingState itemDetailLoadingState) {
        showProgressBarLoading();
    }
Enter fullscreen mode Exit fullscreen mode
  • After some time, the result is obtained from the RxJava async call on the ViewModel:
    private void onItemLoaded(Item item) {
        setItemDetailState(ItemDetailLoadedState.of(item));
    }
Enter fullscreen mode Exit fullscreen mode

This will again update the LiveData, but this time with an instance of the ItemDetailLoadedState, which contains the data of the item that was loaded.

  • Again, the ListDetailFragment receives the update and calls the "accept" method, but this time in the ItemDetailLoadedState, thanks to the polymorphic method ":

// On the ListDetailFragment

    private void onItemDetailStateChange(IItemDetailState itemDetailState) {
        itemDetailState.accept(this);
    }

// On the ItemDetailLoadedState
    @Override
    public void accept(ItemDetailVisitor itemDetailVisitor) {
        itemDetailVisitor.handleItemDetailLoadedState(this);
    }
Enter fullscreen mode Exit fullscreen mode
  • Finally, the ListDetailFragment handles the call, and updates the View:
   @Override
    public void handleItemDetailLoadedState(ItemDetailLoadedState itemDetailLoadedState) {
        setItemDetailData(itemDetailLoadedState);
        setItemDataOnView();
        hideLoadingProgressBar();
        Snackbar.make(requireView(), R.string.toast_loaded_successfully, Snackbar.LENGTH_SHORT).show();
    }
Enter fullscreen mode Exit fullscreen mode

Basically we define the handler methods on the Visitor interface, and the implementation, in this case the ListDetailFragment decide how to implement them, updating the view according to the state.

With this approach, we can leverage the polymorphic types of the state and provide a solid architecture that handles beautifully the changes in the view.

Conclusions

This was a small proof of concept of how you can use the idea from other frameworks/libraries in different part of an application. In this case I took the Reducer idea from Redux, a frontend library, which can be quite useful for handling state changes using async calls in an Android application.

There might be better approaches but I found this one particularly interesting because it makes use of polymorphism to solve the reduction problem of multiple states.

I am planning on making several articles based on the same app as it has several interesting implementation details. For instance why using an interface for the State istead of having one state with "type" as a field. Also the RxJava subscription handling.

Top comments (0)