Managing State in Litho
This is the second post in series of exploring Litho. In the first post - Android GIF search engine with Litho, we explored some key aspects of Litho - LayoutSpec, MountSpec, Use Glide to load images, Update RecyclerView data, etc. If you’re not familiar with Litho, I would suggest you to go through the first post.
In this brief post, we will see how we can manage and update state of components in Litho. We already have a list of gifs and now we’ll see how useful component state are how we can use the immutability to our advantage. To demonstrate this, we will add a like/favorite button to gifs. We’ll use immutable data (GifItem) and see how we need not update it to maintain state of components.
State
A component can have props
and state
to define how it should be rendered, what it should render, etc. Props are immutable and can not be changed once set. State can be updated but it’s tricky and since it’s tricky it’s good so that we won’t mess it up. As a good practice, state should be immutable or at least thread safe. But we are not going to worry about it right now. We’ll just have a boolean state to display whether the user has liked the gif or not.
Let’s start coding. This is how our updated GifItem would look like.
We have included isLiked
but as you can see the class is immutable.
We need to update how our component is going to be laid out. I’m changing previous post’s GifItemViewSpec to GifImageViewSpec. And we are going to create a different GifItemView component which will have a square GIF image and a favorite button on top right corner.
You can find GifImageViewSpec
here - GifImageViewSpec.
Yoga (Flexbox) doesn’t work exactly like Android. To put the favorite button on top right corner, we need to use .positionType(YogaPositionType.ABSOLUTE)
in combination with .alignSelf(YogaAlign.FLEX_END)
. The first property gets us FrameLayout behavior and the second one makes it layout_gravity=end
.
As you can see, isLiked
is a state which would affect the drawable of the button.
@OnCreateInitialState
is only called once for a component. This is where we need to set our state. We are using @Prop boolean initLiked
to set the state. This method will never be invoked for the same component throught its lifecycle. After the state is initialized, lifecycle will call other methods. We access this state in onCreateLayout
to set the correct drawable to the button. To update the state, we need create @OnUpdateState
method. Litho will generate the code for us and then we can call that method to update the state.
We want to update the state of the button when the user clicks on it. We need to toggle isLiked
and update the drawable. We need to add a ClickEvent
which we will receive when the user clicks on the button. @OnEvent(ClickEvent.class)
method will be invoked by the Click Handler. Litho internally does some magic to propagate events. I’ll write about it in upcoming posts. We can get access to state and any props in event method.
Once we call updateLikeButton
or updateLikeButtonAsync
, Litho (magically) will call @OnUpdateState
method and it will recreate the ComponentLayout and our drawable will be updated.
In our MainActivity, we’ll need to make some changes to include @Prop initLiked
.
If you run it now, look for batman GIFs and hit button on some of the images, the state for those components will be updated. When you scroll up and down, you’ll see that component state is still maintained without you having to set liked
in GifItem.
Boilerplate
We can see that we can have immutable properties and still work very effectively with Litho. This is one of the main advantage of unidrecitonal data flow.
If you reopen the app and search for batman again, you would not have any liked GIFs. To fix that you can have a shared preferences which stores id of the gif as key and boolean as value. And every time you get new data, you can set isLiked
by looking up values in shared preferences. Once you have your shared preferences or any other db set up, you can use an interface and pass it as a @Prop
to GifItemView component and call the interface method in your onEvent(ClickEvent.class)
method.
Here we can have optional prop with @Prop(optional = true)
. If a prop is optional, we need not pass it all the time. Here’s the complete code - GifItemViewSpec.
To save our data, we can use SharedPreferences.
PreferenceLikeStore
implements LikeStore
a basic interface. You can skip on the interface if you want. Here, we can remove gif id from shared preferences if it is unliked.
You can find complete code here - LikeStore and PreferenceLikeStore.
We need to update MainActivity
in order to have the callback.
As I had mentioned in the previous post, GifProvider
class uses Retrofit to call API and provide List<GifItem>
. Here, we are going to modify it a bit and pass PrefernceLikeStore
to set whether the gif was liked by the user or not. You can find the code here - GifProvider and MainActivity.
Code
You can find current code here - LithoGifDemo - v2
You can find the latest code (keeps updating) here - LithoGifDemo
In the next post, we will see how we can implement navigation with Litho!
P.S. Working with State is a bit more complicated than it seems but I have explored some ways using which we can synchronize states.
Series
- Android GIF search engine with Litho
- Managing State in Litho
- Navigation with Litho
- Events with Litho
- Synchronizing state between different components
Litho - Facebook's declarative UI framework
Explore Facebook's brilliant UI framework through the journey of making an android app to search GIFs. Build your own app with Litho as we analyze Litho and learn about its Components, State, Navigation, Events and synchronization.