Write a post

Enjoy this post? Give Kevin Farst a like if it's helpful.

3

Creating a Twitter-style Text Counter in Android Using Data Binding

Published Apr 26, 2017
Creating a Twitter-style Text Counter in Android Using Data Binding

A little while back I attended an Android bootcamp in which we built a handful of apps
as we learned. One of the apps we were tasked with building was a Twitter app, and one of
the features I wanted to build was the almost iconic character counter for tweet composition, as they are of course limited
to 140 characters. As you type each character, the gray text counts down to zero before turning red and continuing
to count back up in the negative range.

Additionally, since the Twitter API will reject any tweet posted over 140 characters, we need to disable the button
next to the counter to prevent the tweet from attempting to be posted.

pm2MYbP.gif

Getting started

We'll be using data binding, so in our build.gradle for our app module we'll need to enable it.

  android {
      ...
      dataBinding.enabled = true
      ...
  }

In the layout file for the fragment, we need to expose a variable to tie logic from the fragment's class to values
inside the layout. Data-binding layout files need to be wrapped in a layout tag, followed by a list of variable tags
within an opening and closing data tag. The variable tag we'll add is what Android will use to tie a property in
our ComposeTweetFragment to the fragment_compose_tweet.xml layout.

  <?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools">

      <data>
          <variable name="tweetViewModel" type="com.kfarst.apps.whispertweetnothings.models.TweetViewModel"/>
      </data>
      ...
  </layout>

We'll come back to the TweetViewModel explanation shortly. Back to our ComposeTweetFragment class, we need to add a variable
called tweetViewModel to tie our layout-declared variable to the fragment's class. From the documentation,

By default, a Binding class will be generated based on the name of the layout file, converting it to Pascal case and suffixing "Binding" to it.

Therefore the variable we'll declare will be of type FragmentComposeTweetBinding.

...
public class ComposeTweetFragment extends DialogFragment {
    ...
    private FragmentComposeTweetBinding binding;
    ...
}

In the onCreate method, we inflate the view, assign the binding to our private variable, and set the value of our layout-defined
tweetViewModel variable.

  ...
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
      // Inflate the layout for this fragment
      View view = inflater.inflate(R.layout.fragment_compose_tweet, container, false);
      binding = FragmentComposeTweetBinding.bind(view);

      // Bind view model for observing the number of tweet characters
      binding.setTweetViewModel(new TweetViewModel(tweet));
      ...
  }
  ...

The tweet variable comes from a value passed into the fragment class.

Where did the TweetViewModel come from?

Returning to the TweetViewModel class, let's look at part of the definition, as this is a class we're declaring ourselves.

  ...
  public class TweetViewModel {
      private static Integer TOTAL_TWEET_LENGTH = 140;
      private Tweet tweet;
      public ObservableField<Integer> charactersRemaining = new ObservableField<>(TOTAL_TWEET_LENGTH);
      ...
  }

The notable item to point out here is the ObservableField property, which provides a way in which data bound UI can be notified of changes.
The character count changes dynamically based off of user input, so it needs to know when to update. We also need to add a TextWatcher to
the class to watch the EditText field as the user types.

  ...
  public TextWatcher watcher = new TextWatcher() {
      @Override
      public void afterTextChanged(Editable editable) {
          tweet.setStatus(editable.toString());
          charactersRemaining.set(TOTAL_TWEET_LENGTH - editable.toString().length());
      }
  };
  ...

Finally, in our fragment_compose_tweet.xml layout file (and only showing the properties related to the functionality we're building), we have:

        <EditText
            android:hint="What's happening?"
            android:text="@{tweetViewModel.tweet.status}"
            android:addTextChangedListener="@{tweetViewModel.watcher}"
            android:id="@+id/etTweet" />

        <TextView
            android:text='@{""+tweetViewModel.charactersRemaining}'
            android:textColor="@{tweetViewModel.charactersRemaining > -1 ? @android:color/darker_gray : @android:color/holo_red_dark}" />

        <Button
            android:text="Tweet"
            android:id="@+id/btnTweet"
            android:alpha="@{tweetViewModel.charactersRemaining == 140 || tweetViewModel.charactersRemaining &lt; 0 ? 0.5f : 1.0f}"
            android:clickable="@{tweetViewModel.charactersRemaining &lt; 140 &amp;&amp; tweetViewModel.charactersRemaining > -1 ? true : false}" />

We use the @{} syntax for interpolating our data-bound expressions, which by element type are

EditText

  • text - The actual tweet body.
  • addTextChangedListener - The text watcher that determines when to update the character count.

TextView

  • text - The dynamic count of the number of characters remaining (the ""+ casts the Integer to type String).
  • textColor - If the number of characters remaining is between 0 and 140, the text color will be gray, otherwise a negative count will turn the text red.

Button

  • alpha - Show a partially faded button indicating that it is disabled if the user hasn't started typing a tweet yet, or if they type more than 140 characters. The < needs to be escaped.
  • clickable - Allow the button to be clicked if the user has typed at least one character and not more than 140. The < and && need to be escaped.

Conclusion

Both data binding and observables are very powerful concepts in Android, and I encourage you to explore the vast array of ways they can be utilized in your
apps. The official documentation is the best place to start, and they are plenty of other tutorials
easily found on the Googles. I've only shown the bare minimum code for explanation of the topic at hand, but if you would like to go through the tutorial
again with the full source, you can find it on my GitHub account here. If you have any questions, or
suggestions for a better way to structure the code above, please don't hesistate to comment. Good luck and thanks for reading!

Discover and read more posts from Kevin Farst
get started
Enjoy this post?

Leave a like and comment for Kevin

3
Be the first to share your opinion

Subscribe to our weekly newsletter