Custom Fonts in Android and iOS Applications using Kotlin Multiplatform and Jetpack Compose
In today’s digital landscape, creating visually appealing and unique mobile applications has become crucial for engaging users. One powerful way to enhance the visual appeal of your Android and iOS applications is by incorporating custom fonts. By deviating from the standard system fonts, you can add a touch of personalization and branding to your app. In this article, we will explore how to implement custom fonts in Android and iOS applications using Kotlin Multiplatform and Jetpack Compose, two cutting-edge technologies that streamline cross-platform development.
Preparing the environment
Up to this point of development using Kotlin Multiplatform I will omit many basic aspects, since by this point it is obvious that you are focused on beautifying the application you have already created.
So I will only leave important points to mention.
Android resource configuration
From your build.gradle.kts file (usually the one of the shared module) add the following code to enable the resources where your Fonts will be found.
android {
sourceSets["main"].res.srcDirs("src/androidMain/res")
}
Now, to create the folder of our Android resources we select our androidMain module, right click -> New -> Android Resource Directory
And in the displayed screen select font
This will generate a small new “main” module for Android, where we can add our Fonts (I’ve already added them for the photo)
So far, this is everything we need to get our Android environment ready to use Custom Fonts, so let’s move on to the iOS side.
iOS resource configuration
I would like to say that it is just as simple for iOS, without having to leave the AndroidStudio environment, however there is still no such configuration to Bind sources to the Project, so we will have to open XCode…. This is the only time we will do it.
Secondary click on iosApp.xcodeproj file -> Open in -> Finder
Now, select the X file and double click, it will open XCode and now we will get to work.
With our project open in XCode all we have to do is follow these simple steps:
- Drag your Font sources to the iosApp folder and drop them
2. You will see this popup screen to Link the sources to the Project and add them to the code (this is the part that we can’t do yet from AndroidStudio)
3. Select Copy elements if necessary and also Add to targets: iosApp.
4. Now only the last step is missing, but this can be done from AndroidStudio or XCode and this is to add the Custom Fonts to the info.plist:
XCode:
AndroidStudio:
Perfect, now we can continue with the integration, but I would like to show how it is that now from AndroidStudio (using Git) we can see that the changes made from XCode were also integrated into the project. See the project.pbxproj changes:
Integration
As we know, in order to make use of special functions of each platform within the shared code we use 2 reserved words: expect and actual, where expect is what the shared code needs and actual is how the implementing module will return the value, in this case Android and iOS.
So from our expect in the shared module we will create the function that will return the source:
expect val montserratFontFamily: FontFamily
expect val righteousFontFamily: FontFamily
Ready, now it will appear in red, indicating that this function must be implemented in both platforms, so let’s start.
Android implementation
From our androidMain module we add the following code that will allow us to make use of our res folder where our Fonts are located:
actual val montserratFontFamily: FontFamily = FontFamily(
Font(R.font.montserrat)
)
actual val righteousFontFamily: FontFamily = FontFamily(
Font(R.font.righteous_regular)
)
iOS Implementation
Android was very simple, but now we will show you a simple way to do it using the JetBrains Skia library (already within the KMP project).
This function allows us to return a TypeFace by passing it the name of our Custom Font that we linked to iOS:
import org.jetbrains.skia.FontStyle
import org.jetbrains.skia.Image
import org.jetbrains.skia.Typeface
private fun loadCustomFont(name: String): Typeface {
return Typeface.makeFromName(name, FontStyle.NORMAL)
}
Now the current ones for iOS, but without first commenting that the name to be integrated is the simple name, i.e. without the fonts it may have: Regular, Bold, Italic, etc.
actual val montserratFontFamily: FontFamily = FontFamily(
Typeface(loadCustomFont("montserrat"))
)
actual val righteousFontFamily: FontFamily = FontFamily(
Typeface(loadCustomFont("righteous"))
)
As we can see, with Skia we return a TypeFace (it is not the same as Android), but through Jetpack Compose we can generate a FontFamily that is generated by this special TypeFace parameter.
In addition, we see that the name that we pass is as the name of the font, without extensions or types.
How to use it?
Another great advantage of Jetpack Compose is that you can use the same Theme for both platforms, so for this example, we will use MaterialTheme 3 and the way to integrate is very simple:
- Create our Typography value to indicate the different fonts we will contain and the custom FontFamily we will add:
val typography = Typography(
titleLarge = TextStyle(
fontFamily = righteousFontFamily, // Custom Font
fontWeight = FontWeight.Normal,
fontSize = 24.sp
),
bodyLarge = TextStyle(
fontFamily = montserratFontFamily, // Custom Font
fontWeight = FontWeight.Light,
fontSize = 24.sp
)
)
2. Add the Typography to Theme:
@Composable
fun AppTheme(
content: @Composable () -> Unit
) {
MaterialTheme(
typography = typography, // our custom typography
content = content
)
}
3. Use it:
Text(
text = "Custom Fonts",
style = MaterialTheme.typography.titleLarge, // righteousFontFamily
color = MaterialTheme.colorScheme.onTertiary,
textAlign = TextAlign.Center
)
That’s really all, I hope I can help someone as it was a blocker for me for several days… and that said, keep writing code and sharing!
Lastly, I leave some example screens of an App I created: