Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fonts and Emoji Using Jetpack

Anita
July 22, 2018

Fonts and Emoji Using Jetpack

Talk given at 360|AnDev in Denver on 19th July, 2018.

Android Oreo introduced exciting text changes that make it easier to use custom fonts, communicate with emojis and reduce APK size. Luckily, the Support Libraries from v26 onwards allow us to implement these new features with backwards compatibility.

In this talk, you will learn how to implement these text changes with the help of Support Libraries (now repackaged as AndroidX and part of Jetpack), and their limitations. You will also learn about how they are natively implemented by the framework versus Jetpack.

At the end of this session, you will have an increased understanding of the inner workings of Fonts in XML, Downloadable Fonts and EmojiCompat, as well as be ready to update your apps with them without the fear of the unknown!

Links:
Fonts in XML
https://segunfamisa.com/posts/custom-fonts-with-android-support-library
https://medium.com/google-design/the-android-developers-guide-to-better-typography-97e11bb0e261
https://www.youtube.com/watch?v=TfB-TsLFJdM

Downloadable Fonts
https://github.com/googlesamples/android-DownloadableFonts
https://developers.google.com/fonts/docs/android
https://proandroiddev.com/android-downloadable-fonts-8e60d3e146b7
https://hackernoon.com/downloadable-fonts-for-android-2041235f91e6

EmojiCompat
https://github.com/googlesamples/android-EmojiCompat
https://medium.com/exploring-android/exploring-the-android-emoji-compatibility-library-1b9f3bb724aa
http://dannyroa.com/2017/08/29/future-proof-emojis-on-android/

Anita

July 22, 2018
Tweet

More Decks by Anita

Other Decks in Programming

