felangel · GitHub

Use case

If a developer wants to call runApp within a custom Zone with zoneValues and the zoneValues rely on the underlying platform, they must call WidgetsFlutterBinding.ensureInitialized() like:

final token = Object();
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final value = await _getPlatformValue();
  runZoned(
    () => runApp(MyApp()),
    zoneValues: {token: value},
  );
}

This appears to work as expected, however, because WidgetsFlutterBinding also initializes other bindings like GestureBinding the zoneValue is not accessible within onPressed callbacks:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print(Zone.current[token]); // OK
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              print(Zone.current[token]); // NULL
            },
            child: Text('Click Me'),
          ),
        ),
      ),
    );
  }
}

Proposal

It would be great to have a way to initialize the ServicesBinding and SchedulerBinding independently of the remaining bindings in order to support accessing data from the underlying platform before runApp but without initializing the WidgetsBinding.instance within the root zone.

final token = Object();
void main() async {
  FlutterServicesBinding.ensureInitialized();
  final value = await _getPlatformValue();
  runZoned(
    () => runApp(MyApp()),
    zoneValues: {token: value},
  );
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print(Zone.current[token]); // OK
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              print(Zone.current[token]); // OK
            },
            child: Text('Click Me'),
          ),
        ),
      ),
    );
  }
}

Where FlutterServicesBinding could look something like:

FlutterServicesBinding Sample
import 'dart:ui' as ui show SingletonFlutterWindow, PlatformDispatcher, window;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
class FlutterServicesBinding extends _StandaloneBindingBase
    with SchedulerBinding, ServicesBinding {
  FlutterServicesBinding._();
  static FlutterServicesBinding? _instance;
  static FlutterServicesBinding ensureInitialized() {
    return _instance ??= FlutterServicesBinding._();
  }
}
abstract class _StandaloneBindingBase implements BindingBase {
  _StandaloneBindingBase() {
    initInstances();
    window.onReportTimings = null;
  }
  @override
  ui.SingletonFlutterWindow get window => ui.window;
  @override
  ui.PlatformDispatcher get platformDispatcher {
    return ui.PlatformDispatcher.instance;
  }
  @override
  @protected
  @mustCallSuper
  void initInstances() {}
  @override
  @protected
  @mustCallSuper
  void initServiceExtensions() {}
  @override
  @protected
  bool get locked => false;
  @override
  @protected
  Future<void> lockEvents(Future<void> Function() callback) async {}
  @override
  @protected
  @mustCallSuper
  void unlocked() {}
  @override
  Future<void> reassembleApplication() async {}
  @override
  @mustCallSuper
  @protected
  Future<void> performReassemble() async {}
  @override
  @protected
  void registerSignalServiceExtension({
    required String name,
    required AsyncCallback callback,
  }) {}
  @override
  @protected
  void registerBoolServiceExtension({
    required String name,
    required AsyncValueGetter<bool> getter,
    required AsyncValueSetter<bool> setter,
  }) {}
  @override
  @protected
  void registerNumericServiceExtension({
    required String name,
    required AsyncValueGetter<double> getter,
    required AsyncValueSetter<double> setter,
  }) {}
  @override
  @protected
  void postEvent(String eventKind, Map<String, dynamic> eventData) {}
  @override
  @protected
  void registerStringServiceExtension({
    required String name,
    required AsyncValueGetter<String> getter,
    required AsyncValueSetter<String> setter,
  }) {}
  @override
  @protected
  void registerServiceExtension({
    required String name,
    required ServiceExtensionCallback callback,
  }) {}
  @override
  String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
}

DartPad Demos

  • DartPad illustrating the limitation when using WidgetsFlutterBinding.ensureInitialized();

  • DartPad illustrating an example of how this can be improved via FlutterServicesBinding.ensureInitialized();

Additional Info

Published https://pub.dev/packages/flutter_services_binding as a workaround in the meantime.

Read the original on github.com ↗