A few handy Flutter tools, dead simple `UriRouter` for `Uri`-based navigator or `BuildTracker` to track widget rebuilds and what caused them to rebuild.

Overview

noob

A few handy tools for Flutter apps.

UriRouter

Dead simple Uri-based page router.

class BooksPage extends StatelessWidget {
  static final route = UriRoute(
    path: '/books/:id',
    pageBuilder: (context, params) => BooksPage(id: params.pathParams['id']!),
  );

  static String path(String id) => route.build(pathParams: <String, String>{'id': id});
...
}

final router = UriRouter(routes: [
  BooksPage.route,
]);

runApp(MaterialApp(onGenerateRoute: router.generateRoute));

Navigator.pushNamed(context, BooksPage.path('42'));

Hooks

Some handy hooks:

  • useDisposable: Manage objects that need to be disposed
  • useRebuild: Manually trigger rebuilding of a HookWidet/HookBuilder
  • useVariable: Lightweight hook to create a variable (mutable value) that doesn't trigger rebuilds when it's changed
  • useListener/useValueListener: Attach a callback to Listenable/ValueListenable without triggering rebuilds when they get notified
  • useAsyncValue: Turn Future into AsyncValue
  • useLastValidAsyncData: Remember most recent valid AsyncData

Providers

Some handy providers:

  • lastPointerEventProvider: Track the last[PointerEvents of active pointers
  • globalPositionProvider: Track the global position of a BuildContexts RenderBox

PointerIndicator

PointerIndicator(child: ...)

The PointerIndicator shows positions of PointerEvents and hence allows to record the screen including "fingers".

BuildTracker

void main() {
  // initialize `TrackingBuildOwnerWidgetsFlutterBinding` to enable tracking
  TrackingBuildOwnerWidgetsFlutterBinding.ensureInitialized();

  // initialize `BuildTracker`
  final tracker = BuildTracker(printBuildFrameIncludeRebuildDirtyWidget: false);

  // print top 10 stacks leading to rebuilds every 10 seconds
  Timer.periodic(const Duration(seconds: 10), (_) => tracker.printTopScheduleBuildForStacks());

  // run
  runApp(...);
}

A lot of people have spent hours in trying to figure out why Flutter constantly rebuilds dozens of widgets in their app. To the rescue, BuildTracker allows to track which widgets are rebuilt during every frame and -- even more important -- what caused them to rebuild.

Flutter's rendering pipeline is usually idle until some widget in the widget tree is marked as dirty, e.g. by calling setState on the State of a StatefulWidget. In that case, Flutter will schedule the widget to be build during the next frame. However, before the next frame is actually being built, there might be many more widgets being marked dirty. All these widgets we call the "build roots" of the succeeding frame.

When Flutter is eventually building the next frame, it starts by building the build roots and these might trigger builds of more widgets as the process is recursively trickling down the widget tree.

For each frame, BuildTracker prints all widgets that were rebuilt and the stack traces for each of the build roots.

Consider the following example test-case:

void main() {
  TrackingBuildOwnerAutomatedTestWidgetsFlutterBinding.ensureInitialized();

  final tracker = BuildTracker(enabled: false);

  testWidgets('Test build frames', (tester) async {
    tracker.enabled = true;

    final text = ValueNotifier('');

    debugPrint('# `tester.pumpWidget(...)`');
    debugPrint('');
    await tester.pumpWidget(
      ValueListenableBuilder<String>(
        valueListenable: text,
        builder: (_, value, child) => Directionality(
          textDirection: TextDirection.ltr,
          child: Text(value),
        ),
      ),
    );

    debugPrint("# Looping `text.value`");
    debugPrint('');
    for (var i = 0; i < 10; i++) {
      text.value = '$i';
      await tester.pump();
    }

    debugPrint('# test end');
    debugPrint('');
    tracker.enabled = false;

    tracker.printTopScheduleBuildForStacks();
  });
}

If we run the example, BuildTracker generates markdown-formatted logs that contain rebuilt and dirty widgets. The full output can be found here.

For example, after text.value = '0', we have:

Widgets that were built

  • [root]
  • ValueListenableBuilder<String> ← [root]
  • Directionality ← ValueListenableBuilder<String> ← [root]
  • Text ← Directionality ← ValueListenableBuilder<String> ← [root]

Widgets that were marked dirty (build roots)

[root]:

Stack trace #1beada3:

  Element.markNeedsBuild                   package:flutter/src/widgets/framework.dart 4157:12
  RenderObjectToWidgetAdapter.attachToRenderTree package:flutter/src/widgets/binding.dart 1102:15
  WidgetsBinding.attachRootWidget          package:flutter/src/widgets/binding.dart 934:7
  WidgetTester.pumpWidget.<fn>             package:flutter_test/src/widget_tester.dart 520:15
  _CustomZone.run                          dart:async
  TestAsyncUtils.guard                     package:flutter_test/src/test_async_utils.dart 72:41
  WidgetTester.pumpWidget                  package:flutter_test/src/widget_tester.dart 519:27
* main.<fn>                                test/build_tracker_test.dart 17:18

Hence, we can easily spot that test/build_tracker_test.dart 25:10 was the actual location that triggered the frame, which is text.value = '0'.

Furthermore, calling BuildTracker.printTopScheduleBuildForStacks prints the top stack traces resulting in rebuids:

Top 10 scheduleBuildFor stack traces (build roots)

10 times:

Stack trace #16e7ece8:

  State.setState                           package:flutter/src/widgets/framework.dart 1287:15
  _ValueListenableBuilderState._valueChanged package:flutter/src/widgets/value_listenable_builder.dart 182:5
  ChangeNotifier.notifyListeners           package:flutter/src/foundation/change_notifier.dart 243:25
  ValueNotifier.value=                     package:flutter/src/foundation/change_notifier.dart 309:5
* main.<fn>                                test/build_tracker_test.dart 30:12
...

1 times:

Stack trace #1beada3:

  Element.markNeedsBuild                   package:flutter/src/widgets/framework.dart 4157:12
  RenderObjectToWidgetAdapter.attachToRenderTree package:flutter/src/widgets/binding.dart 1102:15
  WidgetsBinding.attachRootWidget          package:flutter/src/widgets/binding.dart 934:7
  WidgetTester.pumpWidget.<fn>             package:flutter_test/src/widget_tester.dart 520:15
  _CustomZone.run                          dart:async
  TestAsyncUtils.guard                     package:flutter_test/src/test_async_utils.dart 72:41
  WidgetTester.pumpWidget                  package:flutter_test/src/widget_tester.dart 519:27
* main.<fn>                                test/build_tracker_test.dart 17:18
...

PeriodicListenable

    final timer30 = useDisposable<PeriodicListenable>(() => PeriodicListenable(const Duration(seconds: 30)), (_) => _.dispose());

    useListener(timer60, callInitially: false, callback: () => context.refresh(daaProvider));

PeriodicListenable can be used to create a Listenable that notifies its listeners periodically.

You might also like...

Simple GEL converter to showcase Flutter basics. Fundamental widgets and simple methods.

gel_converter A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if

Oct 17, 2021

A simple command-line application to generate simple folder and file structure for Flutter Applications

A simple command-line application to generate simple folder and file structure for Flutter Applications

Kanza_cli is a simple command line tool to generate folder and file structure for your Flutter apps. To use it, you should do the followings: 1. First

Dec 16, 2022

Future based HTTP client for the Dart and Flutter

Uno Future based HTTP client for the Dart and Flutter. Uno, inspired by Axios, bringing a simple and robust experience to the crossplatform apps in Fl

Dec 16, 2022

Swagger/OpenAPI code generator based on Chopper and JsonAnnotation for Flutter

Swagger/OpenAPI code generator based on Chopper and JsonAnnotation for Flutter

Code partially generated with chopper 📣 Build dart types from Swagger/OpenAPI schemas SwaggerDartCodeGenerator is a code generator that looks for *.s

Jan 5, 2023

Dart phone number parser, based on libphonenumber and PhoneNumberKit.

Dart library for parsing phone numbers. Inspired by Google's libphonenumber and PhoneNumberKit for ios.

Dec 31, 2022

Dart phone number parser, based on libphonenumber and PhoneNumberKit.

Phone Numbers Parser Dart library for parsing phone numbers. Inspired by Google's libphonenumber and PhoneNumberKit for ios. The advantage of this lib

Dec 31, 2022

The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.

The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.

The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs. Inspired by SwiftGen. Motivation Using asset path str

Jan 6, 2023

A high-performance, web standards-compliant rendering engine based on Flutter.

A high-performance, web standards-compliant rendering engine based on Flutter.

A high-performance, web standards-compliant rendering engine based on Flutter.

Dec 30, 2022

A code generation tool based on Database. :construction: developing :construction:

A code generation tool based on Database. :construction:  developing :construction:

dbgen A code generation tool based on Database. Getting Started This project is a starting point for a Flutter application. A few resources to get you

Jun 8, 2022
Comments
  • Fix noisy null-safety warning

    Fix noisy null-safety warning

    Problem

    When importing 'package:noob/noob.dart', with flutter stable v3.0.5, we see a bunch of red warnings in the console about using the null-check operator ! over WidgetsBinding.instance, that is not nullable.

    Warnings as seen in console
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/tools/providers.dart:12:20: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
        WidgetsBinding.instance!.pointerRouter.addGlobalRoute(_handler);
                       ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/tools/providers.dart:17:20: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
        WidgetsBinding.instance!.pointerRouter.removeGlobalRoute(_handler);
                       ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/tools/providers.dart:50:20: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
        WidgetsBinding.instance!.addPostFrameCallback(_callback);
                       ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/tools/providers.dart:64:22: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
          WidgetsBinding.instance!.addPostFrameCallback(_callback);
                         ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/build_tracker/build_tracker.dart:123:22: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
          WidgetsBinding.instance!.addPostFrameCallback(_frameCallback);
                         ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/build_tracker/build_tracker.dart:197:22: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
          WidgetsBinding.instance!.addPostFrameCallback(_frameCallback);
                         ^
    /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/noob-0.0.7/lib/src/build_tracker/tracking_build_owner.dart:41:27: Warning: Operand of null-aware operation '!' has type 'WidgetsBinding' which excludes null.
     - 'WidgetsBinding' is from 'package:flutter/src/widgets/binding.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/binding.dart').
        return WidgetsBinding.instance!;
                              ^
    
    flutter doctor
    Doctor summary (to see all details, run flutter doctor -v):
    [√] Flutter (Channel stable, 3.0.5, on Microsoft Windows [Versi¢n 10.0.22000.856], locale es-ES)
    [√] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    [√] Chrome - develop for the web
    [X] Visual Studio - develop for Windows
        X Visual Studio not installed; this is necessary for Windows development.
          Download at https://visualstudio.microsoft.com/downloads/.
          Please install the "Desktop development with C++" workload, including all of its default components
    [√] Android Studio (version 2021.2)
    [√] Connected device (3 available)
    [√] HTTP Host Availability
    
    ! Doctor found issues in 1 category.
    

    Solution

    Remove redundant null-check operator ! over WidgetsBinding.instance

    Unresolved topics

    Compatibility with previous versions

    I'm assuming this was working without warnings in previous versions of flutter, where WidgetsBinding was nullable, likely to not supporting null-safety at that time.

    We may need to ensure that compatibility between different flutter versions and this package, but since I don't know much about this right now, I didn't resolve this aspect.

    TrackingBuildOwnerWidgetsFlutterBinding.ensureInitialized is not working

    TrackingBuildOwnerWidgetsFlutterBinding.ensureInitialized is not working, as it's checking if WidgetsBinding.instance is null, which won't happen:

    static WidgetsBinding ensureInitialized() {
      if (WidgetsBinding.instance == null) {
        TrackingBuildOwnerWidgetsFlutterBinding();
      }
      return WidgetsBinding.instance;
    }
    

    But calling TrackingBuildOwnerWidgetsFlutterBinding(); instead before creating a BuildTracker works fine:

    void main() {
      TrackingBuildOwnerWidgetsFlutterBinding();
      BuildTracker();
      runApp(...);
    }
    

    There seems to be some changes in recent versions of flutter about how this is handled; and calling TrackingBuildOwnerWidgetsFlutterBinding(); directly sounds like a small hack, but it works for me.

    Increase version number and add entry in the changelog

    As I don't know if this must be a 0.0.8 or a 1.0.0 (if fixing ensureInitialized ends up in a breaking change), I still haven't added an entry on the changelog.

    opened by metalbass 1
  • Exception initializing BuildTracker

    Exception initializing BuildTracker

    Following the example from initializing BuildTracker, but it throws the following error:

    I/flutter (11578): ══╡ EXCEPTION CAUGHT BY FLUTTER FRAMEWORK ╞═════════════════════════════════════════════════════════
    I/flutter (11578): The following assertion was thrown:
    I/flutter (11578): `TrackingBuildOwnerWidgetsBindingMixin` is required (<WidgetsFlutterBinding>)
    I/flutter (11578): 'package:noob/src/build_tracker/build_tracker.dart':
    package:noob/…/build_tracker/build_tracker.dart:1
    I/ViewRootImpl@da141ad[FlutterActivity](11578): [DP] cancelDraw io.flutter.embedding.android.FlutterActivityAndFragmentDelegate$2@75849c1  isViewVisible: true
    I/flutter (11578): Failed assertion: line 114 pos 9: 'WidgetsBinding.instance is TrackingBuildOwnerWidgetsBindingMixin'
    I/flutter (11578):
    I/flutter (11578): When the exception was thrown, this was the stack:
    I/flutter (11578): #2      BuildTracker.enabled=
    package:noob/…/build_tracker/build_tracker.dart:114
    I/flutter (11578): #3      new BuildTracker
    package:noob/…/build_tracker/build_tracker.dart:53
    
    opened by larssn 1
