Skip to content

About

Bottom sheet widget and the content provider to search and download fonts from Google Fonts.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

android-font-picker

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.

Font picker demo

Features

  • Material bottom-sheet UI – launches as a full-height BottomSheetDialogFragment with 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 RemoteMediator so fonts are fetched on demand and cached offline.
  • Live search – debounced search field filters both the local cache and remote pages.
  • Typeface reader – TypefaceReader downloads .ttf files, stores them in the cache directory and returns live android.graphics.Typeface objects.
  • Content provider – exposes the cached typeface/font tables through TypefaceProvider so 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.

Modules

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.

Requirements

  • Android minSdk 21 (Android 5.0 Lollipop) or higher
  • compileSdk 35
  • Java / Kotlin JVM target 17
  • A valid Google Fonts API key – get one from the Google Cloud Console

Installation

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.

Getting a Google Fonts API key

  1. Open the Google Cloud Console.
  2. Create (or select) a project and enable the Google Fonts Developer API.
  3. Create an API key under APIs & Services → Credentials.
  4. 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.

Quick start

1. Register the TypefaceProvider

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" />

2. Add the INTERNET permission

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" />

3. Create a FontPickerConfig

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
)

4. Show the bottom sheet

Call FontPickerConfig.showFontPicker() from any click handler:

findViewById<Button>(R.id.open_font_picker).setOnClickListener {
    fontPickerConfig.showFontPicker()
}

5. Apply the picked font

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
    }
}

6. Release the config

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.

Public API reference

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).

Font model

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
)

TypefaceReader API

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 .ttf

Styling

The 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>

Architecture overview

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 RemoteMediator that bridges the network and the local cache.
  • An action/state pipeline (actions/, states/, events/) that drives the UI reactively from FontPickerViewModel.

Building from source

# 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:installDebug

The 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.

License

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.

Appreciate my work!

If you find this library useful, please consider buying me a coffee.

Buy Me A Coffee

About

Bottom sheet widget and the content provider to search and download fonts from Google Fonts.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages