Text widget with wrong emoji on initialization in Flutter

789

After creating this question, doing many unsuccessful tries, and no working answer, I've created an issue on Flutter's official Github repository.

After I showed my problem, an user explained that that is a bug related to how Flutter renders and caches the fonts, in case of the default font cannot handle properly some character:

The first time that dualCharEmoji is rendered Flutter sees that the default font does not handle the 2694 character. [...]it can render the basic 2694 crossed swords character but not the 2694 FE0F emoji.

When singleCharEmoji is rendered Flutter now includes the NotoSansSymbols font in the list of fonts given to the HarfBuzz text shaper.[...] and matchFamilyStyleCharacter returns NotoColorEmoji. Flutter also adds this font to the cache.

The next time the engine tries to render dualCharEmoji it provides both cached fonts (NotoSansSymbols and NotoColorEmoji) to HarfBuzz. Now that HarfBuzz is aware of the sequences defined in NotoColorEmoji it can recognize the 2694 FE0F emoji and return it to Flutter.

I've referenced here only some parts of the discussion. You can read the full discussion and explanation on the issue page I've linked above.

Users suggested some workarounds, namely two:

First, you can force the engine to pre-cache a font that handles emojis correctly. You can do this by adding the following code in the build, or the initState method (anywhere that runs before building the Text Widget):

import 'dart:ui';

ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(locale: window.locale));
pb.addText('\ud83d\ude01');  // smiley face emoji
pb.build().layout(ParagraphConstraints(width: 100));

This worked on my example project, but as stated on the Issue page:

However, this relies on implementation details of the current Flutter text engine that may change in the future.

So be aware that this workaround can stop working at any given time.

The second workaround is given as follow:

For now the work around is to list the desired font in the text style and it will be resolved correctly.

Which is simply to give a TextStyle property to the Text Widget, with its fontFamily (or the fontFamilyFallback) property set. The chosen font must already support all characters required. This also worked for me, however I had to include a custom font from my computer (or from a public online package).

Share:
789
Naslausky
Author by

Naslausky

Self-learner

Updated on December 15, 2022

Comments

  • Naslausky
    Naslausky over 1 year

    I want to display an emoji within a text widget, using Flutter.

    When I copy an example emoji from the internet, some of them shows up with 2 characters in my IDE.

    E.g:

    static String dualCharEmoji = "⚔️";
    static String singleCharEmoji = "🗡";
    

    When I use this variable in the text widget, both of them work fine:

    Text("⚔️",)
    Text("🗡",)
    

    However, only when first running the app, the dual character emoji shows up as its first character only.

    i.e. Only when first opening the app, the sword icon shows up as instead of as ⚔️

    After it gets reloaded it gets fixed, and hot reloading/hot restarting does not makes it bug again.

    My question is:

    Is this a bug? Am I missing some detail here? Why does it only happen when first opening the app?

    How can I show a 2 sized emoji from the start?

    I'm using the following Flutter version:

    >flutter --version
    Flutter 1.9.1+hotfix.4 • channel stable • https://github.com/flutter/flutter.git
    Framework • revision cc949a8e8b (9 weeks ago) • 2019-09-27 15:04:59 -0700
    Engine • revision b863200c37
    Tools • Dart 2.5.0
    

    See the minimum reproducible example below:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key}) : super(key: key);
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      static String dualCharEmoji = "⚔️";
      static String singleCharEmoji = "🗡";
      String text = dualCharEmoji;
      int count = 0;
      void swapText() {
        setState(() {
          if (count % 2 == 0)
            text = singleCharEmoji;
          else
            text = dualCharEmoji;
          count++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  text,
                  style: TextStyle(fontSize: 50),
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: swapText,
          ),
        );
      }
    }