flutter_gen icon indicating copy to clipboard operation
flutter_gen copied to clipboard

Add Lottie integration

Open onlymice opened this issue 3 years ago • 5 comments

What does this change?

  • Adds lottie integration.

  • Checks for lottie files by in json tags

  • Example includes on how to use it

  • Docs changed accordingly

Related

Initial Feature Request: https://github.com/FlutterGen/flutter_gen/issues/47 Previous PR that tried to introduce support for Lottie: https://github.com/FlutterGen/flutter_gen/pull/70

What's different now?

Previously HiroyukiTamura https://github.com/FlutterGen/flutter_gen/pull/70 implemented a Lottie integration per request https://github.com/FlutterGen/flutter_gen/issues/47#issuecomment-1151910798 but as stated by britannio's https://github.com/FlutterGen/flutter_gen/pull/70#issuecomment-769968336 in the initial PR, their implementation will be better off checking for keys in the file itself, rather than only it's extension *_lottie.json as it was initially requested two years ago.

So I did.

Other issues

Why there is a lot of changes to tests

Initially Implementation of this feature required File read access, there was no issues with that if you run code as designed e.g

flutter packages pub run build_runner build

But if you try to run tests you might ended up seeing that the relative asset path that you get in isSupport is not relative to the Directory.current.path and the code in 'packages/core/lib' can't access it while running tests.

So I introduced mocked rootPath #assets_gen_integrations_test.dart#L13 for tests and now the AssetType has field for absolutePath that is constructed from the passed rootPath (config.rootPath) and the key/path. Other integrations haven't tried to read files, cause they don't need it, so here we are, feel free to propose a better solution.

What is the value of this and can you measure success?

Measure

  • Pass tests.

  • No linting issues with generated code.

  • Example runs, plays the animation and works well.

Value

  • Others who tends to use Lottie in their projects will be happy to know that flutter_gen supports it

onlymice avatar Aug 27 '22 19:08 onlymice

For Discussion

  1. I have set a strict version for lottie: '>=1.4.1 <2.0.0' because lower versions accept different types for parameters. 1.1. we can use dynamic and recheck for allowed runtimeType in generated constructor. 1.2. leave as is.

  2. keys are checked by every with list lottieKeys that must be included in the file for Lottie to render it properly, the thing is that I haven't found the exact specification for what keys are required (and other libs don't even care if they don't exist), in fact the only thing I have found is that the version in the flutter Lottie package is set to check for >=4.4.0, and layers is where most of the data are. 2.1. choose what keys are actually required. 2.2. leave as is.

onlymice avatar Aug 27 '22 20:08 onlymice

@onlymice

Thank you for your nice PR. I'll check it next week, so please wait. 🙏🏽

wasabeef avatar Aug 30 '22 17:08 wasabeef

My codes.

integration.dart

abstract class Integration {
  // ..
  bool isSupport(AssetType type, {String? rootPath});
  // ..
}

svg/rive/flare integratinos.dart

class SvgIntegration extends Integration {
  // ..
  @override
  bool isSupport(AssetType type, {String? rootPath}) => type.mime == 'image/svg+xml';
  // ..
}

lottie integratinos.dart

class LottieIntegration extends Integration {
  // ..
  @override
  bool isSupport(AssetType type, {String? rootPath}) =>
      isLottieFile(type, rootPath ?? p.current);

  @override
  bool get isConstConstructor => true;

  bool isLottieFile(AssetType type, String rootPath) {
    if (!type.path.endsWith('.json')) {
      return false;
    }
    var input = File(p.join(rootPath, type.path)).readAsStringSync();
    var fileKeys = jsonDecode(input);
    if (fileKeys.runtimeType != Map &&
        !lottieKeys.every((key) => fileKeys.containsKey(key))) {
      return false;
    }
    var versions = fileKeys['v'];
    if (versions is! String) {
      return false;
    }
    var version = int.tryParse(versions.replaceAll('.', '')) ?? 0;
    // Lottie version 4.4.0 is the first version that supports BodyMovin.
    // https://github.com/xvrh/lottie-flutter/blob/0e7499d82ea1370b6acf023af570395bbb59b42f/lib/src/parser/lottie_composition_parser.dart#L60
    return version / 1000 >= 0.440;
  }

  // ..
}

assets_generator.dart

// ... only this
(element) => element.isSupport(assetType, rootPath: rootPath),
// ...

assets_gen_integrations_test.dart

   // No change except here
   // only add.
    test('Assets with Lottie integrations on pubspec.yaml', () async {
      const pubspec = 'test_resources/pubspec_assets_lottie_integrations.yaml';
      const fact =
          'test_resources/actual_data/assets_lottie_integrations.gen.dart';
      const generated =
          'test_resources/lib/gen/assets_lottie_integrations.gen.dart';

      await expectedAssetsGen(pubspec, generated, fact);

      final integration = LottieIntegration();
      expect(integration.className, 'LottieGenImage');
      expect(integration.classInstantiate('assets/lottie'),
          'LottieGenImage(\'assets/lottie\')');
      final testResPath = p.absolute('test_resources');   // 🌟
      expect(
          integration.isSupport(AssetType('assets/lottie/hamburger_arrow.json'),
              rootPath: testResPath), // 🌟
          isTrue);
      expect(
          integration.isSupport(
              AssetType('assets/lottie/hamburger_arrow_without_version.json'),
              rootPath: testResPath),
          isFalse);
      expect(integration.isConstConstructor, isTrue);
    });

wasabeef avatar Sep 15 '22 07:09 wasabeef

@onlymice Thanks for the great suggestions for a lottie users and me.

wasabeef avatar Sep 15 '22 07:09 wasabeef

Hi @wasabeef, Hope you doing well, I'm pinging you because I've decided to use a proper semver, my bad for not going with it initially 😅

updated code 🎯

bool isLottieFile(AssetType type) {
    try {
      if (type.extension != '.json') {
        return false;
      }
      String input = File(type.absolutePath).readAsStringSync();
      final fileKeys = jsonDecode(input) as Map<String, dynamic>;
      if (lottieKeys.every((key) => fileKeys.containsKey(key)) &&
          fileKeys['v'] != null) {
        var version = Version.parse(fileKeys['v']);
        // Lottie version 4.4.0 is the first version that supports BodyMovin.
        // https://github.com/xvrh/lottie-flutter/blob/0e7499d82ea1370b6acf023af570395bbb59b42f/lib/src/parser/lottie_composition_parser.dart#L60
        return version >= Version(4, 4, 0); 🎯
      }
    } on FormatException catch (e) {
      // Catches bad/corrupted json and reports it to user. 🎯
      stderr.writeln(e.message);
    }
    return false;
  }

onlymice avatar Sep 19 '22 18:09 onlymice

Hi @onlymice, I've merged #298 based on your PR codes created, it changed a few variable names, etc. Thank you so much. We will be releasing v5.0.0 soon.

wasabeef avatar Sep 28 '22 07:09 wasabeef