2016/11/16

A Tutorial for FluxJava

This document will show you how to use FluxJava step by step. You can find all the sources in Github.

For someone may not familiar with Flux, here is a good place to start.

Start from BDD

In this document, I will demo the usage of FluxJava by developing a simple todo app. The process will start from a BDD flow.

First, we need to define our requirements. Below is the list of requirement for the simple todo app:
  • View todo list.
  • Switch list between different names.
  • Add a todo.
  • Close/Open a todo.
Then we need a story to describe these requirements, it should look like this:
Story: Manage todo items

Narrative:
As a user
I want to manage todo items
So I can track something to be done


Scenario 1: Add a todo
When I tap the add menu on main activity
Then I see the add todo screen

When I input todo detail and press ADD button
Then I see a new entry in list


Scenario 2: Add a todo, but cancel the action
When I tap the add menu on main activity
 And I press cancel button
Then Nothing happen


Scenario 3: Switch user
When I select a different user
Then I see the list changed


Scenario 4: Mark a todo as done
When I mark a todo as done
Then I see the todo has a check mark and strike through on title

Ok, since it is just a demo, the content of story has been simplified and it should be more complicate in the real cases.

Plan how to test

There will be one test class base on Espresso treated as UAT to verify the story, since I only wrote one story. For the non-Android component classes, they will be followed by a test class as unit test. The rest classes that base on Android component will be tested with Robolectric and treated as integration test.

In order to make the covert process from story to the test cases easier, I will use Spock Framework to replace JUnit. And Java will be replaced by Groovy, when writing the test cases.

Here is the dependencies for test:
  • Groovy
  • Spock Framework
  • RoboSpock
  • Espresso
RoboSpock is an Android testing framework which brings Spock Framework and Robolectric work together. It comes with Robolectric, so you don’t need to add the dependency of Robolectric again. But you also limited to use the version of Robolectric that RoboSpock works with.

Here is the sample for how to configure build.gradle with these frameworks:

Create the UI

After created a empty Android project, the first thing is modify MainActivity. In this Activity, we will need a RecyclerView and a Spinner to show the data of todos and users. The list of todo can be switch from Spinner. Here is the layout:

Create UAT

To contain the test files, we need to create a folder named groovy under androidTest and delete java one. If you make the build.gradle right, you will see the groovy folder with background color shows it is a test folder. Now, we can put our test file 'ManageTodoStory.groovy' into groovy folder with package structure.

Here, we can see the advantage brought by Spock, the codes almost the same with the origin story.
Then we can put the real test codes into the class as below:
There are some steps of resources creation have been skipped.

Create the bus

According to Flux pattern, dispatcher is the central hub to deliver messages. In FluxJava, it uses bus to replace the functionality of dispatcher. So, we have to create a bus instead of a  dispatcher.

There is a IFluxBus interface required by FluxJava when you create a bus. In this demo, I used EventBus from greenrobot to make the code simple. Here is the codes:
If you use Rx add-on of FluxJava, you won’t need to create a bus. There is a built-in bus class named RxBus.

Create the models

The models in this demo are two POJOs. They are used to contain single user and todo data. you can check the content of these two class from 'User.java' and 'Todo.java'.

Define the constants

This not the necessary step but it will make codes more clear. As you can see, there are two constants that point to user and todo data type. Each data type has it’s own actions base on the description from story.
DATA_USER and DATA_TODO will be used as key of StoreMap latter. In FluxJava, you can use any type of data as a key of StoreMap not just integer. It could be a string, a type of class, a instance of class.

Create the actions and stores

UserAction and TodoAction inherit from FluxAction directly. The type of their data property is List, so they can contain more then one item when they pushed by dispatcher. you can check the content of these two class from 'UserAction.java' and 'TodoAction.java'.

To create a store, you can inherit from FluxStore in FluxJava (for Rx add-on it will be RxStore) or implement IFluxStore. register and unregister of IFluxStore are provided for UI components to tell store that they need to get the event of data change.

If you have multi-instance of a store in you application, tag will be useful to distinct which instance of store you are relate to.

The methods getItem, findItem, getCount are basic operations for inquiry data to display. The reason of getItem not return a list is to avoid any list modification outside store. It still has possibility that the content of single item changed by non-store class. So, it recommends create a new instance before return from getItem method.
Now, we need to let store get actions from dispatcher. For EventBus case, you can add a method as below:
For RxStore, you need to overwrite onAction method:
The action constants defined in previous step are used here. In UserStore, for example, there is only one action - load user data. All the user data will prepared by ActionCreator in this demo and the data will be sent with action. What store need to do in USER_LOAD is put the data into list.

Then store can emit an event to tell listeners that data is ready. The events store need to emit are define inside the class in this demo.

Both EventBus and Rx case, it will need to put job into background thread to avoid block UI. In EventBus, I added an annotation attribute to specify working thread is background. In Rx, the job will be done by RxStore.

You don’t need to worry about the action type when action pass in. They will be filtered by the parameter type of method or getActionType method from RxStore. But you need to worry when there are more then one instance of store waiting to receive actions. In this case, you need to put tag information into actions before you send.

