Flutter: "Binding has not yet been initialized" when accessing Flutter Secure Storage from an isolate

1k Views Asked by At

I'm working on a large Flutter project for Android/iOS/Windows. It will have multiple processor-intensive code segments, such as server data syncing, so to prevent the UI from being slowed down we're running those on separate isolates. One of the packages we're using is Flutter Secure Storage 7.0.0 to store data between sessions. However, whenever I try to access secure storage from within the isolate, I get the following error:

Binding has not yet been initialized.
The "instance" getter on the ServicesBinding binding mixin is only available once that binding has been initialized.
Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will return the binding.
In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test's "main()" method to initialize the binding.
If ServicesBinding is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.

The error suggests calling WidgetsFlutterBinding.ensureInitialized(), however I've added that to the main function of my main isolate, and attempting to call it on the second isolate throws another error about running UI actions on non-root isolates.

One of the possible workarounds I'm aware of is to use send/receive ports to only use secure storage on the main isolate, and transfer data as needed. The problem is, one of the future plans for these isolates is to keep these isolates alive when the user closes the app, to allow us continuous background data syncing. My team has little experience on that kind of isolate usage, so I'm aware I may have a misunderstanding of how that part of our app may work in the future. But until we have a better grasp of that, we're looking to make the isolates as independent from the main isolate as possible.

Here's a minimal code sample needed to reproduce this issue:

//Run this function from main isolate
void runIsolateStorageTest() async {
  //Create storage and write a value
  const storage = FlutterSecureStorage();
  await storage.write(key: "key", value: "value");
  
  //Create and run second isolate. Main project uses a send/recieve port system, so I've replicated that here
  var recievePort = ReceivePort();
  await Isolate.spawn(isolateMain, recievePort.sendPort);

  //Await the returned value from second isolate and print to console
  var storageValue = await recievePort.first;
  print(storageValue);
}

//Function to be run on second isolate
void isolateMain(SendPort sendPort) async {
  //Several S.O. posts on similar issues have recommended this line. However, it seems to have no effect on my code
  DartPluginRegistrant.ensureInitialized();

  //Create storage and attempt to access written key
  const storage = FlutterSecureStorage();
  dynamic value = "";
  try {
    //Error is thrown when attempting to read value.
    //Other storage functions such as .write or .containsKey throw the same error when used here
    value = await storage.read(key: "key"); 
  }
  on Error catch (e) {
    value = e;
  }

  //Return the resulting value to the main isolate
  sendPort.send(value);
}

EDIT: After even more searching and digging through the flutter_secure_storage code, I've been able to more accurately identify the problem; This package uses platform channels to handle the platform specific storage implementations. According to the Flutter docs, channel methods must be invoked on the main thread. There may be a way around that using platform specific implementations, but I haven't figured that out yet.

0

There are 0 best solutions below