In Flutter development, we often encounter the need to replace the current font globally. In Native development, we usually load Asset or downloaded font files. How can WE use Native font files directly in Flutter?

After all, most font files are large, especially some fonts with encryption policies. Creating another font file in Flutter will waste space and duplicate code. Therefore, we need to obtain the Native font file in Flutter.

In Flutter, the system provides us with a FontLoader to load fonts dynamically. As before, we create a Native interface to fetch Byte streams from Native and use the FontLoader to load fonts.

FontLoader loads font data

In order to improve the transmission efficiency, we use BasicMessageChannel as the implementation of Channel, which has been demonstrated in our explanation of the communication mechanism between Flutter and Native. We directly take the Google Demo code and modify the required content. Bring in the FontLoader as shown below.

import 'dart:async';
import 'dart:convert';

import 'package:flutter/services.dart';

class NativeFontApi {
  NativeFontApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger;

  final BinaryMessenger? _binaryMessenger;

  Future<void> loadNativeFont(String fontFamily) async {
    final BasicMessageChannel<ByteData> channel = BasicMessageChannel<ByteData>(
      'dev.flutter.pigeon.NativeFontApi.loadNativeFont',
      const BinaryCodec(),
      binaryMessenger: _binaryMessenger,
    );
    try {
      final uInt8List = utf8.encoder.convert(fontFamily);
      final Future<ByteData> fontData = _loadFontFileByteData(uInt8List.buffer.asByteData(), channel);
      if (await fontData != ByteData(0)) {
        final FontLoader fontLoader = FontLoader(fontFamily);
        fontLoader.addFont(fontData);
        fontLoader.load();
      }
    } catch (e) {
      return;
    }
  }

  Future<ByteData> _loadFontFileByteData(ByteData data, BasicMessageChannel channel) async {
    final ByteData? fontData = await channel.send(data);
    if (fontData != null) {
      return fontData;
    } else {
      return ByteData(0);
    }
  }
}
Copy the code

After loading the font data, we can use it directly in the code. This is the same way we use the new font in the configuration file, just specify fontFamily. The code is shown below.

Text(
  model[index].bookName ?? "",
  style: const TextStyle(
    fontSize: 16,
    fontFamily: 'xxx_Medium_60',
  ),
)
Copy the code

The only thing to note is that we need to initialize our font file when the program starts, as shown below, loading the font file by calling Channel with loadNativeFont.

NativeFontApi().loadNativeFont('xxx_Medium_60');
Copy the code

Native implementation

We modeled our FontBridgeApi on Pigeon’s implementation. The reason we didn’t build it directly on Pigeon was because Pigeon didn’t support Byte arrays yet, so we had to write it ourselves, as shown below.

// Autogenerated from Pigeon (v1.0.15), do not edit directly. https://pub.dev/packages/pigeon import java.nio.ByteBuffer; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryCodec; import io.flutter.plugin.common.BinaryMessenger; @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class FontBridgeApi { public interface NativeFontApi { ByteBuffer loadNativeFont(String familyName); static void setup(BinaryMessenger binaryMessenger, NativeFontApi api) { BasicMessageChannel<ByteBuffer> channel = new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.NativeFontApi.loadNativeFont", BinaryCodec.INSTANCE); if (api ! = null) { channel.setMessageHandler((message, reply) -> { try { String param = new String(message ! = null ? message.array() : new byte[0]); ByteBuffer output = api.loadNativeFont(param); reply.reply(output); } catch (Error | RuntimeException ignored) { } }); } else { channel.setMessageHandler(null); }}}}Copy the code

Here is the implementation interface, as shown below.

private class NativeFontApiImp : FontBridgeApi.NativeFontApi { private val externalAppFontIndexes = intArrayOf( ETConverter.FONT_TYPE_INDEX_XXX_LIGHT, ETConverter.FONT_TYPE_INDEX_XXX_MEDIUM, ETConverter.FONT_TYPE_INDEX_XXXXX ) override fun loadNativeFont(familyName: String?) : ByteBuffer { val trueTypeFolder = File(ApplicationContext.getInstance().filesDir, ETConverter.FOLDER_TRUE_TYPE_FONTS) if (! trueTypeFolder.exists()) trueTypeFolder.mkdirs() val familyNameIndex = when (familyName) { "XXX_Light" -> 0 "XXX_Medium_60" -> 1 "XXXXSerif_Bold" -> 2 else -> 0 } val ttf = File(trueTypeFolder, ETConverter.getFontTypeName(externalAppFontIndexes[familyNameIndex]) + ETConverter.POSTFIX_NEW_TTF) val inputStream: InputStream = FileInputStream(ttf) val output = ByteArrayOutputStream() val buffer = ByteArray(4096) var n = 0 while (true) { try { if (-1 == inputStream.read(buffer).also { n = it }) { inputStream.close() output.close() break } } catch (e: IOException) { e.printStackTrace() } output.write(buffer, 0, n) } return ByteBuffer.allocateDirect(output.toByteArray().size).put(output.toByteArray()) } }Copy the code

So we’re going to initialize it where Pigeon implemented it all the way back, and the code is as follows.

FontBridgeApi.NativeFontApi.setup(flutterEngine.dartExecutor, NativeFontApiImp())
Copy the code

To optimize the

By the way, we easily realized Flutter end loading Native font file, but in the process of code implementation, actually can be optimized in some places, such as load font in the Flutter of the asynchronous method, we can build an enumeration, according to the different status value, to modify the code to perform logic, For example, adding states such as “loading” and “loading failed”. In this way, when the program is abnormal, you can determine whether to skip the following loading process or re-execute the loading process, which can increase the robustness of the code.

// state enum NativeFontLoadState {loading, failed, complete, Future<NativeFontLoadState> loadFontIfNeeded(String fontFamily)Copy the code

Global font

In Flutter, we usually encapsulate some Text components that are specific to our project. In these components, we can specify fontFamily directly. In this way, we don’t need to specify fontFamily repeatedly during business development, just use XXXText.

You can also specify fontFamily directly in themeData of your APP as follows:

theme: ThemeData(
    fontFamily: xxxx,
),
Copy the code

This provides default font support for child components. If you need to change the default font in some scenarios, you can override Text with a different fontFamily.

I would like to recommend my website xuyisheng. Top/focusing on Android-Kotlin-flutter welcome you to visit