Quick and Easy StateListDrawables in Android with ONE PNG

One of the things that used to drive me (and my designer co-workers) crazy was that we needed to create a full stack of various PNG’s to create different states on our drawables. That means if I wanted a button with a white default state, orange pressed state and grey disabled state I’d have to create three PNG’s (for each density) … which … as you know is a huge number of PNG’s and a real pain to update when the time comes.

One Png To Rule Them All

I’m not the first to come up with this solution, but I figured its a good time to share it so its out there. This is not a one size fits all solution (see the conclusion at the end for more info), but it does give you a good jumping point and if you’re building state list drawables. You can see how this would help you simplify some of your icons by only having to create one.*

With this solution we have one PNG and then we use some Java code to create a StateListDrawable at runtime. This allows us to have one default PNG and then change the colors at runtime with just code.

Place the code below in a file called DrawableUtil.java and put it somewhere in your project.

public class DrawableUtil { 

    public static StateListDrawable getStateListDrawable(Context context, @DrawableRes int imageResource, @ColorRes int desiredColor, @IntRange(from = 0, to = 255) int disableAlpha) {

        // Create the colorized image (pressed state)
        Bitmap one = BitmapFactory.decodeResource(context.getResources(), imageResource);
        Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Bitmap.Config.ARGB_8888);

        Canvas c = new Canvas(oneCopy);
        Paint p = new Paint();
        int color = context.getResources().getColor(desiredColor);
        p.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
        c.drawBitmap(one, 0, 0, p);

        // Create the disabled bitmap for the disabled state
        Bitmap disabled = BitmapFactory.decodeResource(context.getResources(),imageResource);
        Bitmap disabledCopy = Bitmap.createBitmap(disabled.getWidth(), disabled.getHeight(), Bitmap.Config.ARGB_8888);

        Canvas disabledCanvas = new Canvas(disabledCopy);;
        Paint alphaPaint = new Paint();
        alphaPaint.setAlpha(disableAlpha);
        disabledCanvas.drawBitmap(disabled, 0, 0, alphaPaint);

        StateListDrawable stateListDrawable = new StateListDrawable();

        // Pressed State
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, new BitmapDrawable(oneCopy));

        // Disabled State
        stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, new BitmapDrawable(disabledCopy) );  // - symbol means opposite, in this case "disabled"

        // Default State
        stateListDrawable.addState(new int[]{}, context.getResources().getDrawable(imageResource));

        return stateListDrawable;
    }

}

The code above creatse a StateListDrawable that will return a Drawable that has three states:

  • Default – The default image (imageResource)
  • Pressed – The default image, colored with the color param (desiredColor)
  • Disabled – The disabled state image is the default image (above) but has the opacity set to the value that is passed in (disableAlpha)

 

How to Use It

Simply call the static method with the required parameters and it will return a StateListDrawable that you can use to set the background of any image that can have state (like a Button, ImageView, etc).

myImageView.setBackground(DrawableUtil.getStateListDrawable(context, R.drawable.ic_user_dark, R.color.white, 127)); // 127 = 50% in 0…255 alpha

Now, if for some reason you want to change the color of the pressed state, simply change the color value that is passed in – say changing R.color.white to R.color.red and have the new selected image be red. All done with a simple code change.

If you want to get advanced you could use the Pallete lirbary to help get your colors and colorize your icons based upon the theme of the image that is composing the screen, the PocketCasts team does a great job of this in their player.

Here’s what it looks like if we use a user icon, set the pressed color state to red and the disabled to 90.

 

Why Did I Use This

There are a couple of other support library implementations that we could have used (shown below). The reason why this implmentation was used over the others is because I wanted to keep the default state of the PNG intact. What do I mean? I wanted to use a PNG that looked like this as the default state (not pressed, nor disabled):

 

When pressed though, I wanted the image to look like this:

Other Implementations such as DrawableCompat.setTintList() would not allow me to keep the original drawable.

 

Other Implementations

On the /r/androiddev comments for this article it was brought up that you can use DrawableCompat to wrap and set the tint list on the drawable. This is correct, somewhat. A problem occurs when you want to perform what I set out to do above – keep the original drawable but tint the other states. If you don’t care about keeping the original color (or simply want to change it anyway) you can use the DrawableCompat with great success like this:

