preface

Dynamically modify iconfont? Is it possible?

More and more teams are using Flutter to develop apps. In Flutter, we can easily use iconfont instead of images to display ICONS, just like the front-end: Place the iconFONT font file in the project directory and use the icon in the file in the Flutter code. However, it is difficult to meet the needs of dynamically modifying the icon. If the product manager suddenly wants to change the icon in the project (such as changing the icon in festivals), we usually can only solve the problem by issuing the version, but the steps of issuing the version are tedious and invalid for the old version.

Let’s explore a dynamic loading scheme for IconFONT based on Flutter.

Iconfont principle

Iconfont refers to “font icon”, which is a font file made of ICONS and then displays different ICONS by specifying different characters. In font files, each character corresponds to a Unicode code, and each Unicode code corresponds to a display glyph. Different fonts refer to different glyph, i.e. characters correspond to different glyph. In iconfont, only the glyphs corresponding to Unicode codes are made into ICONS, so different characters end up rendering different ICONS.

Iconfont has the following advantages over images in Flutter development:

  • Small size: You can reduce the installation package size.
  • Vector: Iconfont is a vector icon. Zooming in does not affect its clarity
  • Text styles can be applied: you can change the color, size alignment, and so on of font ICONS just like text.

Because of these advantages, iconFONT was given priority over images in the Flutter project.

Iconfont was used before iconfont became dynamic

