Short answer
Make main async and call WidgetsFlutterBinding.ensureInitialized() as its first line, before any await or plugin call. In tests, call TestWidgetsFlutterBinding.ensureInitialized(). In a background isolate, pass a RootIsolateToken and call BackgroundIsolateBinaryMessenger.ensureInitialized(token) first.
Why the binding isn’t ready, and the fix
01
A plugin call in main() before runApp
“You only need to call this method if you need the binding to be initialized before calling runApp.” Flutter, WidgetsFlutterBinding.ensureInitialized
Firebase.initializeApp, SharedPreferences.getInstance, path_provider and SystemChrome.setPreferredOrientations all need the binding. Create it first.
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); final prefs = await SharedPreferences.getInstance(); runApp(MyApp(prefs: prefs)); }02
A unit test that touches a plugin
Plain test() blocks don’t create a binding; testWidgets does. Create the test binding at the top of main, and mock the plugin (for example SharedPreferences.setMockInitialValues) since there is no platform side in tests.
void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('loads settings', () async { SharedPreferences.setMockInitialValues({}); // ... }); }03
A plugin call in a background isolate
“A BinaryMessenger for use on background (non-root) isolates.” Flutter, BackgroundIsolateBinaryMessenger
Isolate.run or compute starts a new isolate with no binding. Pass the root isolate token in and set up the background messenger before any plugin call. Not every plugin works off the main isolate.
final token = RootIsolateToken.instance!; await Isolate.run(() async { BackgroundIsolateBinaryMessenger.ensureInitialized(token); final dir = await getApplicationDocumentsDirectory(); // ... });
Checked against the vendor’s docs on : Flutter API: WidgetsFlutterBinding.ensureInitialized; Flutter API: BackgroundIsolateBinaryMessenger.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: Flutter MissingPluginException · Flutter “Null check operator used on a null value” · Flutter LateInitializationError · Flutter “type 'Null' is not a subtype of type 'String'”