Skip to content

Flutter framework does not warn when ensureInitialized is called in a different zone than runApp #94123

@felangel

Description

@felangel

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.

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Fluttercustomer: crowdAffects or could affect many people, though not necessarily a specific customer.frameworkflutter/packages/flutter repository. See also f: labels.r: fixedIssue is closed as already fixed in a newer version

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions