Jetpack Compose: Custom Font Integration

Jetpack Compose, Android’s modern UI toolkit, makes building beautiful and responsive user interfaces easier than ever. One key aspect of UI design is typography, and using custom fonts can greatly enhance the look and feel of your app. In this comprehensive guide, we’ll explore how to integrate custom fonts into your Jetpack Compose project.

Why Use Custom Fonts?

  • Brand Identity: Using a custom font can reinforce your brand identity, making your app more recognizable.
  • Enhanced Aesthetics: Custom fonts can significantly improve the visual appeal and overall aesthetics of your app.
  • Unique User Experience: Stand out from the crowd by providing a unique and tailored experience.

Steps to Integrate Custom Fonts in Jetpack Compose

Here’s a step-by-step guide on how to add and use custom fonts in your Jetpack Compose project.

Step 1: Add Font Files to Your Project

First, you need to add your custom font files to your Android project. The recommended location is the res/font directory. If this directory doesn’t exist, you’ll need to create it.

  1. Create a new directory named font inside the res directory.
  2. Copy your font files (.ttf or .otf) into the res/font directory.

For example, if you have a font named “MyCustomFont”, you might have files like MyCustomFont-Regular.ttf and MyCustomFont-Bold.ttf in your res/font directory.

Step 2: Create a FontFamily

Next, define a FontFamily that references your font files. This is done using the Font class from androidx.compose.ui.text.font.


import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import com.example.your_app.R

val MyCustomFont = FontFamily(
    Font(R.font.mycustomfont_regular, FontWeight.Normal),
    Font(R.font.mycustomfont_bold, FontWeight.Bold)
)

In this example:

  • We import necessary classes from the androidx.compose.ui.text.font package.
  • We declare a FontFamily named MyCustomFont.
  • We create two Font instances, one for the regular weight and one for the bold weight. R.font.mycustomfont_regular and R.font.mycustomfont_bold are references to your font files in the res/font directory. Adjust these based on your file names.
  • The FontWeight parameter specifies the weight of each font file.

Step 3: Define a TextStyle

Now, create a TextStyle that uses your custom FontFamily. You can do this as part of your theme or as a standalone style.


import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp

val customTextStyle = TextStyle(
    fontFamily = MyCustomFont,
    fontWeight = FontWeight.Normal,
    fontSize = 16.sp
)

In this example:

  • We create a TextStyle named customTextStyle.
  • We set the fontFamily to our MyCustomFont.
  • We set the fontWeight to FontWeight.Normal.
  • We set the fontSize to 16.sp.

Step 4: Apply the TextStyle in Your Composables

Finally, apply your custom TextStyle to your composables, such as Text.


import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun CustomFontExample() {
    Text(
        text = "Hello, Custom Font!",
        style = customTextStyle
    )
}

@Preview(showBackground = true)
@Composable
fun CustomFontExamplePreview() {
    CustomFontExample()
}

Here, the Text composable will render “Hello, Custom Font!” using the MyCustomFont family, with the normal font weight, and a font size of 16sp.

Using Custom Fonts in Themes

To maintain consistency and reusability, it’s a good practice to integrate custom fonts into your app’s theme. If you have custom typography set in a Theme, follow these guidelines:

Update Your Theme

You’ll typically have a Theme composable set up. Update the Typography in your Theme definition to include your custom font family:


import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.your_app.MyCustomFont

val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = MyCustomFont,  // Using your custom font here
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    ),
    titleLarge = TextStyle(
        fontFamily = MyCustomFont,  // Using your custom font here
        fontWeight = FontWeight.Bold,
        fontSize = 22.sp
    ),
    // Other default text styles can also be defined with MyCustomFont
    // ...
)

Applying Custom Fonts via Material Theme

After setting up the typography in your theme, apply your themes throughout the Composables as such:


import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun ThemedCustomFontExample() {
    Text(
        text = "Hello, Themed Custom Font!",
        style = MaterialTheme.typography.bodyLarge  // Now uses MyCustomFont
    )
}

@Preview(showBackground = true)
@Composable
fun ThemedCustomFontExamplePreview() {
    ThemedCustomFontExample()
}

Loading Fonts Asynchronously (Best Practices for Performance)

For a better user experience, particularly when dealing with large font files, you should load fonts asynchronously. Android and Compose handles much of this inherently through its resource management but explicit handling ensures fonts are loaded more efficiently:

Using Font() correctly leverages built-in caching

The simplest (and often best performing method) is ensuring the Font() resource loader utilizes Compose and Androids built-in caching mechanisms. The earlier examples leveraged this effectively when passing the resource ID for font files.

Potential (less common) Async Method:

If, hypothetically you are loading a font programmatically, ensure the .ttf or .otf files are loaded from the Resources asynchronously to minimize blocking on the UI Thread.

Note It is exceedingly rare you would need an Async Task with Font() when loading directly from resource directories as described previously

Complete Example

Here is a complete example combining all steps, to achieve clarity.


package com.example.customfont

import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp

// Step 1 & 2: Font Resources should already exist in res/font

// Step 3: Define a FontFamily
val MyCustomFont = FontFamily(
    Font(R.font.pacifico_regular, FontWeight.Normal)  //  make sure R file matches name you gave
)

// Step 4: Define a TextStyle
val customTextStyle = TextStyle(
    fontFamily = MyCustomFont,
    fontWeight = FontWeight.Normal,
    fontSize = 24.sp
)

// Step 5: Use the custom TextStyle in a Composable
@Composable
fun CustomFontExample() {
    Text(
        text = "Hello, Custom Font!",
        style = customTextStyle
    )
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    CustomFontExample()
}

Remember to replace the R.font.pacifico_regular with your actual resource id and replace com.example.customfont package name in above script with yours. Finally copy your `.ttf` into your project /res/font directory

Troubleshooting

  • Font Not Applying:
  • Ensure that the font file name in the code matches the file name in the res/font directory.
  • Clean and rebuild your project.
  • App Crashing:
  • Check if the font file is corrupted. Try a different font file or redownload the existing one.
  • Ensure that all the dependencies are correctly added.

Conclusion

Integrating custom fonts in Jetpack Compose enhances your app’s visual identity and user experience. By following these steps, you can easily add custom fonts and use them across your application, providing a more personalized and professional look. Remember to choose fonts that are readable and align with your app’s design to create a cohesive and appealing user interface.