Drawable logoDrawable = getResources().getDrawable(R.drawable.ic_agilevent_logo);
Drawable tintableDrawable = DrawableCompat.wrap(logoDrawable);
DrawableCompat.setTintList(tintableDrawable, getResources().getColorStateList(R.color.logo_color_selector));
DrawableCompat.setTintMode(tintableDrawable, PorterDuff.Mode.SRC_IN);
myImageView.setBackground(tintableDrawable);

 

The R.color.logo_color_selector looks like this:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/red" />
<item android:state_enabled="false" android:color="@color/green" />
<item android:color="@color/blue" />
</selector>

 

Conclusion*

The new material design support libs out there you can do some of the same things I’m showing above, but some have still opted for this solution as they’re not 100% material (or not going material at all).

This is one of many solutions to a very common problem. It does not mean that you have to use this, in fact if you’re using v21+ you can use android:tint attribute to colorize android pngs. You can also use the android colorFilter to do the same thing we’re doing above. Like I said, there are a few ways to do this, but this is one way where you create advanced state list drawable in code.

Lastly, there is also a TintImageView that is present in the android.support.v7.internal.widget.TintImageView that has very similar settings. The code to use it looks like this:

 

<android.support.v7.internal.widget.TintImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_icon"
android:backgroundTint="@color/green"
android:backgroundTintMode="src_over"/>

However, I would advise against using this as this is inside of the internal package inside of android.support.v7. Traditionally anything inside of an internal package name indicates that it is not a public API that should be consumed or relied upon. Choose to use it at your own risk. :)

Why You Should Use a GIT SHA in Your Crash Reporting

A common problem developers encounter when developing applications that use a crash reporting tool like Crashlytics is determinig if a particular crash/bug has been fixed/addressed or not.

For example assume that you get a crash report for a recent release. But you released three times this week already … which release does it apply to?

This is usually solved by reviewing the version code and verison name in Crashlytics. But even then you have to be properly tagging your releases. If you’re doing that you can trace back the release to a particular commit and then investigate.

However … Lets be 100% honest here – not everyone does this. Unfortunately , very few companies do this in my experience and it declines even more when the size of the team deminishes to even a single developer. There’s a lot going on, its easy to miss. Furthremore, chasing down a tag, then finding a commit, well … its kind of a pain. If someone forgot then its all for nothing.

That said, here’s a quick tip that can save you a ton of time when you’re performing crash and bug triage with tools like Crashlytics.

Adding The GIT SHA
In your Android application, open the build.gradle file and add the following above the android block.

// Hat tip to Jake Wharton for this - found it in the u2020 app
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()

Back in the Android block add a git sha build config constant.

android {
 compileSdkVersion 19
 buildToolsVersion "21.1.0"

 defaultConfig {
 applicationId "co.your.appname"
 minSdkVersion 19
 targetSdkVersion 19

 buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
 }

}

Now go back to where you’ve set up your Crashlytics instance in your application code (example shown below). Just below the initialization script add the following code:

Crashlytics.setString("git_sha", BuildConfig.GIT_SHA);

What this will do is set a string with the key value being “git_sha” and the value being the short git-sha from your source control.

Now, when your application crashes you’ll get a bug report in Crashlytics and you’ll be able to see what the latest commit was on that code.

Reviewing in Crashlytics
Open Crashlytics and go to one of your crashes. Then click on “more details”. Here you will see (screenshot below) the git_sha that the application was built off of.

Then you’ll see this ..

 

Remediation
Once you have identified the crash and the git-sha you can checkout that exact version of the code by issuing

git checkout git_sha_goes_here

At this point you’re in a detached head state. You’ll want to see what caused the crash in this state. Then you’ll want to return to your current develop branch or tagged branch to fix/hotfix the issue and release the fix.

The git_sha saves a ton of time and its super easy to set up. You no longer have to dig through git logs, tags, patches, etc to find “what commit is this crash happening on? Did we fix it already? How can I find out?” Simply check the git sha, look for the bug, see if it’s fixed yet. If it is, cool. If not, fix it and be on your way.

