LY Corporation Tech Blog

We are promoting the technology and development culture that supports the services of LY Corporation and LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam).

This post is also available in the following languages. Japanese, Korean

Adding font customization to LINE for Android

Following the merger of LINE Corporation and Yahoo! Japan Corporation, we launched a subscription-based membership called LYP Premium. LYP Premium offers not only the benefits previously provided by Yahoo! Premium, but also new LINE-specific benefits. One feature of LYP Premium is the "members-only font" function, which allows users to change the display font in the LINE app to their preferred font. When implementing this feature, we had three main requirements:

  1. Show a list of fonts to the user.
  2. Dynamically download the font chosen by the user.
  3. Apply the downloaded font across the entire app.

In this article, I'm going to share our experiences dealing with the second and third requirements.

How to apply fonts in Android

To understand how to apply fonts in Android, you first need to know the definitions and differences of the following three terms:

  • Font file: This is a file that collects information (style, weight, size) used to display characters on the screen.
  • FontFamily: This is a collection of fonts defined by style and weight.
  • Typeface: This is a class that allows you to load a font into memory and apply it as a system resource.

Using these terms, we can redefine our requirements as "dynamically downloading a font file from the server" and "loading it into the Typeface class to apply it to the view".

Now, let's look at both static and dynamic ways to apply fonts in Android.

Static font application method

The most basic way to apply a font in Android is to add the font file to the app resources and define it as a style resource to use where needed. The font defined as a style can be applied uniformly throughout the app by specifying it as a theme. However, this method has the following two drawbacks:

  • All font lists are included in the app bundle, unnecessarily increasing the size of the app.
  • Font updates are not possible until the app is updated.

Dynamic font application method - downloadable fonts

To overcome the drawbacks of the static font method, Android offers a dynamic font system called downloadable fonts. This allows you to use fonts without adding the font file to the app resources, unlike the static method. Let's take a look at how this feature works and how to use it.

Dynamically fetching the font selected by the user with the downloadable fonts feature

Let's understand how the dynamic font system of Android, known as the downloadable fonts feature, works. We'll do this by exploring how to fetch fonts from Google Fonts using this feature.

Fetching fonts from Google Fonts with the downloadable fonts feature

The image below shows how the downloadable fonts feature works.

Source: Android Developer Guide - downloadable fonts

Let's assume that App1 wants to use a specific font file. When App1 requests the desired font information through FontsContract, FontProvider downloads the font file from the network or returns a cached file. Once a font is downloaded, it's managed at the OS level, and if another app requests the same font, the previously downloaded font file is reused. This saves network usage and disk space.

Here, the FontProvider downloads the font file from Google Fonts, a font provider. Google Fonts is an open-source library that provides fonts through an API. Android allows various fonts to be shared and used across different apps on a single device using Google Fonts.

