Compose Toggleable Buttons

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 Icon and 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.

Increment the comment count

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 TweetView function.

First, the ActionRow is updated to accept an event listener for a click on the comment view. This listener is passed to the Comment function.

Next, the TweetView function implements the event listener and passes it to the ActionRow function.

With the listener hooked up, the next step is to modify the Tweet class. The commentCount property needs to be changed from val to var so the listener can write to it.

Then the value can be changed in the TweetView.

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.

Log statement printed after clicking on the comment action item

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.

Since 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.

Implement toggle actions

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.

The 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.

The 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 Toggleable. The onClick listener parameter is updated to onLikeChanged which requires a Boolean property for the lambda. This listener is passed as the onValueChange property to Toggleable.

Similar changes are made to Retweet.

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.

Like and 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.

The 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 retweetCount and likeCount properties are also changed to vars so they can be updated as well.

The onCreate() in MainActivity and the preview function need to add these properties to the Tweet so the project can build.

Now the TweetView can provide the necessary data to the ActionRow.

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 Like.

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!

Photo by Hal Gatewood on Unsplash