Adding custom fonts to Android application is one of the most effective ways to differentiate your product from competitors. Even standard system fonts Roboto or Noto Sans do not always convey the desired atmosphere: be it the elegance of premium banking, the playfulness of a children's application, or the minimalism of a fitness tracker. However, many developers encounter problems when integrating custom fonts - from compilation errors to incorrect display on different versions. devices), programmatically using Android.

In this article we will look at three current methods for adding fonts taking into account modern requirements: through XML-resources (suitable for supporting older devices), programmatically using Typeface (flexibility for dynamic change), and the most progressive method - through Jetpack Compose (recommended for new projects). You will also learn how to avoid common mistakes, for example memory leaks when fonts are not loaded correctly from, and how to optimize the APK size when working with large font families.

The material will be useful for both novice developers and experienced professionals who want to refresh their knowledge or migrate to Compose. All code examples are tested on Android 13 (API 33) and are compatible with Android Studio Giraffe, but are provided with backward compatibility to API 16 (where possible).

1. Preparing fonts: formats, licenses and optimization

Before adding a font to a project, you need to prepare it correctly. Not all font formats are supported Androidand some may significantly increase the size of your APK. Here are the key points to consider:

  • ๐Ÿ“Œ Supported formats: .ttf (TrueType), .otf (OpenType) and .ttc (TrueType Collection). Format .woff/.woff2, popular on the web, not supported native.
  • ๐Ÿ” License restrictions: Many free fonts (for example, with Google Fonts) allow use in applications, but some (for example, Helvetica Neue) require the purchase of a license for commercial projects.
  • โš–๏ธ Size optimization: Fonts that support Cyrillic, hieroglyphs or Arabic script can weigh 10+ MB. Use tools like FontForge or TransType to remove unnecessary glyphs.

To check the font license, open the file in any text editor - the information is usually contained in the metadata at the beginning of the file. For example, in fonts from Google Fonts you will see a block with text License: SIL Open Font License, 1.1, which means you can use it freely.

๐Ÿ’ก

If you need a font with Cyrillic support, but the weight of the file is critical, consider using Font Subsetting a tool that leaves only the necessary characters. For example, for the Russian language, a subset with the letters A-Z, a-Z, numbers and basic punctuation marks is sufficient.

Pay special attention to fonts with variable boldness (Variable Fonts). They allow you to dynamically change the text saturation without loading separate files for Light, Regular and Bold. However, their support in Android appeared only with API 26 (Android 8.0), and older versions will require a fallback to standard files.

2. Method 1: Adding a font via XML (Android 8.0+)

The easiest and recommended method for most projects is to use font resourcesavailable from Android 8.0 (API 26). This method allows you to declaring fonts in XML and apply them through styles or directly in markup.

Here are step-by-step guide:

  1. Create a folder font to res: Go to res โ†’ right click โ†’ New โ†’ Android Resource Directory. In the Resource type field, select font.
  2. Add fonts files: Copy font files (for example, montserrat_regular.ttf) to the created folder.
  3. Define the font family: Create a file fonts.xml in the folder res/font and describe the family:
    <?xml version="1.0" encoding="utf-8"?>
    

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

    android:fontProviderAuthority="com.example.app.fontprovider"

    android:fontProviderPackage="com.example.app"

    android:fontProviderQuery="Montserrat"

    android:fontProviderCerts="@array/com_example_app_fonts_certs">

    <font

    android:font="@font/montserrat_regular"

    android:fontStyle="normal"

    android:fontWeight="400"/>

    <font

    android:font="@font/montserrat_bold"

    android:fontStyle="normal"

    android:fontWeight="700"/>

    </font-family>

  4. Apply font in markup: In any TextView specify the attribute android:fontFamily="@font/montserrat".

Folder `font` created in `res`|Font files have correct names (no spaces)|Font family defined in `fonts.xml`|Correct `fontProviderAuthority` specified-->

For device support below API 26 use the library AndroidX AppCompatthat provides backward compatibility via app:fontFamily. For example:

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:fontFamily="@font/montserrat"/>

Via XML (fontFamily)|Programmatically (Typeface.createFromAsset)|Jetpack Compose (Font)|Haven't tried it yet-->

3. Method 2: Programmatic loading of fonts (Typeface)

If you need to dynamically change fonts at runtime (for example, by user choice) or support devices below API 16, use the class Typeface. This method requires more code, but gives full control over the process.