Below are the results of TodoStore for EventBus and Rx:
Two more cases were added into codes compare to UserStore. TODO_ADD is simply put the new item get from action into list and inform the listeners that list has been updated. TODO_CLOSE need to find the position of list due to getItem return a different instance. For demo purpose, I wrote a very simple loop to do the job. Once the item has been located, it will be replaced by new one and the listeners will be informed.

Create ActionHelper

ActionHelper is used to customize the process of action creation and the process will be done by ActionCreator in FluxJava.

First thing that ActionHelper can do is decide which type of action to create instance. Second part is translate the data format.

In above codes, the ActionHelper choose the type of action by the constants defined by previous step.

There are more jobs to be done in the second part of ActionHelper. In Flux documents said, you can make a remote call to get data in Actions Creator before create an action. To follow this, you can write the remote API calls in wrapData of ActionHelper.

Another usage of wrapData shows in above codes. It could be different format when get data from UI components. For example, it will be an integer when asking load user data, a string when asking load todo data, a todo item when modify todo. It is a good place in wrapData to change these forms into uniform one.

If you don’t like what ActionCreator in FluxJava did, you can inherit it and overwrite the methods. Then put your own ActionCreator instance into FluxContext when initializing it.

Put the components together

It’s time to put all things together. The Flux flow is cover all the application lifecycle in the demo, it is good choice put initialize jobs in Application class.

I wrote a AppConfig inherits from Application and modified the 'AndroidManifest.xml' to make it work. In AppConfig, there is a method call setupFlux as below:
How you put all components together is using builder inside FluxContext and pass the instance of classes that were created in previous steps.

Create the adapters

The adapters are used to feed data to Spinner and RecyclerView in MainActivity. They also play a role to connect the FluxJava framework. In this demo, they will be the views of Flux pattern.

I wrote two adapters UserAdapter and TodoAdapter. The former provides user data for Spinner to display. And the other provides todo data for RecyclerView. Before create these adapters, we need to create item layout first. Check the content in  'item_user.xml' and 'item_todo.xml'.

Here is complete UserAdapter:
The UserAdapter get the store instance in constructor. The first parameter of getStore is USER_DATA that was defined in 'Constants.java' and also a key of StoreMap. So FluxContext can get the store that we want.

The second one is tag for getting store instance, but we don’t use here, so pass null. The last one need pass a instance of view that want to get events from store. You also can do it by your self after get store instance using it’s register method.

In the rest part of UserAdapter, store keeps the data list for adapter, so UserAdapter just call store when it need.

There is a similar method with the stores in the end of UserAdapter. It utilize EventBus to get events too. In the same manner, the parameter type will limit the events passed in. By specify the annotation attribute, the execution back to the main thread in order to access UI components and only one thing need to do in this demo - notify Spinner that data has been changed.

You also need to put tag into events, if you have more than one store will emit events.

The last one method is used to release references that will avoid memory leak issue.

For Rx add-on, you won’t need to add a method to receive events, instead you can call  subscribe from the Observable that exposed by toObservable of RxStore.
The content of another adapter, TodoAdapter, show as below:
There are not much need to explain, just one method was added and it is used to get another event. It can make RecyclerView to distinct what to refresh, the whole list or just one item.

Want to work with Rx add-on? Like UserAdapter, just use subscribe instead of add methods. See the sources here.

Ok, we are ready to connect adapters with UI components. Here is the MainActivity:
As you see, nothing special when setup RecyclerView and Spinner. There will be a chain reaction from onStart. First, a request will be sent out to load user data. After user data loaded, Spinner will trigger a request to load todo data. Then you can see the data shows in the Spinner and RecyclerView.

Why did I put the request in onStart  I want to make sure the Activity get the most update data when it back to foreground, no matter it was changed or not.

The dispose method of Adapters are called in onStop of Activity to release resource before it close, then we will get free from memory leak issue.

Create integration test

To make apps more testable is one of the reasons I introduce Flux in my codes. When running a integration test, we don’t want to use real data. Isolate stores is an option when we make integration test.

In this demo, it shows how to archive this goal in FluxJava. Robolectric provides a convenient way to inject stub Application when a test start. So I wrote a StubAppConfig and specify in annotation attribute:
In StubAppConfig, I replaced the production classes with test ones. After did this, the data retrieved by UI components are from the test data source.
If you want to do the same thing in UAT or Espresso, you can replace the Application class too. But the different is you have to create a custom JUnitRunner as below:
The last step is specify the new JUnitRunner in Configurations of Android Studio:

When you run the UAT, you will see the different with the production one:
Production   Test
 

 

Create add todo feature

Let’s continue to enhance the app. It is a simple process for how to add a todo, just let user tap the add item in menu and show a AlertDialog to get data, then done. Obviously we need create a menu layout first:
Then put the menu into MainActivity:
I used DialogFragment to show the AlertDialog, so here is the layout:
And the codes:
Add codes in MainActivity to show the AlertDialog when user tap the menu:
After running the test, we know we finished the add todo feature, cool!

Create close todo feature

It’s the last feature we are not complete. To make it happen just back to TodoAdapter, modify onBindViewHolder as below:
Run the test, you will find all the cases passed. All done, have fun!



0 意見:

張貼留言