In our existing Flutter project, the use of Iconfont is to download the TTF font file to assets folder of the project through [icontfont official website](https://www.iconfont.cn). Then configure in pubsepc.yaml file to implement static loading of TTF font file. fonts: - family: IconFont fonts: - asset: assets/fonts/iconfont.ttfCopy the code

We then define a class (ZcyIcons) to manage all IconData in the iconFont file:

You can write a script to automatically generate code for this class, so that you only need to execute the script every time you update the iconFont file to generate the latest code.

class _MyIcon {
  static const font_name = 'iconfont';
  static const package_name = 'flutter_common';
  const _MyIcon(int codePoint) : super(codePoint, fontFamily: font_name, fontPackage: package_name,);
}

class ZcyIcons {
  static const IconData tongzhi = const _MyIcon(0xe784);
  
  static Map<String, IconData> _map = Map(a); ZcyIcons._();static IconData from(String name) {
    if(_map.isEmpty) {
      initialization();
    }
    return _map[name];
  }

  static void initialization() {
    _map["tongzhi"] = tongzhi; }}Copy the code

When used, there are two ways to call it

  /// Method 1: Load directly
  Icon(ZcyIcons.arrow)
  /// Method 2: Obtain the IconData corresponding to the map through the value of name
  Icon(ZcyIcons.from(name))
Copy the code

Although the second method can dynamically load the corresponding IconData from the map by changing the key value, it is limited to the fact that all IconData are already configured in the map and will not be changed.

Since iconfont is a font file, if the system can dynamically load font files, it must be able to dynamically load iconfont in the same way.

Iconfont dynamic scenario

Step 1: Load the TTF file that is delivered remotely

The Flutter SDK provides the FontLoader class to dynamically load fonts. The core of our solution is the FontLoader class.

It has an addFont method that converts ByteData into a font package and loads it into the application font repository:

class FontLoader{...void addFont(Future<ByteData> bytes) {
    if (_loaded)
      throw StateError('FontLoader is already loaded');
    _fontFutures.add(bytes.then(
        (ByteData data) => Uint8List.view(data.buffer, data.offsetInBytes, data.lengthInBytes)
    ));
  }
  
  Future<void> load() async {
    if (_loaded)
      throw StateError('FontLoader is already loaded');
    _loaded = true;
    final 可迭代<Future<void>> loadFutures = _fontFutures.map(
        (Future<Uint8List> f) => f.then<void>(
            (Uint8List list) => loadFont(list, family)
        )
    );
    returnFuture.wait(loadFutures.toList()); }}Copy the code

We can create an interface to deliver the remote address of the iconFONT font file and the hash value of the file, and compare the hash value of the local font file with the value in the interface every time we start the APP. If any difference exists, download the remote font file to the local PC and use the data format of ByteData for FontLoader to load. Some key codes are attached:

/// Download the remote font file
static Future<ByteData> httpFetchFontAndSaveToDevice(Uri fontUri) {
  return(a)async {
    http.Response response;
    try {
      response = await _httpClient.get(uri);
    } catch (e) {
      throw Exception('Failed to get font with url: ${fontUrl.path}');
    }
    if (response.statusCode == 200) {
      return ByteData.view(response.bodyBytes.buffer);
    } else {
      /// If the execution fails, an exception is thrown.
      throw Exception('Failed to download font with url: ${fontUrl.path}'); }}; }/// The font is loaded from a local file. If the font does not exist, use [loader] to load it
static Future<void> loadFontIfNecessary(ByteData loader, String fontFamilyToLoad) async {
  assert(fontFamilyToLoad ! =null&& loader ! =null);
  
  if (_loadedFonts.contains(fontFamilyToLoad)) {
    return;
  } else {
    _loadedFonts.add(fontFamilyToLoad);
  }
  
  try {
    Future<ByteData> byteData;
    byteData = file_io.loadFontFromDeviceFileSystem(fontFamilyToLoad);
    if (awaitbyteData ! =null) {
      return _loadFontByteData(fontFamilyToLoad, byteData);
    }
    
    byteData = loader();
    if (awaitbyteData ! =null) {
      /// Load the downloaded font file by FontLoader
      final fontLoader = FontLoader(familyWithVariantString);
      fontLoader.addFont(byteData);
      awaitfontLoader.load(); successLoadedFonts.add(familyWithVariantString); }}catch (e) {
    _loadedFonts.remove(fontFamilyToLoad);
    print('Error: unable to load font $fontFamilyToLoad because the following exception occured:\n$e'); }}Copy the code

Step 2: Get the Unicode value to be loaded by the icon name

In practice, we find that we need to specify the codePoint of the font file corresponding to the icon, that is, the Unicode value:

The unicde value of iconfont is used as follows:

// the StringToInt method is defined as "&#xe67b;" From (StringToInt('&#xe67b; '));Copy the code

This usage is not very friendly for our development. We need to find which icon the unicde value corresponds to each time, so we can create a mapping table from the interface where we downloaded the TTF file. The dynamically issued icon name is then associated with Unicode through code during iconFONT initialization.

Interface return data format:

Use of icon in code after changing interface format:

// _aliasMap is a Map of MyIcons. From (StringToInt(_aliasMap['tongzhi']);Copy the code

Suppose we have such a scenario: the APP enters the home page, downloads the latest iconfont. TTF file and loads it, but the Icon has been loaded. How can we dynamically refresh the contents of the current Icon?

Step 3: Load asynchronous optimizations dynamically

The previous steps can complete the update of the local font file after APP startup, but cannot solve the data update after icon has been loaded. Therefore, our dynamic solution needs to rely on FutureBuilder.

FutureBuilder relies on a Future, and dynamically builds itself based on the state of the dependent Future.

We can extend the dynamic method of an Icon to return a FutureBuilder dependent Icon and have FutureBuilder force the Icon to refresh when our iconFONT file is successfully updated.

The main code is as follows:

// extension method of Icon, /// [dynamic] method mainly through [FutureBuilder] to achieve dynamic loading of the core principle of extension DynamicIconExtension on Icon {/// Widget get dynamic {/// Return if (this.icon is! DynamicIconDataMixin) return this; final mix = this.icon as DynamicIconDataMixin; final loadedKey = UniqueKey(); return FutureBuilder( future: mix.dynamicIconFont.loadedAsync, builder: Return KeyedSubtree(context, snapshot) {return KeyedSubtree(key: snapshot.hasData ? loadedKey : null, child: this, ); }); }} // call the following code: Icon(MyIcons. From ('&#xe67b; ')).dynamicCopy the code

So far, our dynamic solution supports the following capabilities:

  • You can dynamically modify existing ICONS in the project
  • Set the icon dynamically by name/code
  • You can use the new icon in your project

The flow chart of the whole scheme is as follows:

conclusion

In general, the core principle of the whole scheme is to realize the dynamic loading of font files by FontLoader. However, it involves some dynamic processing and the exploration of the principle of iconfont, involving multi-point and multi-faceted knowledge, which needs to be integrated and combined together.

The resources

Because Chinese website

Recommended reading

Exploration of low-cost screen adaptation solution for Zhengcai Cloud Flutter

Redis Bitmaps

MySQL InnoDB lock system source code analysis

, recruiting

Zhengcaiyun Technology team (Zero) is a passionate, creative and executive team based in picturesque Hangzhou. The team has more than 300 r&d partners, including “old” soldiers from Alibaba, Huawei and NetEase, as well as newcomers from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. Team in the day-to-day business development, but also in cloud native, chain blocks, artificial intelligence, low code platform system, middleware, data, material, engineering platform, the performance experience, visualization technology areas such as exploration and practice, to promote and fell to the ground a series of internal technical products, continue to explore new frontiers of technology. In addition, the team is involved in community building, Currently, There are Google Flutter, SciKit-Learn, Apache Dubbo, Apache Rocketmq, Apache Pulsar, CNCF Dapr, Apache DolphinScheduler, and Alibaba Seata and many other contributors to the excellent open source community. If you want to change something that’s been bothering you, want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the original savvy is good, but there is always a layer of fuzzy window…… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a technology team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]

Wechat official account

The article is published synchronously, the public number of political cloud technology team, welcome to pay attention to