clock icon indicating copy to clipboard operation
clock copied to clipboard

Flutter's WidgetsFlutterBinding.ensureInitialized() and withClock() - causes clock.now() to misbehave

Open mpszczolinski opened this issue 2 years ago • 2 comments

If flutter app starts with:

void main() {
  WidgetsFlutterBinding.ensureInitialized();

Then clock.now() prints not mocked value if outside of build method.

Minimum reproduceable code:

import 'package:clock/clock.dart';
import 'package:flutter/material.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  
  withClock(Clock.fixed(DateTime(1990)), () {
    runApp(const MyApp());
  });
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _printClockNow() {
    print('_printClockNow is: ${clock.now()}'); // prints real NOW
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    print('build clock.now() is: ${clock.now()}'); // prints mocked NOW

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[Dummy()],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _printClockNow,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Dummy extends StatelessWidget {
  @override
  Widget build(Object context) {
    return Text('t: ${clock.now()}');
  }
}

Expected behavior: clock.now() to have mocked value despite WidgetsFlutterBinding.ensureInitialized()

EDIT: Changed because I discovered that culprit is WidgetsFlutterBinding.ensureInitialized().

mpszczolinski avatar Aug 23 '22 09:08 mpszczolinski

@mpszczolinski - thanks for the report!

From the report, it sounds like you think the resolution here might lie in Flutter's WidgetsFlutterBinding.ensureInitialized() method. If so, you may want to re-file this issue in the https://github.com/flutter/flutter repo.

devoncarew avatar Aug 23 '22 16:08 devoncarew

I encountered this behavior in a project of mine, the clock override is registered in the current Zone, and for some reason it seems that calling WidgetsFlutterBinding.ensureInitialized() this way causes things like button callbacks to be executed in a different Zone, or at least one that doesn't see the fake clock (it can't be completely separate, static classes still have the same initialized members, I checked).

I don't have enough knowledge about how Zones work or how they're used inside the Flutter framework to comment on why this is happening. I will say that running WidgetsFlutterBinding.ensureInitialized() as the first thing within the withClock() call seems to resolve the issue for me, so putting that call as the first level within main() may work around this problem.

ElteHupkes avatar Dec 23 '22 09:12 ElteHupkes