Reactive Task Pattern

Peike Dai
AndroidPub
Published in
6 min readOct 10, 2017

When dealing with a problem in Android development, one hard part is to find the best solution because usually there are more than one working solution. And usually, the time that you will decide to sit down and tell yourself “enough is enough” is when you have encountered that specific problem probably three or four times. That is how I came up with this.

The Problem

You must have had this kind of problem. There are so many things that need to be done during the launching of your app. You want to check the network status, check if there is any logged in user, check if all the required permissions are granted, check if the database is in sync with the cloud, maybe check if the app is running on an emulator, check if the battery saving mode is on or not, and many many more checks that’s necessary to your important app.

The checks are not the difficult part. What’s getting overwhelming is that for the different combination of results coming out of all the checks, you need to execute the corresponding task. And for each task, their execution may succeed or fail, and another task may depend on that.

There are a lot of other aspects that we need to consider when implementing a feature in Android. What if some checks or tasks need to be run in the background? What if some task only needs to be executed, and its result does not matter? What if a task that gets executed during the launching time is also needed when the user press a button?

You may think if the architecture is properly designed and implemented, this problem should not happen very often.

In fact, that’s not true. There are many scenarios which you will encounter this kind of problem. Some scenarios like:

  • user log in/out
  • listen to the network change
  • listen to the location change
  • device boot up/shut down

In my opinion, the properly designed architecture does a good job modularize the components in order to separate the concerns. It draws a clear big picture. However, the problems we have above does not sit in this scope. It needs a specific design pattern to resolve it.

Here I’m gonna tell you the design pattern that does it. I call it Reactive Task pattern.

The Pattern

There are two components in this pattern: Task and Sequence.

Task

A task does one thing and one thing only. A task can usually be implemented with one method (within 50 lines) or with one extra private method. By default task is asynchronous, but you can reconfigure it to make it run on the Android main thread. Some tasks I am using include:

  • Add Firebase Realtime Database listener
  • Write data1, 2, 3 into Firebase Realtime DB (three different tasks)
  • Check a permission (condition check is something different, I will talk about it later).
  • Request a permission.

Sequence

A sequence consists of a chain of task or sequence. The ability to chain not only task but also sequences makes it easier to reuse part of a sequence somewhere else. This is where all your business logic goes. Things like what happens if check A succeeded or task B failed.

A sequence I used is called InitialSequence. It contains a task CheckPermissionTask and two sequences SignInSequence and SyncDbSequence. You can find the simplified code snippet in the third section below.

Task and sequence have something in common.

  1. They both have output. There are three types of output success, failure, and completed.
  2. Both can be used independently. Some common places I use them include Repository (from Repository Pattern), Activity, and BroadcastReceiver.
  3. Both follow the reactive programming methodology.

A task defines the way you objectify your actions. With that, it becomes possible for the sequence to chain them and objectify your feature. Since I started using this pattern, I found that the project becomes:

  • more extendable
  • more maintainable
  • easier to test (not demonstrated here)

The Code

Now that we have addressed the problem and introduced the pattern, let’s take a look at the code. RxJava2 is heavily used in the implementation of this pattern.

Task

Task.java

The Single from RxJava2 is used instead of Observable or Flowable. You can make the interface internal instead of public. But if the project grows and the tasks number becomes larger then it’ll be a better idea to put the task on the package where it’s been used. The type parameter T represents the object which is returned once the task is completed.

Sequence

Generally speaking, a sequence is also a task. It contains a PublishSubject which acts like a status publisher. The status can be used in the UI. Remember something similar? Yes, AsyncTask in Android.

Let’s look at how to make the asynchronous task.

One thing to note here is that the invocation of the task(...) is wrapped in a protected method performTask(...). With that, the subclass will have the chance to do some pre-check and cancel the execution if needed.

Let’s see how the Reactive Task pattern is used in a real app first.

The trigger here is onStart(). All the tasks that need to be executed are maintained outside of the Activity because all I care is the final result of the tasks.

The InitialSequence looks like this:

Some very important things happened here.

  1. This is in Java on purpose. The Kotlin version is at the end for comparison.
  2. A sequence should always be marked final. This prevents the possible confusion caused by excessive inheritance.
  3. flatMap() is used to connect two Task.
  4. checkPermissionTask internally will request the permission if not yet granted. Note that I didn’t use the result from checkPermissionTask because no matter it’s granted or not, I need to do the signInSequence. Thus here checkPermissionTask is merely a completable task.
    That also means I have to check the permission in every task that requires permission. It’s not ideal in the perspective of DRY. But it brings very solid robustness to the app, which is more important than DRY in production.
  5. Observable.merge(...) is used to combine the status from the sub-sequences.

The same Result class is used as the response from most of sequences and tasks I created. It is a better choice than a Boolean or String in most cases.

If you have extra data that needs to be returned, just extend it and create a subclass (the SignInResult here as an example).

As you can see so far, first I extract the logic of the initialization out of the Activity. Then I separate the initialization into three different parts, check permission, do the sign in, and then do the database synchronization. After each part is implemented, I then connect them based on the result. Lastly, the final result is delivered to the Activity.

Each single action in your app is defined as a task. What you will need to do is simply assembling the ones you need based on the requirement of each feature.

This is a relatively easy example. There is only one check (permission check) and its result is not even considered. When there are multiple checks, we need to integrate with some other pattern to fulfill the need.

Specification Pattern

There is one task in my app which involves database writing. The conditions to execute that task include:

  • A: The app cannot run on an emulator.
  • B: There must be a logged in user.
  • C: The permission has to be granted.

A B and C are also used by many other tasks, all together, two of them or individually. To apply the DRY principle, Specification Pattern is here to save the day.

There are some other methods defined in the Specification Pattern which I have removed since they are not needed for this project.

I have many tasks that are doing the database writing action. So I created a base class to simplify the work. You may ask why it’s emitter.onSuccess() not emitter.onError(). In the RxJava perspective, the error is something unexpected and fatal failure. If the result is somewhat expected, it should always be a success.

It’s awesome you made it here. This design pattern solved a problem I have had for a long time with many projects. After implementing this I found myself unconsciously tried to apply it to many problems which are not suitable eventually. It is very important to remember that this pattern, just like every other pattern, only solve one type of problem.

This pattern still has a lot place to improve. The heavily used RxJava2 looks like a trade-off. There are many boilerplate codes required. And the flatMap looks like a hardcoded workaround. The improvement of the design pattern, however, can’t be done like code refactor. It needs experience and actual use to test and trial. It needs to evolve by itself.

How do you think this pattern? Do you have any idea or suggestion? Please don’t hesitate to leave in the comment section!

Oh, here is the Kotlin version of the InitialSequence.java. Can you see how incredibly short it is?

--

--