APOD-COMPOSE
This project is a little demo about the usage of jetpack compose and redux kotlin.
The APOD api
The APOD api is provided by nasa and returns the Astronomic Picture of the Day.
It is used in the homepage with the “startDate” parameter to retrieve the last N apods, and with the “date” parameter in the detail to retrieve the apod of that day.
Redux/Flux
The Flux design pattern is composed by four parts organized in a one-way data pipeline: Action, Dispatcher, Store and View.
The view dispatches an action to the store, the store receives it and handles it using its sub-components, then notifies the View if something changed in the data source.
Inside the store, there’s two components: the State, representing the state of the whole application, and the Reducers, whose job is to take the payload of an action and copy it in a new instance of the state.
To handle asyncronous actions (like an http download), the action is intercepted by a component that I called Tales (inspired from Angular’s sagas and ReduxObservables’ epics) that starts a coroutine that returns a new action to be dispatched.
The app state
The app is composed by two sections: the home and the detail.
The same applies to the appstate. In addition, a DepsState contains the dependencies needed by the tales, in a similar way as how redux-observables handles it:
The actions
The action base class is just
open class Action
In a multiplatform project that contains a js target you may want to assign the base class a unique id to avoid checking the action class type with too many instanceof
We simply then have the Home Actions as follows:
The reducers
To avoid looking for bugs everywhere, all the logic should be places into the Tales and the reducer should be quite simple and only create a new state to avoid having to write unit tests for them.
For instance, the homeReducer just copy the data to the state when it’s ready:
The tales
A tale is a suspend method that takes an action and the state as an input, and returns a list of actions to be dispatched after its execution.
For instance, this is the homeTales method
Initializing the store
We are now ready to initialize the store with a custom middleware. A middleware is a function that takes the store, the action and a “next” method to be called to dispatch the action to the reducer.
We handle it this way:
After that, we just need to initialize the store passing the root reducer, the tales list and the middleware:
Jetpack Compose
Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.
For example, this is the composable for the full-width image in the apod detail:
An apod could also contain an url of a youtube video. For this reason instead of an image we use a webview to show the youtube webpage. This is a nice example on how to integrate legacy views inside of a composable:
As you can see, this syntax is very clear and one can visualize the composable by reading the code way easier than with XML or other non-dsl programmatical view creation! In addition, in the latest android studio build, you can also see the composable preview in the IDE without building the whole application.
Navigation
Navigation is handled using a NavHost placed in the ActivityComposable, the root of the application. The simplified composable looks like this, having removed some parameters for readability:
Store and View Interaction
The MainActivity subscription
Since there’s only one activity and its composable contains the whole hierarchy, it’s up to it to register to the store changes and propagate them to the composable child.
In this way we have a single point of entry of the state and action updates, and the compose re-render system (similar to react’s virtual dom) will only re-render the composable that access properties that changed since the last render cycle.
Since I usually develop apps with a lot of static data services in the same page, I also included rxKotlin to debounce the state updates and avoid too many unnecessary re-render.
So, these are the fields of the MainActivity:
The rxKotlin behaviour subject
The state to be passed to the composable
The subscription to the store
And finally, the behaviourSubject subscription that triggers on every state changes and debounces them to the mutableState of the Composable every 250ms
After that, it’s just a matter of passing the state to the composable and then to the child:
And voilà! Now the composables and the redux store are connected!
Launching actions
To launch an action when a composable is displayed, we can rely on the LifecycleOwner callbacks.
For example, to initialize the homepage we can do so in the HomeComposable:
Now we’re all set! The activity will pass the starting AppState to the HomeComposable, that will launch the initialization action and receive the new state after the flow completes.
Contribution
If you wanna contribute to the architecture and readability of this project, feel free to open pull requests and open issues on the repository 😉