Friday, May 26, 2017

Limiting Android Localization to Supported Languages

When you're working with an Android app that will be used in a variety of markets, localization is an important feature to support the different countries, date formats, number and currency formats that are used around the world. Android has deep support for localization which you can read about in great detail.

In iOS, in order to add support for a language, you add languages to your project properties in an explicit way. In Android, adding support for a language is more implicit -- you simply start localizing resources. This is easy to use but it creates a problem where you might have partial support for a language in place but not be ready to take that support live. Android doesn't offer a means of explicitly listing which languages you wish customers to have access to. This can lead to situations where you have to keep localization resources on a branch or out of source-control or otherwise limit how those files are managed. That's viable but it isn't a great fit for every workflow.

On a recent client project, I did a little work with Gradle to make supported languages more explicit.  I added a list of supported languages in the build.gradle, used resource shrinking with resConfigs and exposed the languages to Java in BuildConfig using buildConfigField:

// Approved Languages
def approvedLanguages = [ "en", "ru" ]
resConfigs approvedLanguages
buildConfigField "String[]", "APPROVED_LANGUAGES", "{\"" + approvedLanguages.join("\",\"") + "\"}"

By exposing the approved languages to Java, it was possible to use it localization code, for instance to only localize dates to languages that are in the supported language list:
public class LocaleUtils {

