Custom and Downloadable Fonts on Android

See how to make great looking apps using the new custom and downloadable fonts capability available in Android Studio 3.0, all in Kotlin. By Ivan Kušt.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Custom fonts in layouts

You’ve already seen in previous steps how to add a custom font to TextView. Now you will add a custom font to a Theme, changing the default font on all Activities that use the Theme.

Open the file res/values/styles.xml.

Change app theme Theme.FontQuiz – add the fontFamily attribute:

<style name="Theme.FontQuiz" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="fontFamily">@font/opensans</item>
</style>

Build and run the app.

You can see that across the app, OpenSans is now used:

Custom fonts programatically

You can set the custom font programatically as well. To do that you will use the ResourcesCompat class from the support library. Type the following at the end of the onCreate() method in MainActivity:

val typeface = ResourcesCompat.getFont(this, R.font.opensans_bold)
startButton.typeface = typeface

Build and run your project.

You can see that the font on the start button has been set to OpenSans Bold.

Note again that you use the support library to support Android versions earlier than Android 8.0.

Downloadable fonts

Now that you’ve seen how custom fonts work, let’s jump onto another novelty – downloadable fonts. Downloadable fonts allow you to add fonts to your application that download on demand or when your application starts.

This has more benefits:

  • fonts get downloaded only when required
  • reduced application .apk size
  • more applications can share fonts through font providers which can reduce used disk space

How Font Providers work

Font providers take care of retrieving and caching downloadable fonts used across applications. This is what the process of requesting a font looks like:

All applications that use downloadable fonts pass their requests via FontsContractCompat. It then communicates with the requested font provider. A font provider is an application that takes care of fetching and caching the appropriate fonts. There can be more of them installed on a device but currently only a Google font provider is available.

Security & certificates

To ensure security when using font providers, you have to provide the certificate used to sign by the font provider. This enables Android to verify the identity of the font provider. You have to do this for font providers that are not pre-installed on the device or when using support library.

Your next task is to add the certificate for the Google font provider.

Click on the res\values folder, press ⌘N (or File\New) and select Values resource file.

In the dialog, name it font_certs and click Ok.

You define font provider certificates in a string-array. If the font provider has more than one set of certificates, then you must define an array of string arrays. The Google font provider used with the support library uses two sets of certificates, and the next step is to define an array for each set.

Add a string array in the new file by adding <string-array> in the <resources> section and name it com_google_android_gms_fonts_certs_dev.

Add a single item to it with the following content:

<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>

Now add another string array with the name com_google_android_gms_fonts_certs_prod and add a single item to it with the following content:



<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>

Finally, create an array named com_google_android_gms_fonts_certs and add the two previously defined string arrays as its items.

Your font_certs.xml file should now look like this:


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="com_google_android_gms_fonts_certs">
        <item>@array/com_google_android_gms_fonts_certs_dev</item>
        <item>@array/com_google_android_gms_fonts_certs_prod</item>
    </array>
    <string-array name="com_google_android_gms_fonts_certs_dev">
        <item>
            MIIEqDCCA5CgAwIBA…
        </item>
    </string-array>
    <string-array name="com_google_android_gms_fonts_certs_prod">
        <item>
            MIIEQzCCAyugAwIBAgIJAMLgh0…
        </item>
    </string-array>
</resources>

Build and run your project.

There is no visible change but you are now ready to add downloadable fonts.

Downloadable fonts programatically

The FontQuiz application is still missing one key feature – text in quiz questions always must be in font from the question.

You can implement requests to fetch downloadable fonts and apply them to a View in code as well. You must use the FontsContractCompat class from the support library to support Android versions older than 8.0.

Your task will be to use it to request and set a random font on the quiz question Activity.

Open QuestionActivity and find the showFont() method.

Font family names available for the quiz are in a list in res\values\family_names.xml file. The logic to pick a random font for the question and four offered answers is already there. Your job is to request and show the font with the name passed to showFont().

First, hide all the buttons and show a ProgressView that will show hat font is loading by adding:

buttons.forEach { button -> button.isEnabled = false }
progressView.visibility = View.VISIBLE

Build and run your project.

Click on “Start Quiz” and you’ll see disabled buttons and a progress indicator when the first question opens up:

Time to add the font request.

Creating a font request

Your next task is to add requesting downloadable fonts to QuestionActivity.

Create a query string and a request for the downloadable font:

val query = "name=$familyName"
val request = FontRequest(
  "com.google.android.gms.fonts",
  "com.google.android.gms",
  query,
  R.array.com_google_android_gms_fonts_certs
)
Note: make sure you’re using the FontRequest class from android.support.v4.provider package. The one from android.provider is not compatible with support library.

When creating a FontRequest you have to pass:


  • provider authority – only Google provider com.google.android.gms.fonts is available so far
  • provider package – for Google font provider it’s com.google.android.gms
  • query – query string that describes the font you are requesting
  • array of certificates – to verify the provider

To request a new font use requestFont() method from FontsContractCompat. Add following to the end of showFont():

FontsContractCompat.requestFont(
    this,
    request,
    object : FontsContractCompat.FontRequestCallback() {
      override fun onTypefaceRetrieved(typeface: Typeface?) {
        
      }

      override fun onTypefaceRequestFailed(reason: Int) {

      }
    },
    handler
)

Requesting a downloadable font is an asynchronous operation. Method requestFont() passes the result through a FontsContractCompat.FontRequestCallback interface. If the request is successful, FontContractorCompat calls onTypefaceRetreived(). Use the Typeface instance passed to set the font on a View. Enable all the buttons and hide progress indicator:

override fun onTypefaceRetrieved(typeface: Typeface?) {
  buttons.forEach { button -> button.isEnabled = true }

  progressView.visibility = View.INVISIBLE
  fontTextView.typeface = typeface
}
    

In case of an error, FontContractorCompat will call onTypefaceRequestFailed(). Use it to display an error message by calling showError() and passing it error code:

override fun onTypefaceRequestFailed(reason: Int) {
  showError(reason)
}

The last thing you need when requesting fonts is a Handler instance.

Note: In short, a Handler enables you to send code to a different thread which it will then execute.

FontContractorCompat uses it to execute retrieving a font on a Thread associated with that Handler. Make sure you provide a Handler that is not associated with a UI thread.

val handlerThread = HandlerThread("fonts")
handlerThread.start()

handler = Handler(handlerThread.looper)

For convenience, create a private field that will hold the handler and a property that will initialize and retrieve it:

private var handler: Handler? = null

private val handlerThreadHandler: Handler
  get() {
    if (handler == null) {
      val handlerThread = HandlerThread("fonts")
      handlerThread.start()
      handler = Handler(handlerThread.looper)
    }

    return handler ?: throw AssertionError("Set to null by another thread")
  }

Using the handlerThreadHandler property will initialize the handler on the first use and return it.

The call at the end of showFont() should now look like:

FontsContractCompat.requestFont(
    this,
    request,
    object : FontsContractCompat.FontRequestCallback() {
      override fun onTypefaceRetrieved(typeface: Typeface?) {
        buttons.forEach { button -> button.isEnabled = true }

        progressView.visibility = View.INVISIBLE
        fontTextView.typeface = typeface
      }

      override fun onTypefaceRequestFailed(reason: Int) {
        showError(reason)
      }
    },
    handlerThreadHandler
)

Build and run your project. Start the quiz:

Now you can see the text on each question in the appropriate font! :]