Custom fonts: FontFamily(Font(R.font.X))
Drop your .ttf / .otf into res/font/, register it once at app
startup, then reference it from KBC source exactly the way you would in
any other Compose codebase.
In your KBC source
@KetoyComposable
fun HeroCard() {
Text(
text = "Ship Kotlin. Server-side. Today.",
fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.courgette_regular)),
)
}That's the entire surface area. The compiler plugin notices the
FontFamily(Font(R.font.X)) shape and atomically collapses it into a
single string literal carrying just "courgette_regular". At render
time, your host APK resolves that name to the real FontFamily you
registered, no reflection, no getIdentifier lookups, no risk of R8
silently stripping the resource in release builds.
The five generic family singletons (FontFamily.Default,
FontFamily.SansSerif, FontFamily.Serif, FontFamily.Monospace,
FontFamily.Cursive) work too, with zero host-side setup.
Wiring it on the host, with Hilt
@Provides
@Singleton
fun provideMaterialFontFamilyResolver(): MaterialFontFamilyResolver =
materialFontFamilyResolver {
register(
"courgette_regular",
FontFamily(Font(R.font.courgette_regular)),
)
}
@Provides
@Singleton
fun provideKetoyConfigCustomizer(
fontFamilyResolver: MaterialFontFamilyResolver,
): KetoyConfigCustomizer = KetoyConfigCustomizer { default ->
default.copy(fontFamilyResolver = fontFamilyResolver)
}Every KBC screen rendered through KetoyScreen picks up the resolver
automatically.
Wiring it on the host, without Hilt
class MyApplication : Application() {
lateinit var ketoyRuntime: KetoyRuntime
override fun onCreate() {
super.onCreate()
val fontResolver = materialFontFamilyResolver {
register(
"courgette_regular",
FontFamily(Font(R.font.courgette_regular)),
)
}
val config = KetoyConfig(fontFamilyResolver = fontResolver)
ketoyRuntime = KetoyRuntime(
capabilityRegistry = CapabilityRegistry().apply {
registerCoreCapabilities(context = this@MyApplication)
},
config = config,
)
}
}The R.font.X reference at the register(...) call site is a
compile-time field read, R8 sees it and keeps your font resource
alive in release builds. No -keep rules required.
Multi-font families
val brand = FontFamily(
Font(R.font.inter_regular, FontWeight.Normal),
Font(R.font.inter_medium, FontWeight.Medium),
Font(R.font.inter_bold, FontWeight.Bold),
)
materialFontFamilyResolver {
register("inter_regular", brand)
register("inter_medium", brand)
register("inter_bold", brand)
}KBC source can reference any of the three resource names and get the full family, Compose picks the right weight at render time.