I hope that helps!

** Update **

Jake had a great comment in the reddit thread that I wanted to share …

Worth noting that this will break incremental compilation as you commit (and thus cause the value to change). Internally we switched to writing the SHA to a file if it is missing and using the file’s value (we do the same with build timestamp). This means that only a clean build gets fresh values. Since CI builds and making releases are always clean (right? RIGHT?) it keeps developer builds fully incremental yet still gives the right values where it matters. – Jake Wharton

Is Google I/O Worth It?

This is the question I asked myself last year before I went to Google I/O 2014. I’m an independent consultant who has to foot the bill for all of these types of trips. So not only does the ticket price get you in the wallet but so does the flight, hotel, meals and time off of client work. After weighing the options, I spoke to previous attendees and figured out what they got out of the conf and I figured it was a no-brainer. Totally worth it based upon their personal feedback.

I still decided to calculate the costs. When all said and done, this conference can easily cost any consulting firm around $7,500 per person.

You might be shocked, but let’s break it down.

  • Google I/O Ticket: $900
  • Flight: ~$300
  • Hotel in SF near Conf: ~$1200 (if you’re lucky)
  • Meals/Etc: $300
  • Lost Billing Time: $4800

Billing time is calculated at a very common rate of $150/hr for most consulting shops that perform quality work. Again this is a rough estimate.

With these numbers, you can ask yourself “Is going to Google I/O worth $7,500? It’s ultimately up to you to decide, but here’s what I determined …

I Came, I Saw, I Left Depressed

I know that may sound harsh, but it was exactly how I felt when I left Google I/O 2014. You may be wondering “its nearly 8 months since the conference happened, why is he bringing this up?”. Simply put – I was not sure what the network effect would be of Google I/O. The network effect does not happen overnight, it takes some time and that’s what I wanted to give it – more time.

I’ve probably written this post a few times now, each time I’ve deleted it. Yup, typed it into the editor, reviewed it, edited it, then deleted it. Today, it feels right, so I’m writing it and posting it.

On my way to Google I/O I was pumped. The only other conference I’ve been to of this magnitude was TechEd in Orlando in 2008 (I think /build is the new big one in place of TechEd, I think). When I went to TechEd I was blown away. Seeing that Google was the new hotness in the industry, I figured this con would blow TechEd out of the water. Unfortunately, that was not the case. I showed up with some teammates from MyFitnessPal  and we walked to the Moscone center where the conference was. I was dumbfounded. There was a line wrapped one and half times around the building. This was the line to get into the Keynote. INTO THE KEYNOTE. It was 4-6 people wide and looked like a gigantic snake of people around the building. We got in line and waited. If it’s this popular, it’s got to be good, right?

After about 35-40 minutes of truffle shuffling around the building we got in. There were barely any seats left and the line still went around the building. Crazy. Seat nabbed, time for action to begin.

The keynote was nothing spectacular. A new OS which looked cool, Android Auto, Android TV, and Android Wear all came out of their shell in a big way, but I’ve been skeptical of all of these technologies for a bit. Not because I don’t believe in them, but because I don’t think they’ll work right now. I feel that these technologies that we have (wear, auto, tv) are all stepping stones to something bigger and better. We have to go through this phase of technology to get to the better stuff. People hate vendor lock-in and that’s exactly what Wear, Auto and TV are: Android Vendor lock-in. Don’t get me wrong, I love Android – I can’t stand using iPhones. What the developer side of me saw was cool new tech, but the realistic business side of me saw vendor lock-in and a fight for a portion of the market. I saw three new products that needed to have some major push behind them to work and I didn’t feel any of them were going to get the public relations and developer relations attention they need to succeed. In my eyes, I was looking at something that was not a huge starter for me. The one exception to that was Android TV with the ADT-1. Mixed with Chromecast I think there are a lot of opportunities to take existing tech to the next level (in some areas).

The keynote being a bust wasn’t really a concern of mine. To me, keynotes are usually huge marketing webinars, but with $1000 ticket price to watch in person. Honestly, think about that next time you’re in a big keynote (Apple, Google, Microsoft, etc) … Does this feel like a webinar? Probably. You’ll be surprised. Next up was the sessions.