Owner
Technical Fellow at HERE Technologies
null
Parse and compose Magnet URI extension (BEP53) ranges.

bep53-range Parse and compose Magnet URI extension (BEP53) ranges. Dart port of https://github.com/webtorrent/bep53-range Usage parse Parse Magnet URI

Chiziaruhoma Ogbonda 2 Feb 7, 2022
Application that simplifies the search process in several supermarkets, allowing the user to define the shopping list in a few minutes.

economiz Application that solves the price research process in different supermarkets, facilitating and simplifying this process for the user. Getting

Guilherme Henrique 1 Dec 29, 2021
A tool to easily install the Android SDK command-line and platform tools.

gibadb A tool to easily install the Android SDK command-line and platform tools. For developers: This README describes the CLI tool that ships with th

null 3 Sep 22, 2022
Dart language version of Persian-Tools

Persian tools Persian Tools dart package which you can use in all platforms Features Adding ordinal suffixes Converting Persian words to number Conver

Persian Tools 54 Dec 29, 2022
This package binds to Cronet's native API to expose them in Dart.

Experimental Cronet Dart bindings This package binds to Cronet's native API to expose them in Dart. This is an HTTP Client Package with almost the sam

Google 103 Dec 9, 2022
Extension functions on ValueListenable that allows you to work with them almost as if it was a synchronous stream.

functional_listener Extension functions on ValueListenable that allows you to work with them almost as if it was a synchronous stream. Each extension

null 54 Oct 9, 2022
This package allows programmers to annotate Dart objects in order to Serialize / Deserialize them to / from JSON

This package allows programmers to annotate Dart objects in order to Serialize / Deserialize them to / from JSON. Why? Compatible with all target plat

Alexander Mazuruk 356 Jan 6, 2023
Command-line tool to provide null-safety percentage info of a project. Track your migration progress on mixed-version programs that execute with unsound null safety.

null_safety_percentage Command-line tool to provide null-safety percentage info of a project. Track your migration progress on mixed-version programs

dartside.dev 8 Mar 27, 2022
null 9 Dec 1, 2022
Args simple - A simple argument parser and handler, integrated with JSON and dart

args_simple A simple argument parser and handler, integrated with JSON and dart:

Graciliano Monteiro Passos 1 Jan 22, 2022