Let's look at how the downloadable fonts feature works in a bit more detail.

  1. The client that wants to use a new font sends a request to FontsContractCompat with a FontRequest class that includes the specifications of the ContentProvider (authority, package, cert) and what font it wants (query).
  2. FontsContractCompat passes the FontRequest sent by the client to the FontProvider.
  3. The FontProvider finds the ContentProvider that matches the specifications through the PackageManager (in this example, Google Fonts' ContentProvider is returned).
  4. The FontProvider requests the font from the ContentProvider using FontRequest#Query.
  5. The ContentProvider first looks for the corresponding font in the cache, and if it's not there, it downloads it from the server.
  6. The font received from the ContentProvider is put into the FontInfo and returned.
  7. Finally, FontsContractCompat loads the FontInfo into the Typeface and returns it.

Below is an example code. As shown below, you can request a font programmatically through FontsContractCompat by putting information into a FontRequest, and use the returned Typeface.

class FontRepository {

	fun getTypeFace(): TypeFace {

		val request = FontRequest(

			"com.google.android.gms.fonts",

			"com.google.android.gms",

			"ABeeZee",

			R.array.com_google_android_gms_fonts_certs"

		)

		return suspendCancellableCoroutine { continuation ->

			val callback = object : FontsContractCompat.FontRequestCallback() {

	            override fun onTypefaceRetrieved(typeface: Typeface?) {

    	            continuation.resume(typeface)

        	    }



            	override fun onTypefaceRequestFailed(reason: Int) {

                	continuation.resume(null)

	            }

    	    }

			FontsContractCompat.requestFont(context, request, callback, handler)

		}

	}

}

Also, you can access the font resources in XML and use it as the fontFamily of a TextView.

[res/font/abeeZee.xml]

<?xml version="1.0" encoding="utf-8"?>

<font-family xmlns:app="http://schemas.android.com/apk/res-auto"

    app:fontProviderAuthority="com.google.android.gms.fonts"

    app:fontProviderPackage="com.google.android.gms"

    app:fontProviderQuery="ABeeZee" 

    app:fontProviderCerts="@array/com_google_android_gms_fonts_certs" />

---

[main_activity.xml]

<LinearLayout...>

	<TextView

		android:id="@+id/text_view"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:fontFamily="@font/abeezee" />

<LinearLayout/>

Although the explanation of how it works is complex, when you actually use it, as shown in the code above, you can easily get the desired font from Google Fonts as long as you properly request it with the information in the FontRequest.

However, the fonts used in the LINE app are those for which LINE has obtained usage licenses. It can't be obtained through Google Fonts, nor can it be shared with other apps. So, how can we fetch fonts from LINE's server instead of Google Fonts?

Fetching fonts from LINE's server with the downloadable fonts feature

As we saw earlier, when a request is made with FontRequest, this FontRequest is passed to the FontProvider, and the FontProvider finds the ContentProvider that matches the specifications of the FontRequest. Therefore, we determined that if we customize the ContentProvider and request it with matching specifications, we can fetch the font that can be used in the LINE app.

We implemented it in the way that configures a ContentProvider to fetch fonts from LINE's server and delivers a FontRequest tailored to the specifications of that ContentProvider, as shown in the image below.

The actual code would look something like this:

  • LineFontProvider
pakcage com.example.line.font



class LineFontProvider: ContentProvider() {

	override fun query(...): Cursor? {

		// Checks if this ContentProvider can handle the URI.

		if (uri is LineContentUri) return MatrixCursor(...)

		else return null

	}



	override fun openFile(uri: Uri, mode: Mode): ParcelFileDescriptor {

		// Fetches the font file from the LINE server or local cache.

		val file = LineFontRepository.getFontFile(uri)

		return ParcelFileDescriptor.open(fontFile, MODE_READ_ONLY)

	}

}
  • custom_font.xml
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"

        app:fontProviderAuthority="com.example.line.font"

        app:fontProviderPackage="com.example.line"

        app:fontProviderQuery="LineFont"

        app:fontProviderCerts="@array/line_fonts_certs">

</font-family>

When a request is made to FontsContractCompat in this way, the FontProvider requests a font from the LineFontProvider implemented by LINE and then returns it as a Typeface.

Dynamically applying the downloaded font to the LINE app

So far, we've looked at how to fetch the font we want from the LINE app using the downloadable fonts feature and get it as a Typeface. There are several ways to apply this fetched Typeface to the entire LINE app. Let's find out which method is most efficient.

Applying it to each view individually

There are three methods to apply it to each view: the static method, the programmatic method, and the custom view method with the font pre-applied. Let's look at how to use each method and their pros and cons.

Static method
<TextView

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:layout_marginHorizontal="30dp"

    android:fontFamily="@font/custom_font"

    android:text="Apply line font in xml" />
  • How to use: Assign the @font/custom_font resource defined above as the fontFamily of the TextView in the XML layout.
  • Disadvantages
    • You can't dynamically determine and apply whether the user is using a custom font in the XML layout.
    • Each service manager needs to apply it to each view where text is displayed, which requires a lot of work.
Programmatic method
class MainActivity {

	override fun onCreate() {

		...

		if (needToApplyCustomFont) {

			binding.textView.typeface = LineFontRepository.getTypeface()

		}

	}

}
  • How to use: Set the appropriate font to be applied depending on whether the font is used or not in onCreate().
  • Advantages: This method allows you to set the Typeface dynamically, so you can apply it depending on whether a custom font is used.
  • Disadvantages: Each service manager needs to apply it to each view where text is displayed, which requires a lot of work.
Pre-applied custom view method
class FontedTextView(

    context: Context,

    attributeSet: AttributeSet

) : androidx.appcompat.widget.AppCompatTextView(context, attributeSet) {

    init {

		if (needToApplyCustomFont) {

        	typeface = LineFontRepository.getTypeface()

		}

    }

}
  • How to use: Create a custom view that applies the appropriate font depending on whether the font is used or not.
  • Advantages: Each service doesn't need to consider whether the font is used or not, and unnecessary code can be reduced.
  • Disadvantages
    • You need to customize not only the TextView, but also various views that display text, such as Button, Toast, Header, etc.
    • It requires a huge amount of work to replace all views with FontedCustomView.
    • When implementing a new layout, if you don't use the custom view, the font won't be applied, which is easy to overlook.

Because the LINE app is so vast, using the view-based response method as described above would require a lot of work. In order to find a more efficient way to solve the problem, we did more research and came up with the method of setting the font style as a theme, which I mentioned at the beginning of the post.

Setting the downloadable fonts feature as a theme

Setting the font style as a theme was a method we had ruled out because you need to be able to predefine the font resources in XML in order to reference them as a style. However, we found out that we could dynamically fetch fonts from the LINE server using the downloadable fonts feature, and also confirmed that it could be referenced and applied in the XML layout. Therefore, we determined that it was a method that could be sufficiently used. The application method is as follows:

  1. Specify the @font/custom_font resource as a style resource.
    <?xml version="1.0" encoding="utf-8"?>
    
    	<resources>
    
    		<style name="default_font">
    
    			<item name="fontFamily" />
    
    		</style>
    
    		<style name="custom_font">
    
    			<item name="fontFamily">@font/custom_font</item>
    
    		</style>
    
    	</resources>
  2. Set the style resource as a theme when each activity is created (onCreate) using Application#ActivityLifecycleCallbacks.
    class CustomFontThemeApplier : Application.ActivityLifecycleCallbacks {
    
    	
    
    		override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
    
    			if (needToApplyCustomFont) {
    
    				activity.setTheme(customFontStyle)
    
    			}
    
    		}
    
    		...
    
    	}

By using the method of setting the downloadable fonts feature as a theme, we were able to apply the desired font to all places in the LINE app where text is displayed. Each service doesn't need to respond separately to apply the font, and only needs to handle cases where it should be excluded according to the intention, which greatly reduced the amount of work needed to apply the feature.

Conclusion

While implementing the members-only font feature, we used a method of dynamically fetching and applying fonts as a theme, which brought many benefits. However, there are still areas we want to improve.

  • Restart issue: When the font that needs to be applied changes, the app needs to be restarted because the style resources need to be reloaded.
  • Loading delay issue: On some devices, there's a delay in dynamically loading the font, causing the default font to be briefly displayed.

We are researching and trying various measures such as changing the query method of FontRequest or using preload in the splash screen to solve these issues.

References

Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.