Basic steps:

  1. Place fonts in assets: Create a folder assets/fonts at the root of the project and add font files there.
  2. Load the font in code:
    Typeface typeface = Typeface.createFromAsset(
    

    getAssets(),

    "fonts/montserrat_regular.ttf"

    );

  3. Apply to view:
    TextView textView = findViewById(R.id.my_text_view);
    

    textView.setTypeface(typeface);

Important nuance: do not load fonts in the main thread, especially if the files are large. Use AsyncTask, Coroutines or RxJava for asynchronous loading. Example with coroutines:

lifecycleScope.launch(Dispatchers.IO) {

val typeface = Typeface.createFromAsset(

assets,

"fonts/large_font.ttf"

)

withContext(Dispatchers.Main) {

textView.typeface = typeface

}

}

What happens if you load fonts in the UI thread?

When loading heavy fonts (for example, with hieroglyphs) in the main thread, the application may freeze for 100-500 ms, which leads to ANR (Application Not Responding) on weak devices. Android Studio warns about this via StrictMode or log "Skipped X frames! The application may be doing too much work on its main thread."

โš ๏ธ Attention: If you download fonts from assets to Activity or Fragment, do not forget to reset the link to Typeface to onDestroy()to avoid memory leaks. Example:
override fun onDestroy() {

super.onDestroy()

textView.typeface = null

}

To simplify working with multiple fonts, you can create helper class:

object FontCache {

private val fontMap = mutableMapOf()

fun getTypeface(context: Context, fontName: String): Typeface {

return fontMap[fontName] ?: Typeface.createFromAsset(

context.assets,

"fonts/$fontName"

).also { fontMap[fontName] = it }

}

}

4. Method 3: Fonts in Jetpack Compose

If your project uses Jetpack Compose, the process of adding fonts becomes easier and more declarative. Here you donโ€™t need to work with Typeface directly - just define the font as a resource and use it in compositions.

Instructions:

  1. Add fonts to res/font (similar to the XML method).
  2. Create an object FontFamily:
    val montserratFamily = FontFamily(
    

    Font(R.font.montserrat_regular, FontWeight.Normal),

    Font(R.font.montserrat_bold, FontWeight.Bold)

    )

  3. Apply to text:
    Text(
    

    text = "Hello, Compose!",

    fontFamily = montserratFamily,

    fontWeight = FontWeight.Bold

    )

Advantages of this approach:

  • ๐ŸŽจ Declarative syntax: It is easy to combine fonts with other styles (color, size).
  • ๐Ÿ”„ Automatic optimization: Compose manages font caching itself.
  • ๐Ÿ“ฑ Preview support: Fonts are displayed correctly in preview mode Android Studio.

To dynamically load fonts (for example, from the network) to Compose use the library Accompanist:

implementation "com.google.accompanist:accompanist-webview:0.30.1"
๐Ÿ’ก

In Jetpack Compose, fonts are automatically optimized for the current screen density (dp/sp), which eliminates manual scaling calculations.

5. Optimization and common errors

Even after successfully integrating fonts, you may encounter performance issues or visual artifacts. Here are the most common errors and ways to avoid them:

Problem Cause Solution
Text is displayed in standard font Invalid file name or path to fonts.xml Check character case and file extension (for example, .TTF โ‰  .ttf)
Increase APK size by 5+ MB Fonts with full glyph set Use Font Subsetting or download fonts dynamically
The application crashes on API 19-23 There is not enough memory to load the font Download fonts asynchronously or reduce their size
Text "shakes" during animation The font is not optimized for rendering on screen Use paint.isAntiAlias = true for anti-aliasing

For dynamic loading of fonts (for example, from the Internet) use Google Fonts API or library Calligraphy. Integration example Google Fonts:

implementation "androidx.core:core-ktx:1.10.1"

implementation "com.google.android.gms:play-services-fonts:16.0.1"

โš ๏ธ Attention: If you use ProGuard or R8, add a rule for saving font classes:
-keepclassmembers class * extends android.graphics.Typeface {

<init>(...);

}

Otherwise the fonts may not load after code obfuscation.

6. Testing fonts on different devices Fonts may display differently on devices with different versions and language settings. Here is a checklist for testing:

Fonts may display differently on devices with different screen resolutions, versions Android and language settings. Here is a checklist for testing:

  • ๐Ÿ“ฑ Different DPI: Check the display on ldpi, mdpi, hdpi, xhdpi etc. Some fonts may appear bolder on high-density screens.
  • ๐ŸŒ Localization: If the application supports multiple languages, make sure the font contains the desired glyphs (for example, Arabic script or Thai characters).
  • ๐Ÿ”„ Dynamic scaling: Enable the "Font enlargement" option in the system settings (Settings โ†’ Accessibility โ†’ Font size) and check the responsiveness.
  • ๐Ÿ–ฅ๏ธ Emulators vs real devices: Some emulators (especially x86) may have rendering artifacts that are not present on ARMdevices.

