Published March 27, 2020
In my previous post, the
Container components are removed since they will be deprecated soon. The vector assets are also drawn using the
IconButton components. In this post, some of the action buttons are set up to modify the state when clicked.
The comment button will increment the comment count since a user can leave multiple comments on a single Tweet. The like and retweet buttons will act as a toggle. These also need a selected state so users know if they have liked or retweeted that Tweet.
A few things are needed to handle a click on the comment action item. The click event lambda needs to handle updating the comment count, and the
Tweet class needs to be updated so our composable functions can react to changes to its properties.
Modifying the click lambda is first. Incrementing the comment count requires moving the declaration of the lambda to a higher-order composable function. It is currently initialized in the
ActionRow function, but there is no reference to the
Tweet class there. Passing the tweet down to the
ActionRow would pollute it with unnecessary information. Instead, the event listener will be lifted up to the
ActionRow is updated to accept an event listener for a click on the comment view. This listener is passed to the
TweetView function implements the event listener and passes it to the
With the listener hooked up, the next step is to modify the
Tweet class. The
commentCount property needs to be changed from
var so the listener can write to it.
Then the value can be changed in the
Running the app now surfaces a problem. Clicking on comment does not change the value shown on the screen. Adding a log statement in the listener and running the app again verifies that it is reacting to the click event, but it is not changing the UI.
The reason this happens is there is nothing telling the
TweetView to redraw itself when the contents of the
Tweet changes. Compose provides an annotation for this functionality. Adding the @Model annotation to the
Tweet class tells the
TweetView to listen for changes to the tweet properties. If one of the properties changes, then the
TweetView will redraw itself to display the new data.
The class comment on the annotation provides details on exactly what this annotation does.
* [Model] can be applied to a class which represents your application's data model, and will cause * instances of the class to become observable, such that a read of a property of an instance of * this class during the invocation of a composable function will cause that component to be * "subscribed" to mutations of that instance. Composable functions which directly or indirectly * read properties of the model class, the composables will be recomposed whenever any properties * of the the model are written to.
TweetView reads the
commentCount property from the
Tweet, it implicitly subscribes to changes to the value. When the
commentClick listener modifies that value, the
TweetView is redrawn to the screen.
With that, the comment functionality is finished. Next, the like and retweet actions are setup.
The like and retweet actions are slightly different than comment. Instead of incrementing the count for each click, they need to toggle on and off since a user can only like or retweet once. While this could be implemented using the
Clickable function, there is a better way.
dev05 release of Compose introduced the
Toggleable function which is exactly what the like and retweet actions need. This function accepts a
value parameter indicating the current state of the toggle. It also accepts an
onValueChange listener that receives the new value as a boolean flag when clicked.
Like is updated first.
Clickable function is changed to
Toggleable. This function requires a value parameter and a value change listener. A boolean flag,
liked, is passed to the
Like function which is forwarded to
onClick listener parameter is updated to
onLikeChanged which requires a
Boolean property for the lambda. This listener is passed as the
onValueChange property to
Similar changes are made to
In fact, these are the exact same changes made to
Like. Since this is the final form of these functions, this duplication can be extracted to a new composable function.
Retweet are simplified by calling through to this new function.
With the low-level functions finished, the
ActionRow function needs updates to pass the new parameters. The new toggle listeners will be passed from
TweetView just like the
commentClick function so
ActionRow can continue to have know knowledge of the
Tweet class itself.
TweetView now needs to provide all these parameters to the
ActionRow. There is one thing to fix first. The
Tweet class needs properties for the liked and retweeted state. These are added as
var properties so they can change. The
likeCount properties are also changed to
vars so they can be updated as well.
MainActivity and the preview function need to add these properties to the
Tweet so the project can build.
TweetView can provide the necessary data to the
The toggle functions set the current state on the tweet, then modify the count based on which way the toggle goes. If the user likes the tweet the count is incremented, and if they unlike it then it is decremented.
To wrap up, a little color added to the toggles helps the users figure out their current state at a glance. The light gray color is used for the off state but a different color would be nice for the on state.
To implement this, a selected color is added as a property of the
ToggleImage function. This color is then set as the
tintColor fo the
DrawVector function as well as the
color parameter of the
TextStyle for the count text.
The color green is used for
Retweet and red is used for
With this in place, the app now styles the like and retweet actions based on their state.
In this post the action row is setup to modify the state of the view based on click events. The comment count increments each time it is clicked while the like and retweet counts toggle back and forth.
In the next post the state management will be further improved by making the
Tweet class read only. Instead of having
var properties, everything will be declared as a
val to have an effectively immutable data class. This will provide some data protection because the individual model objects cannot be updated.
Thanks for reading and stay tuned for more!