A few weeks ago I posted this article about creating a user input text counter in Android. I got some great feedback on refactoring improvements, and I ended up changing so much that I felt like it needed its own blog post. I could re-write the original article, but I thought it important to take this as an opportunity to use the two posts for a lesson in refactoring, and demonstrate that programming is a career-long improvement process. Additionally, I hope those who want to take a risk and share some lessons they’ve learned or some code they’ve written will see that it’s OK to put something out there, even if you aren’t some “programming god” or feel 100% confident in the examples you’re giving. It shows you are willing to give back to the community by sharing your experiences and lessons-learned, and any constructive feedback you receive can only help to improve yourself and others.
Anways, I’ll get off my soap box…
Now, on to the refactor. Originally I was making updates to the content the user sees and checking validations by way of data binding. I’m not saying data binding is the “wrong answer” by any means, it’s a really powerful tool to keep in your Android arsenal; however, my code needed two major improvements:
- I was exposing too much logic inside the view
- My view components were not very reusable
Taking a look at my original
fragment_compose_tweet.xml (with only the related properties listed), I had:
Sure, you can look at this and pretty much know what’s going on from the context, but it doesn’t look very clean, and like I mentioned before, not very reusable. I did most of my refactoring based around two concepts:
- Extending Android’s built-in view classes
- Using a common interface to handle view updates
Extending the TextView
First, I started off by creating a custom
TextView for the text counter by extending the built-in class, which I called
CounterTextView. Let’s take
a look at the XML (with only the relevant attributes).
We have two custom attributes for the
invalidTextColor. The first color will be used when the counter shows
a valid number, in this case greater than -1, and the second color will be shown when the text is invalid, or less than 0. We have to register these attribute
definitions somewhere so Android will recognize them, so I created a
res/values/attrs.xml file and added it there.
Next, taking a look at our custom
TextView class, we have:
In the constructor the attributes are fetched and stored as instance variables. Note that calling
attributes.getInt() needs a fallback
value as a second argument, so I chose a dark gray for valid and dark red for invalid.
Extending the Button
I also needed to create a custom
Button class, called
TweetSubmitButton. First the XML, where you’ll notice we don’t really have any
Next comes the
SubmitTweetButton class. Again, I’m only defining a bare bones class with the necessary default constructors.
You’re probably wonder what the point of overriding the default
Button class was in this case, and here’s why…
Defining an interface
Since we aren’t using data binding anymore, we need a way to tell both the
CounterTextView and the
TweetSubmitButton when to update.
TextView needs to know when and what value to update its text and text color, and the
Button needs to know when to appear faded
to indicate it’s disabled, as well as actually disabling the button. We also need a common and re-usable way to interact with these classes,
so we’ll do so with an interface. It’s very simple, containing one method
countChanged() that takes in the current text count and a flag indicating
whether we’ve reached the limit of the text or not.
Now, updating the view classes we have:
On a side note, we can also remove the data bound attributes on the
EditText widget where the user enters their status.
Updating the view model
One thing I kept around and simply refactored was my
TweetViewModel. I used this class to bind the view components with
Butter Knife, then observe changes to the
EditText where the user is typing out their tweet and
subsequently call the interface-defined methods on both the
CounterTextView and the
Full disclosure, I cheated a little bit in the logic for the
tweetSubmitButton.countChanged() method. Though the flag is named
we also need to disable the button if no text has been entered yet, so we cover both cases in the second argument. Also, you probably notice in
the constructor after I set up the
TextWatcher on the
tweetBody I explicitly set the text of the widget to the current status of the
Tweet object that was passed in. This is done for two reasons:
- It sets the initial text value for the CounterTextView
- If the tweet currently being composed is in reply to another tweet, clicking the reply button will automatically add the Twitter handle of the account the user is responding to. If that’s the case, when the ComposeTweetFragment is rendering for the user to reply to the tweet, part of the text will already be used up, so we need to immediately update the CounterTextView with the updated remaining character count
ComposeTweetFragment class we see the
respondingTweet being passed in and the user handle being extracted before passing the
tweet object into the
Finally, we can see this functionality in action below.
Thanks for taking the time to follow me on this refactoring journey. It was pretty fascinating how a few comments on my previous article ended up having such a large impact on how I structured my code. Though it’s still not perfect I’m sure, I hope you’ve learned a little bit, as I know I’ve learned quite a bit. So you don’t have to go back to the original article and find the link, here’s the source code for the original data binding method as well as the refactor we just walked through. Questions and comments are always welcome, though I prefer constructive criticism over “This article sucks!”. Sorry, bad joke. Anyways, good luck and happy coding!