To automate testing, use Android Test Orchestrator with custom Espresso-checks:

@Test

fun testFontApplication() {

onView(withId(R.id.title))

.check(matches(withTypeface(R.font.montserrat_bold)))

}

If you are developing an application for Android TV or Wear OS, please note that these platforms may have additional restrictions on supported fonts. For example, on Wear OS it is not recommended to use fonts larger than 2 MB.

7. Alternative solutions: Google Fonts and Downloadable Fonts

If you don't want to increase the APK size, consider using downloaded fonts (Downloadable Fonts). This mechanism allows you to load fonts on demand from Google Fonts or your own server.

Advantages:

  • โ˜๏ธ No magnification APK size: Fonts are downloaded on first use.
  • ๐Ÿ”„ Automatic updates: If the font has been updated on the server, users will receive the new version without updating the application.
  • ๐ŸŒ Supports all languages: You can load fonts with Cyrillic, hieroglyphs, etc. as needed.

Integration example:

  1. Add dependency:
    implementation "androidx.core:core-ktx:1.10.1"
  2. Define font in res/font:
    <font
    

    android:font="@font/montserrat"

    android:fontStyle="normal"

    android:fontWeight="400"

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

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

    app:fontProviderQuery="Montserrat"

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

  3. Use as a regular font resource.

To use a custom font server, you need to configure Font Provider. Detailed documentation is available in the official guide from Google.

โš ๏ธ Attention: Downloadable fonts require an Internet connection when first launched. If your application must work offline, provide a fallback to a standard font or include critical fonts in the APK.

FAQ: Frequently asked questions about fonts in Android

โ“ Can I use fonts from the APK of another application?

No, Android isolates application resources from each other. However, you can:

  1. Download a font from open sources (for example, Google Fonts).
  2. If the font is proprietary, contact the developer of the original application to obtain the file.

Attempting to extract a font from someone else's APK may violate the license agreement.

โ“ Why the font looks blurry on some devices?

This is due to font smoothing (anti-aliasing) and subpixel rendering (subpixel rendering) Solutions:

  • Install textView.paint.isAntiAlias = true.
  • For Jetpack Compose use Modifier.drawBehind { drawContext.canvas.nativeCanvas.apply { setDrawFilter(PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG, Paint.FILTER_BITMAP_FLAG)) }}.
  • Check whether the "Battery Saver" mode is enabled in the device settings - it can be disabled anti-aliasing.
โ“ How to add a font for WebView?

For WebView use the CSS rule @font-face. Example:

webView.loadDataWithBaseURL(

"file:///android_asset/",

"<html><head><style>@font-face { font-family: 'MyFont'; src: url('fonts/myfont.ttf'); }</style></head><body style='font-family: MyFont'>Hello!</body></html>",

"text/html",

"UTF-8",

null

)

Make sure that the font is available in the specified path (in this case - in assets/fonts/).

โ“ Is it possible to animate the change of fonts?

Yes, but with reservations:

  • In classic Android View change animation Typeface will cause the view to be redrawn, which may look abrupt. For a smooth transition, use ObjectAnimator with a custom TypeEvaluator.
  • In Jetpack Compose you can animate the property fontFamily via animate*AsState, but the transition will be discrete (without morphing between fonts).

Example for Compose:

var isBold by remember { mutableStateOf(false) }

val fontFamily by animateFontFamilyAsState(if (isBold) boldFamily else regularFamily)

Text(

text = "Animated Font",

fontFamily = fontFamily,

modifier = clickable { isBold = !isBold }

)

โ“ How to reduce the font size in APK?

Several effective ways:

  1. Font Subsetting: Remove unnecessary glyphs using FontForge or online services like Transfonter.
  2. Compression: Fonts in the format .ttf can be further compressed using Zopfli or Brotli (although Android does not support them natively, this will reduce the size when transferred over the network).
  3. Downloadable Fonts: Transfer fonts to the server and load on demand.
  4. Use Variable Fonts: One file instead of several (for example RobotoFlex.ttf instead of Roboto-Light.ttf, Roboto-Regular.ttf etc.).

Size comparison:

FontFull setSubset (Latin + Cyrillic)
Roboto Regular1.2 MB350 KB
Open Sans1.5 MB400 KB