A Material Design bottom-sheet widget and accompanying ContentProvider that lets users search, browse and download fonts from the Google Fonts catalog directly inside an Android app. Selected fonts can be downloaded, cached locally and applied to any TextView as a Typeface.
- Material bottom-sheet UI – launches as a full-height
BottomSheetDialogFragmentwith a search bar, refresh and close actions. - Google Fonts integration – pulls typeface families, categories, variants and subsets straight from the Google Fonts Web API.
- Paged loading – uses AndroidX Paging 3 with a Room-backed
RemoteMediatorso fonts are fetched on demand and cached offline. - Live search – debounced search field filters both the local cache and remote pages.
- Typeface reader –
TypefaceReaderdownloads.ttffiles, stores them in the cache directory and returns liveandroid.graphics.Typefaceobjects. - Content provider – exposes the cached typeface/font tables through
TypefaceProviderso other apps (or components) can query fonts via a content URI. - Theming – every visual aspect can be overridden through theme attributes.
- Compose module – an early Jetpack Compose (
font-picker-compose) variant is included alongside the classic View-based library.
| Module | Description |
|---|---|
font-picker |
Core View-based library: bottom sheet, paging, Room database, networking and TypefaceProvider. |
font-picker-compose |
Work-in-progress Jetpack Compose port exposing FontPickerBottomSheet. |
example |
Sample app demonstrating the View-based API. |
example-compose |
Sample app for the Compose variant. |
benchmark |
Macrobenchmark module for measuring library performance. |
- Android
minSdk21 (Android 5.0 Lollipop) or higher compileSdk35- Java / Kotlin JVM target 17
- A valid Google Fonts API key – get one from the Google Cloud Console
Add the dependency to your module's build.gradle.kts (or build.gradle):
dependencies {
implementation 'com.aureusapps.android:font-picker:1.0.4'
}The library is published to Maven Central. Make sure mavenCentral() is listed under repositories in your settings.gradle.kts.
- Open the Google Cloud Console.
- Create (or select) a project and enable the Google Fonts Developer API.
- Create an API key under APIs & Services → Credentials.
- Store the key safely – it will be passed to the picker at runtime.
The sample projects read the key from a FONTS_API_KEY Gradle property and inject it into BuildConfig. You can do the same or load it from any secure source your app uses.
The provider exposes the cached typeface/font tables through a content URI. Declare it in your app/src/main/AndroidManifest.xml inside the <application> tag, using your application id as the authority:
<provider
android:name="com.aureusapps.android.fontpicker.provider.TypefaceProvider"
android:authorities="${applicationId}.fontpicker.typefaceprovider"
android:exported="false" />The library already merges this permission, but if your app does not declare it elsewhere make sure it is present:
<uses-permission android:name="android.permission.INTERNET" />FontPickerConfig wires the picker to your FragmentManager and a FontPickerListener. Keep it as a field of your Activity/Fragment so it survives configuration changes:
private val fontPickerListener = FontPickerListener { font ->
// `font` is a com.aureusapps.android.fontpicker.db.tables.Font
Log.d("FontPicker", "Selected font: $font")
}
private val fontPickerConfig = FontPickerConfig(
fragmentManager = supportFragmentManager,
fragmentTag = FontPickerDialogFragment.FRAGMENT_TAG,
fontPickerListener = fontPickerListener,
fontsAPIKey = API_KEY
)Call FontPickerConfig.showFontPicker() from any click handler:
findViewById<Button>(R.id.open_font_picker).setOnClickListener {
fontPickerConfig.showFontPicker()
}Use TypefaceReader to download and read the selected font as a Typeface. Build the reader once (e.g. in onCreate) and reuse it:
private lateinit var typefaceReader: TypefaceReader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
typefaceReader = TypefaceReader.create(cacheDirectory = cacheDir.absolutePath)
}
private val fontPickerListener = FontPickerListener { font ->
lifecycleScope.launch {
val typeface = withContext(Dispatchers.IO) {
typefaceReader.readTypeface(font)
}
textView.typeface = typeface
}
}When the host is destroyed, release the registered fragment lifecycle callbacks:
override fun onDestroy() {
fontPickerConfig.release()
super.onDestroy()
}A complete, runnable example lives in the example module.
| Class / Interface | Package | Purpose |
|---|---|---|
FontPickerConfig |
com.aureusapps.android.fontpicker |
Holds the picker configuration; opens and releases the bottom sheet. |
FontPickerDialogFragment |
com.aureusapps.android.fontpicker |
The BottomSheetDialogFragment that renders the picker UI. |
FontPickerListener |
com.aureusapps.android.fontpicker.ui |
Functional callback invoked with the picked Font. |
TypefaceReader |
com.aureusapps.android.fontpicker.data |
Downloads, caches and reads .ttf files into Typeface instances. |
TypefaceProvider |
com.aureusapps.android.fontpicker.provider |
ContentProvider exposing the typeface/font cache. |
TypefaceContract |
com.aureusapps.android.fontpicker.provider |
Constants (URIs, columns, MIME types) for querying the provider. |
FontPickerBottomSheet |
com.aureusapps.android.fontpicker |
Compose variant entry point (experimental). |
data class Font(
val id: Long,
val typefaceId: Long,
val variant: String, // e.g. "regular", "700italic"
val fontUrl: String // direct .ttf URL on Google Fonts CDN
)suspend fun readTypeface(font: Font): Typeface? // load cached .ttf as Typeface
suspend fun downloadTypeface(font: Font): Typeface? // force re-download
suspend fun deleteTypefaces() // clear the on-disk font cache
fun getTypefaceFile(font: Font): File // path of the cached .ttfThe picker resolves its look-and-feel from the fontPickerStyle theme attribute. Override it in your app theme to customise paddings, corner radii, icons, button styles and more:
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="fontPickerStyle">@style/FontPickerStyle</item>
</style>
<style name="FontPickerStyle">
<!-- Sub-styles -->
<item name="textInputStyle">@style/TextInputStyle</item>
<item name="iconButtonStyle">@style/IconButtonStyle</item>
<item name="typefaceCardStyle">@style/TypefaceCardStyle</item>
<item name="materialButtonStyle">@style/MaterialButtonStyle</item>
<!-- Dimensions -->
<item name="paddingNormal">8dp</item>
<item name="paddingLarge">16dp</item>
<item name="paddingExtraLarge">24dp</item>
<item name="cornerRadiusNormal">8dp</item>
<item name="iconSizeNormal">24dp</item>
<item name="strokeWidthNormal">1dp</item>
<!-- Icons -->
<item name="searchIcon">@drawable/ic_baseline_search_24</item>
<item name="filterIcon">@drawable/ic_baseline_filter_list_24</item>
<item name="refreshIcon">@drawable/ic_baseline_refresh_24</item>
<item name="closeIcon">@drawable/ic_baseline_close_24</item>
<item name="warningIcon">@drawable/ic_baseline_warning_24</item>
<item name="clearIcon">@drawable/ic_baseline_clear_24</item>
<item name="errorIconTint">@color/red</item>
</style>
<style name="IconButtonStyle" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="iconPadding">0dp</item>
<item name="android:insetTop">0dp</item>
<item name="android:insetBottom">0dp</item>
<item name="android:paddingLeft">6dp</item>
<item name="android:paddingRight">6dp</item>
<item name="android:minWidth">36dp</item>
<item name="android:minHeight">36dp</item>
<item name="cornerRadius">?attr/cornerRadiusNormal</item>
<item name="strokeWidth">?attr/strokeWidthNormal</item>
<item name="iconSize">?attr/iconSizeNormal</item>
</style>
<style name="TextInputStyle"
parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense">
<item name="boxCornerRadiusTopStart">?attr/cornerRadiusNormal</item>
<item name="boxCornerRadiusBottomEnd">?attr/cornerRadiusNormal</item>
<item name="boxCornerRadiusBottomStart">?attr/cornerRadiusNormal</item>
<item name="boxCornerRadiusTopEnd">?attr/cornerRadiusNormal</item>
</style>
<style name="TypefaceCardStyle" parent="Widget.MaterialComponents.CardView">
<item name="downArrowIcon">@drawable/ic_baseline_keyboard_arrow_down_24</item>
<item name="upArrowIcon">@drawable/ic_baseline_keyboard_arrow_up_24</item>
<item name="cardElevation">2dp</item>
<item name="cardCornerRadius">?attr/cornerRadiusNormal</item>
</style>
<style name="MaterialButtonStyle" parent="Widget.MaterialComponents.Button">
<item name="cornerRadius">?attr/cornerRadiusNormal</item>
</style>
</resources>UI (FontPickerDialogFragment)
│ submits
▼
ActionDispatcher ── FontPickerAction ──▶ ActionProcessor
│ │
│ ▼
▼ FontFetcherActionProcessor (Retrofit + Room)
FontPickerViewModel │
│ PagingData ▼
▼ TypefaceDatabase (Room) + RemoteMediator
RecyclerView (PagingDataAdapter) │
│ ▼
▼ Google Fonts API
TypefaceReader ──▶ .ttf cache ──▶ Typeface
Internally the library uses:
- Dagger for dependency injection (
di/package). - Room (
TypefaceDatabase) to cache downloaded font metadata. - Retrofit + OkHttp + Gson to talk to the Google Fonts service.
- AndroidX Paging 3 with a
RemoteMediatorthat bridges the network and the local cache. - An action/state pipeline (
actions/,states/,events/) that drives the UI reactively fromFontPickerViewModel.
# 1. Clone the repository
git clone https://github.com/UdaraWanasinghe/android-font-picker.git
cd android-font-picker
# 2. Provide your Google Fonts API key
echo "FONTS_API_KEY=YOUR_API_KEY" >> local.properties
# 3. Build and run the example app
./gradlew :example:assembleDebug
./gradlew :example:installDebugThe build depends on the com.aureusapps:version-catalog and com.aureusapps.gradle:plugin-utils plugins declared in settings.gradle.kts, which are resolved from mavenLocal() / Gradle Plugin Portal.
Released under the MIT License.
MIT License
Copyright (c) 2022 Udara Wanasinghe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
If you find this library useful, please consider buying me a coffee.