Transcript

  1. dependencies { implementation ‘com.android.support:appcompat-v7:$version’ or 
 } repositories { maven

    { url ‘https://maven.google.com' } mavenCentral() } build.gradle implementation ‘androidx.appcompat:appcompat:$version'
  2. You can now add custom font files to res/font, and

    use them as @font/lobster. What?
  3. <font-family> <font android:fontStyle="normal" android:fontWeight=“200" android:font=“@font/lato_regular“ app:fontStyle="normal" app:fontWeight=“200" app:font=“@font/lato_regular” /> <font

    android:fontStyle=“bold" android:fontWeight=“600" android:font=“@font/lato_bold” app:fontStyle=“bold" app:fontWeight=“600" app:font=“@font/lato_bold”/> </font-family> lato.xml A font family is a set of font files along with style and weight details
  4. Why? Otherwise we use custom textviews or third-party libs like

    Calligraphy or FontBinding just to use custom fonts!
  5. Why? class CustomFontTextView : TextView { constructor(context: Context) : super(context)

    { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  6. Why? class CustomFontTextView : TextView { constructor(context: Context) : super(context)

    { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  7. <com.winnie.ui.view.CustomFontTextView …… /> Why? class CustomFontTextView : TextView { constructor(context:

    Context) : super(context) { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  8. How? 
 at TypefaceCompatApi26Impl.addFontFromAssetManager(TypefaceCompatApi26Impl.java:150) at TypefaceCompatApi26Impl.createFromFontFamilyFilesResourceEntry(TypefaceCompatApi26Impl.java:218) at TypefaceCompat.createFromResourcesFamilyXml(TypefaceCompat.java:116) at ResourcesCompat.loadFont(ResourcesCompat.java:249)

    at ResourcesCompat.loadFont(ResourcesCompat.java:213) at ResourcesCompat.getFont(ResourcesCompat.java:206) at TintTypedArray.getFont(TintTypedArray.java:119) at AppCompatTextHelper.updateTypefaceAndStyle(AppCompatTextHelper.java:208) at AppCompatTextHelper.loadFromAttributes(AppCompatTextHelper.java:152) at AppCompatTextHelperV17.loadFromAttributes(AppCompatTextHelperV17.java:38) at AppCompatButton.<init>(AppCompatButton.java:77) at android.support.v7.widget.AppCompatButton.<init>(AppCompatButton.java:67) at createView(AppCompatViewInflater.java:109) at AppCompatDelegateImplV9.createView(AppCompatDelegateImplV9.java:1024) at AppCompatDelegateImplV9.onCreateView(AppCompatDelegateImplV9.java:1081) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:772) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730) at android.view.LayoutInflater.rInflate(LayoutInflater.java:863) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.rInflate(LayoutInflater.java:866) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.rInflate(LayoutInflater.java:866) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.inflate(LayoutInflater.java:515) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at android.view.LayoutInflater.inflate(LayoutInflater.java:374) at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287) at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139) AAPT and AppCompat magic!
  9. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Applying Lato
  10. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Applying Lato
  11. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> val typeface = ResourcesCompat.getFont(context, R.font.lato) textView.typeface = typeface Applying Lato
  12. • API 14+ • Make sure to use both android

    and app name spaces • Need to use AppCompat and subclass AppCompatActivity
 Gotchas
  13. • Results in reduced APK size — which can significantly impact your

    app installs! • Sharing fonts results in less usage of precious memory, disk space and cellular data 
 
 Why?
  14. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  15. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  16. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val handlerThread = HandlerThread("fonts") handlerThread.start() val handler = Handler(handlerThread.looper) val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  17. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } //make sure to call this on background thread
 FontsContractCompat .requestFont(this@MainActivity, fontRequest, callback, handler) val handlerThread = HandlerThread("fonts") handlerThread.start() val handler = Handler(handlerThread.looper) val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  18. Using Android Studio & Fonts in XML (recommended) Select a

    text view and click on fontFamily under Attributes in the graphical layout.
  19. font_certs.xml The system uses these certificates to verify the provider’s

    identity, to avoid getting fonts from an unknown source. <resources> <array name="com_google_android_gms_fonts_certs"> <item>@array/com_google_android_gms_fonts_certs_dev</item> <item>@array/com_google_android_gms_fonts_certs_prod</item> </array> <string-array name="com_google_android_gms_fonts_certs_dev"> <item> <!-- string cert --> </item> </string-array> <string-array name="com_google_android_gms_fonts_certs_prod"> <item> <!-- string cert —> </item> </string-array> </resources>
  20. preloaded-fonts.xml When referenced in the manifest, the framework can pre-load

    fonts to avoid delays when the app is launched. <resources> <array name=“preloaded_fonts" translatable="false"> <item>@font/lato</item> <item>@font/lato_bold</item> </array> </resources>
  21. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/>
  22. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/>
  23. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> Now you can apply the downloaded font using Fonts in XML!
  24. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Now you can apply the downloaded font using Fonts in XML!
  25. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Now you can apply the downloaded font using Fonts in XML!
  26. • API 14+, AS 3.0+ if using it to generate

    font files • Using Google Play Services as a font provider only works with version 11+, else it uses the default system font Gotchas
  27. • API 14+, AS 3.0+ if using it to generate

    font files • Using Google Play Services as a font provider only works with version 11+, else it uses the default system font • FontsContractCompat in v27.0.2 has a non-trivial crash that has been fixed in v27.1.0 Gotchas
  28. • ResourcesCompat.getFont() can throw ResourcesNotFoundException if font has not been

    downloaded yet • You will likely have to write your own FontProvider when using a non-Google custom font Gotchas
  29. • ResourcesCompat.getFont() can throw ResourcesNotFoundException if font has not been

    downloaded yet • You will likely have to write your own FontProvider when using a non-Google custom font • Fonts are very persistent once downloaded! For testing : data/data/com.google.android.gms/files/fonts Gotchas
  30. • EmojiCompat helps prevent our apps from showing missing emoji

    characters (“tofu” □) • Helps implement backward-compatible emoji support on devices running Android 4.4+ What?
  31. By using EmojiCompat, your app users do not need to

    update their OS to get the latest emoji! Why?
  32. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  33. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  34. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  35. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs) EmojiCompat.init(config)
  36. Preloading for EmojiCompat <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/> Google

    Play Services can download the font during install or update if added to the app’s Manifest file.
  37. Preloading for EmojiCompat <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/> Google

    Play Services can download the font during install or update if added to the app’s Manifest file.
  38. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  39. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  40. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); Adds ~7MB to your APK, but works on devices without Google Play Services 11+ dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  41. Using EmojiCompat EmojiCompat.get().unregisterInitCallback(…); EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized()

    { val compat = EmojiCompat.get() textInputEditText.text = compat.process( context.getString(R.string.default_text, EMOJI)) } override fun onFailed(throwable: Throwable?) { textInputEditText.text = context.getString(R.string.default_text_without_emoji) } })
  42. Using EmojiCompat EmojiCompat.get().unregisterInitCallback(…); EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized()

    { val compat = EmojiCompat.get() textInputEditText.text = compat.process( context.getString(R.string.default_text, EMOJI)) } override fun onFailed(throwable: Throwable?) { textInputEditText.text = context.getString(R.string.default_text_without_emoji) } })
  43. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  44. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  45. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  46. • hasEmojiGlyph(charSequence, metadataVersion) can check if app can render a

    specific emoji • EDITOR_INFO_METAVERSION_KEY from EditorInfo.extras() bundle EmojiCompat for IMEs
  47. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis Gotchas
  48. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances Gotchas
  49. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances • Make sure to handle the failure cases in the callbacks, otherwise the text may not get set at all Gotchas
  50. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances • Make sure to handle the failure cases in the callbacks, otherwise the text may not get set at all • When using Downloadable Fonts, all its gotchas apply Gotchas