Locale bugs & currency formatting in Android Studio

I grew up in the United States and like most countries I’ve traveled to, I’m used to seeing money that look like this:   $56.33   €12,50   £281.71

A modern IDE tells you when you’re ignorant

Today, Android Studio was kind enough to tell me that some sample code had a problem: “Implicitly using the default locale is a common source of bugs”. Nice.

Looking at this sample code, I asked myself: “Why should the UI format this number, isn’t that going to hide what the underlying application logic is saying?”

So it was a great thing that Android Studio shows little messages like this because it got me thinking about how to properly handle currency in the view layer.

Simply changing your app code won’t save you!

This is a case where view layer formatting isn’t the appropriate place to deal with rounding. The business logic of this application, the algorithm that calculates tips, should ultimately be in charge of rounding to the locale-appropriate currency decimal place, in Java this means using the BigDecimal class for manipulating values. So, I forked my own copy and updated the sample to remove the view formatting code.

Then I changed the locale on my device, and my Espresso tests started breaking.

See, when you write your format codes and test logic under a narrow mindset of one locale/language/currency (en_US), the test data you use can break your tests if the app is run under a different locale (ar-IQ) which format things like currencies (USD, IQD) to a different arithmetic precision (the dollar uses 2 decimal places, the Dinar uses 3). [locale/currency lookup table]

An example of an Espresso test written assuming western currencies can be found below. Dinar has three decimal places, so this particular sample fails because values are handled as doubles (floating point numbers). Without controller logic to deal with rounding, it comes out as 35.912 which is not the same as the 2-decimal data “35.91” in my test code.

To simplify things, much of the code is written using doubles to pass currency around, even though this isn’t best practice. Even still, so long as we use BigDecimal to handle the higher-order calculations, we can downgrade the decimal precision in outbound double values to the view layer. Then we have the option of using locale-accurate test data, managing the precision in our tests as well.

Check it out for yourself…

If you want to spin these examples up yourself, you can clone my repo. Also, if you want to see this work in continuous integration, check out my article on running a Jenkins / Android build server on Docker.