I went to numerous sessions throughout the days. Covering Wear, Material Design, Async Design Patterns and so forth. All from Google employees. If I had to rate them, most of them would be 4-5 stars out of 10. Not because the presenter stunk (though there were a few of those) but because the content was so shallow that I felt like I was being shown the magic and nothing behind the curtain. I felt like some of them were mini-keynotes for niche areas of Googles product teams.

All Is Not Lost

While the sessions were a big let down I did find some amazing other areas of the conference. Usually these were on the floor. I wouldn’t call these sessions but more watch and learn labs where the presenter is talking to 10-30 people around an open podium. I got to witness how to use Dart and some advanced features of Android. How to write Go and so forth.

Another area that myself and a couple employees from MyFitnessPal took advantage of were the hands-on labs. You could pull up a chair and try out some code. I was able to learn polymer quite quickly and then if I remember correctly I did a bunch of AngularJS because it was something I had already been playing with and wanted to dive into more. The Android topics were all very shallow and didn’t go too crazy. It was all stuff that I had done before. I think those courses would have been great for a beginner though.

The most interesting area of the conference to me was the open discussion/presentations that were around entrepreneurship. Two of the best talks I heard were very short, but packed with insightful tips for those interested in building apps/companies. These two talks were my sanity savers at Google I/O. Those two talks were by Aaron Harris of YCombinator and Adora Cheung of Homejoy. These talks should have been much longer and more open. Both were packed.

Google Goodies

As with every Google I/O goodies were to be had. I was able to make it out with the following:

I was also lucky enough to speak to one of the Google TV folks and asked him if I might be able to get my hands on an ADT-1. He said they were all gone but he’d take my info just in case, but he re-assurred me that I would most likely never get one. 3-4 months later a magic box shows up at my door – it’s an ADT-1. I don’t remember your name good sir, but thank you very much. I’ve been playing/testing/etc for a bit. :) From my research online it looks like ADT-1’s are being sold for around $350 through various channels (EBay, Craigslist, etc).

Extraciricular Events

Though the Google I/O sessions were lack luster one great thing was the Google I/O party. They had great food, great drinks (lots of micro brews) and great entertainment. It was good to be with friends, meet new ones and have a good time.

The other events that made this trip better were the other parties going on in the area. Twitter’s being the first one. This was a great place to go. Lots of great people and such a cool vibe. Bit thanks to Hemal for getting us in there. We also went to the first Big Android BBQ in SF. Met some cool folks there and I’m still rocking the Phandroid pint glass that I got there to this day. There were many parties, too many to get to. That’s where it seemed like the real networking came into play.

Was It Worth $7500?

No. Not really. You can take all my goodies away and I’d still stay the same thing and I would not be upset. I feel that this conference at one time may have been a great event. Unfortunately in 2014 Google I/O turned into a marketing conference and the content was rather shallow. I hope that changes, because I love Google’s products and I like the Android platform alot. However due to what I experienced in 2014 I have no interest in attending in 2015.  I look at it objectively – do I feel that am I going to get $7,500 in value out of it? Combine the learning, the networking, the goodies and the mental vacation from being away from the office and I have a quotient for me that says: Nope, doesnt work. Again, this may not be the case for you, but this is the case for me.

 

Android Studio Espresso 2.0 ClassNotFoundException

File this under “I’m writing this so when I Google for it in 6 months, it pops up”. 

 

I recently set up an existing Android project with Espresso 2.0 and immediately stared running into this error when I ran the tests: 

Running tests
Test running started
Test running failed: Instrumentation run failed due to 'java.lang.ClassNotFoundException'
Empty test suite.

I spent some time last night fighting this error and then I figured out the solution this morning.

The problem is that I’m using Dagger in this project and Espresso 2.0 also uses Dagger (see Espresso Dependencies). The core problem is that the ‘javax.inject’ dependency was borking the test run. 

How to to Fix

Change this

androidTestCompile('com.android.support.test.espresso:espresso-core:2.0')

to this: 

androidTestCompile('com.android.support.test.espresso:espresso-core:2.0') {
   exclude group: 'javax.inject'
}

 

Problem solved.