2d-inc/Flare-Flutter

FlareActors inside a TabBar get rebuild

Closed this issue · 5 comments

Putting FlareActors inside a TabBar (MaterialApp -> Scaffold -> bottomNavigationBar) causes some of the FlareActors to rebuild (call initialize method).

Steps to reproduce:

  1. Have a TabBar with more than 2 tabs (important) one (or more) of which is a FlareActor.
  2. Tap on different tabs each time (1 - 2 - 3 - 2 - 3 - 1)
  3. See that one (or more) of the FlareActors get rebuild and restart their animation (initialize method called)
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  TabController tabController;

  @override
  void initState() {
    super.initState();
    tabController = TabController(length: 3, vsync: this, initialIndex: 0);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(),
        bottomNavigationBar: TabBar(
          labelColor: Colors.black,
          controller: tabController,
          tabs: [
            Tab(
              icon: FlareActor(
                'assets/cards-animate.flr',
                animation: 'show',
                color: Colors.black,
              ),
            ),
            Tab(icon: Icon(Icons.add)),
            Tab(icon: Icon(Icons.add))
          ],
        ),
      ),
    );
  }
}

Is this expected behavior? And if so, what is the correct approach to stop this from happening.
ps. If you use a custom FlareController you can see that the initialize method gets called. Diggin trough the sourcers I found out that the attach method gets called in flare_render_box.dart file:

@override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    updatePlayState();
    if (_assets.isEmpty && assetBundle != null) { // This evaluates to true, _assets is empty
      print('attach ${_assets.isEmpty}, ${assetBundle != null}'); 
      _load();
    }
  }
[√] Flutter (Channel stable, v1.7.8+hotfix.3, on Microsoft Windows [Version 10.0.17763.615], locale en-SI)
    • Flutter version 1.7.8+hotfix.3 at C:\Development\tools\flutter
    • Framework revision b712a172f9 (4 days ago), 2019-07-09 13:14:38 -0700
    • Engine revision 54ad777fd2
    • Dart version 2.4.0


[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
    • Android SDK at C:\Users\user\AppData\Local\Android\Sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-28, build-tools 28.0.3
    • ANDROID_HOME = C:\Users\user\AppData\Local\Android\Sdk
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)
    • All Android licenses accepted.

[√] Android Studio (version 3.4)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin version 37.0.1
    • Dart plugin version 183.6270
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)

[√] VS Code (version 1.35.1)
    • VS Code at C:\Users\user\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.1.0

[√] Connected device (1 available)
    • SM G975F • 192.168.64.112:5556 • android-arm64 • Android 9 (API 28)

I think Flutter rebuilds the view as you swap selected Tab bar. I'd suspect it's doing some kind of virtualizing so it only keeps the widgets that are visible attached to the widget hierarchy. There may be a way to prevent that, but that may come at a performance cost. Is there a visual problem with the rebuild?

Take a look at this for a potential way to prevent the rebuild: flutter/flutter#19116

You are correct, but I am talking about the actual TabBar not the TabBarView (see the code - it is the full source). The icons in the TabBar are getting rebuild, thus restarting the FlareActor animation which is a problem. Even if I create my own StatefulWidget and implement the AutomaticKeepAliveClientMixin it doesn't make a difference. Even if I create a custom FlareController and initialize it in the initState() method of a StatefulWidget so that is persists trough the rebuild, the_assets in the flare_render_box.dart file gets reset to empty, which causes a _load call.

ps. I am new to flutter, so there might just be some fundamental thing I am missing.

You could do it with a custom FlareController that doesn't reset animation positions even after reload. You'd just have to make sure the controller outlives the widget. You might still get flashing as it reloads however.

Another way to do this is to share the instanced Flare Artboard across different LeafRenderWidgets. This would allow you to be in control of when the Flare file is loaded and when to display it/how to animate it. Take a look at the solution I proposed to this problem: #111 (comment)

The concept would be the same, instead of passing a filename to the widget, you'd pass the actual Flare Artboard to render. That way even if the widget detached/re-attaches, it'll reuse the same controller and artboard.

The example linked in that comment shows how to do this and implements a custom LeafRenderWidget that does this here:
https://github.com/luigi-rosso/flare_flutter_shared_artboard/blob/master/lib/flare_artboard.dart

See how it gets used here:
https://github.com/luigi-rosso/flare_flutter_shared_artboard/blob/master/lib/main.dart

This seems to have done the trick! Thank you!