Migrating to Downloadable Fonts on Android

This is a rehost of the article I wrote for the Blockchain Engineering blog. You can find the original article here.


At Blockchain, we have many mobile users in bandwidth constrained environments such as developing countries. As such keeping the app small and lightweight is a priority for the Android team. Smaller app sizes increase both installs and conversion rates - important metrics for any mobile-focused company. We’ve used various techniques over the last year to prevent bloat to the app including shrinking what few PNGs we have, using VectorDrawables at every possible opportunity, and aggressively leveraging ProGuard.

In previous versions of the app, we bundled three fonts and relied on a third party library named Calligraphy to render the fonts across all API levels for us. Calligraphy has served us well, and for a long time was the only viable solution for font application. But the library is yet another third-party dependency and hasn’t been updated in nearly a year.

One downside of using a third-party solution is that Blockchain does not have full control over these fonts - Google could break how the library works at some point in the future. The other downside to this approach is that the necessary bundled fonts take up space. Thankfully, this is no longer required.

Android Oreo added downloadable fonts, which allows apps to request typefaces from a font provider and load them at runtime instead of loading them from the apps assets folder. This has added benefits in that it reduces the chances of an app installation failing randomly, and multiple apps requesting the same font can share these resources.

As of Support Library 26.0.0, Google backported the font provider, and this has given us a native (or close to native) solution all the way down to API 14, which is beyond what we need here at Blockchain.

Adding a downloadable font is simple - head to the Layout Editor, select fontFamily -> More Fonts, and choose whatever you might need. Android Studio then imports all of the necessary files, and also adds entries to the Android Manifest and generates the relevant XML files declaring the font used, and how to retrieve it from the font provider.

Selecting fonts in the Layout Editor

We did find a slight oddity as to how this system worked. Google claims that this gives you access to every typeface on fonts.google.com. In practise, however, we could only get two variations of Montserrat via the Android Studio interface - regular and bold. We make heavy use of Montserrat Light plus Montserrat Semi-Bold, so this was not ideal.

In searching for a solution, we found the fonts we wanted via fonts.google.com and noticed that the URL parameters included a weight (300 for light, 600 for semi-bold). Simply adding these parameters into the fontProviderQuery allowed us to download them without issue, although Android Studio did then highlight the query as having no exact match. We found that this particular Lint error could be safely ignored, as the correct weights of font were loaded successfully on every test device.

app:fontProviderQuery="name=Montserrat&weight=600"

We use a couple of custom Views which could not be themed this way directly, so we also had to programmatically download the Typeface and apply this at runtime, rather than using XML or Styles. For this, we used Kotlin and higher order functions to request fonts asynchronously, before delivering the result to the caller.

Taking advantage of the flexibility of Kotlin, we also added an Extension Function that applies the newly loaded Typeface directly to a TextView. We use this in a couple of places to dynamically change the font on a TextView depending on whether or not it was the last selected View.

Lastly, we found that the speed of the loading process occasionally caused Views to “pop” as the font was applied and the View bounds adjusted. This was easily fixed by adding a local cache of Typeface objects keyed to a CustomFont object, which encapsulates the query necessary to load the font. This made programmatically loading fonts functionally instant in most cases.

After all that, we managed to reduce the APK size from 7.7MB to 6.8MB - an 11% reduction - with the download size reducing from 5.4MB to 5.0MB - a 7.5% reduction. This is a substantial win for relatively little work, and makes a huge difference to end users. If you want to view the changes, feel free to check out the feature PR here.