     public static boolean isSupported(Locale locale) {
        for (String language : BuildConfig.APPROVED_LANGUAGES ) {
            if (locale.equals(locale.getLanguage()))
        for (String language : BuildConfig.APPROVED_LANGUAGES) {
            if (language.equals(locale.getLanguage()))
                 return true;
         return false;

     public static String localizeLocalDate(LocalDate localDate, String format) {
        return localDate.toString(
        Locale locale = getDefaultSupportedLocale();
        DateTimeFormatter formatter = DateTimeFormat.forPattern(format).withLocale(locale);
        return localDate.toString(formatter);

With this in place, we can start working on a new locale, integrating translated strings and graphics in the main line of development and know that they won't be exposed to customers until we're ready. With a little work, we could also use a different list of supported languages on a debug and production build.

Monday, October 24, 2016

Swift Mutable, Hashable and Collections

Swift sets have similar behaviour to Java sets when it comes to mutable objects: if you have a mutable object, wherein mutation of the object can result in a change to the hash value, that can lead to surprising behaviour. The collection may store the object in a bucket based on the hash it had at the time of insertion. If you modify the object, the hash value changes, but it doesn't move from one bucket to another. This can mean that a set may report that the object is not present even when it is.

I made a little playground (raw view, compressed form for download) to demonstrate this issue, but in essence:

let mi = MutableInt(1)
let mis:Set = [mi]

mis.contains(mi) // true
mi.value = 2
mis.contains(mi) // false

The same object is present in the same set for both contains calls, but because the hash value has changed, the set can no longer find it.

Sunday, September 18, 2016

Host Key Verification and Ansible

I've been using Ansible to configure some instances on Amazon Web Services with a client, for two reasons:
  • To make it repeatable in the future, if we want to configure an instance again, or another instance.
  • Because I will be repeating it immediately now, making more than one server to live behind a load balancer that are meant to be configured identically.
When you connect to a host for the first time over SSH, you are asked to verify the host key. When you use Ansibleagainst a host for the first time, the same thing happens. If you are connecting to multiple hosts at the same time, bad things happen -- you get multiple prompts to verify the host key and responding to them doesn't seem to work:

$ ansible servergroup -m ping
The authenticity of host ' ()' can't be established.
ECDSA key fingerprint is SHA256:hEdMy3XKWV/zWobmSuwf+b6oI9xt4cYJzM1eAa2T8Ak.
Are you sure you want to continue connecting (yes/no)? 
The authenticity of host ' ()' can't be established.
ECDSA key fingerprint is SHA256:N5iv0/+zRHk7UTsIQOUlzn2ZiU9L2xL+Fn153nlZdjs.
Are you sure you want to continue connecting (yes/no)? yes
Please type 'yes' or 'no': yes
Please type 'yes' or 'no': yes
Please type 'yes' or 'no': yes
Please type 'yes' or 'no': yes
Please type 'yes' or 'no': yes
Please type 'yes' or 'no': ^CProcess WorkerProcess-3:
Process WorkerProcess-2:
Traceback (most recent call last):
 [ERROR]: User interrupted execution

 While I was pleased to read the post about bugs filed, it doesn't seem to be fixed yet.

Happily, as long as you connect to a single host at at time, everything is fine, and after that you can connect to the group:
$ ansible server-one -m ping
The authenticity of host ' ()' can't be established.
ECDSA key fingerprint is SHA256:N5iv0/+zRHk7UTsIQOUlzn2ZiU9L2xL+Fn153nlZdjs.
Are you sure you want to continue connecting (yes/no)? yes
server-one | SUCCESS => {
    "changed": false,
    "ping": "pong"
That's fine for a couple of servers, but it wouldn't be fun with a large cluster. If there's a better way, I haven't discovered it yet.

Thursday, July 14, 2016

Using Ligature Fonts in IntelliJ

IntelliJ IDEA 2016.2 was released with improved support for programming fonts with ligatures. I tried a few of them to see how I felt about them. I do some Scala in IntelliJ, so I used Scala examples to compare how I felt about things.

Hasklig initially felt like the right font for me. It's based on Source Code Pro, which I like, although it's never been my favourite programming font. It's very round, and a bit wider than I normally choose, but legible, and the ligatures look nice.

However, I do like the idea of having a ligature for "!=", and Hasklig limited ligature set doesn't include one for this.

Fira Code
Fira Code looks a reasonable amount like Hasklig, really. It's a little narrower, a little less round. I found it very slightly less legible at first, but it was a close second place. Notice that it does include a not-equals ligature:

Although I started with Hasklig, I have since switched to Fira. 

Monoid is big. It's the only one that I'm showing here in 12pt instead of 13pt, and even that feels bigger than the others. It's pretty legible, but the weight feels a little too light as a result to me. Feels like coding in Helvetica Neue Ultra-Light.

Pragmata Pro
Pragmata Pro is narrow and tall. The wideness of Source Code Pro is one of the reasons I stayed away from it at first, but Pragmata feels almost cramped to me. I like the idea of firing a lot of text in, but it feels less legible as a result of its narrowness, so I'm not sold.

The fact that it costs €199 for the complete family does not endear me to it either. I understand making fonts is a lot of work, but many programmers aren't going to bother with a custom font at all, and making it that expensive pretty much rules out the odds of it being an impulse purchase. Some way of trailing the font in an IDE would be nice.

If I felt this were the right font for me, I suppose I would probably purchase it anyway, but at that price I'd want to be sold right away, and I'm not. If it were cheap, maybe I would have impulse-purchased and given it a longer trial.

Pragmata does have a lot of nice features, so if you do feel so inclined, it's still worth looking at.

My Choice
To be honest, these are all pretty decent fonts, and if you're interested in having programming ligatures, I urge you to try some of them out and see which one fits for you.

For me, I think I'm going to try Fira Code for a while. If Hasklig or Source Code Pro adds more ligatures, I might consider going back. I'll also be watching for other ligature programming fonts (Inconsolata, for instance?).

Have any suggestions I should try?

Tuesday, May 31, 2016

NSDecimalNumber and unsignedIntegerValue

So, if you have an NSDecimalNumber and you want to get the unsigned integer equivalent of it, you'd do [nsdn unsignedIntegerValue], right?

Wrong. Or, at least, risky.

I hit a bug in NSDecimalNumber this week, and I thought I'd share it as a playground, which you can download or read, but I'll summarize here.

Essentially, if you have an NSDecimalNumber containing a real number with a large mantissa and you call unsignedIntegerValue on it, you may get zero instead of the nearest integer. I assume it's a bug, rather than a design decision, but it's definitely risky to rely on unsignedIntegerValue here. If you  convert the value yourself (e.g. call floor(nsdn.doubleValue)) you'll be much better off.

I've filed a radar.

Tuesday, January 12, 2016

Opening App Transport Security failures in Safari

One of my client projects is an iOS application which, like many iOS Applications, has an embedded web view, which it uses in this case to display help content from the web. Because this app targets iOS 7.x as a baseline, it uses UIWebView instead of, say, SFSafariViewController.

The help content is part of the company's website, and the web page footer has social media links, and some of these links, if you clicked on them, wouldn't load.

Initially, I thought this might be because of the target="_blank" attribute, which has been a problem with UIWebView in the past, so I built a UIWebViewDelegate that would inject a little bit of JavaScript to remove the targets. It didn't work. So I changed the JavaScript to set the target to "_self". Still didn't work:

- (void)webViewDidFinishLoad:(UIWebView *)webView
    NSString *injectedJavascript = @"function "
      "AppPrefix_Injected_suppressBlankTargets() {\n"
      "  var links = document.getElementsByTagName('a');\n"
      "  var hrefs = [];\n"
      "  for( index = 0; index < links.length; index++ ) {\n"
      "    var link = links.item( index );\n"
      "    if( link.getAttribute( 'target' ) == '_blank' ) {\n"
      "      link.setAttribute( 'target', '_self' );\n"
    "      hrefs.push( link.outerHTML );\n"
      "    }\n"
      "  }\n"
      "  return hrefs.join();\n"
    NSString *result = [webView 
    DLog( "Injected javascript to suppress blank targets on links: %@", result );

Having failed on two assumptions, I realized that I should verify my assumption instead, and implemented webView:didFailLoadWithError, which quickly showed me that the actual problem was App Transport Security.

The social media links? Some of them are HTTP rather than HTTPS, and those ones won't load because App Transport Security doesn't like it. That left options.

Disable App Transport Security?
I don't like going this route until I have to.  I don't want to whitelist certain sites because the social media links might lead to other sites which would also need to be whitelisted and then eventually you just end up disabling app transport security anyway.

Open ATS Failures in Safari
What I ended up doing instead is listening for errors (webView:didFailLoadWithError) and then check the error. If it was an app transport security failure, pull the URL out of NSError's userInfo and then open that link in mobile safari instead.

It's fairly straightforward. Write a UIWebViewDelegate like this:

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    NSURL *failedUrl = [self parseATSError:error];
    if( failedUrl != nil ) {
        DLog( "ATS Failure, opening in Safari: %@", failedUrl );
        [[UIApplication sharedApplication] openURL:failedUrl];
    } else {
        DLog( "Failed to load, with non-ATS error: %@", error );

- (NSURL *)parseATSError:(NSError*)error {
    if( error == nil )
        return nil;
    if( ![error.domain isEqualToString:NSURLErrorDomain] )
        return nil;
    if( error.code != -1022 )
        return nil;
    NSString *url = error.userInfo[ NSURLErrorFailingURLStringErrorKey ];
    return [NSURL URLWithString:url];

This seems to work. So if you're using an UIWebView and running afoul of App Transport Security, maybe this will help you.

Tuesday, January 5, 2016

AppStore Release Joy

After what felt like an eternal delay, one of the my client's apps has finally gone live in the App Store. It is always nice when something you write goes live, but somehow the app store process, painful though it may be, makes the final victory of "Hey, there's my App, live in the App Store" feel that much sweeter.

I've already seen some press for the app (and related wearables), so it's already somewhat promising, but it'll be interesting to watch it grow.