objectbox / objectbox/objectbox-dart
[Flutter] `openStore()` is not working across multiple FlutterEngines
@greenrobot-team is already working on this.
Since Jan 13, 2025.
- Dominant language
- Dart
- Stars
- 1.2k
- Forks
- 162
- Avg merge
- 15m
- Merged PRs (30d)
- 1
Description
ℹ️ Quick links:
(Edit by ObjectBox)
I have a use case where I have a Flutter app that does two things.
- a
void main(){}entrypoint that runs a regular Flutter app (i.e.runApp(MaterialApp()))
This app uses the database normally. It also schedules tasks using theworkmanagerplugin - the
workmanagerplugin that executes the tasks that the app schedules
The problem
The void main(){} entrypoint is executed from the default FlutterEngine (created by the Activity/AppDelegate)
The workmanager plugin creates a new FlutterEngine for each task it needs to run. This is because the DartExecutor from the original FlutterEngine is executing the void main(){} entrypoint. A DartExecutor can only run one entrypoint at a time.
Because there are two FlutterEngines (each with their own Isolate pool and such), the Dart code is not in sync between the Engines. That is, one engine might have a Store _myStore that is not null, but the other engine still has one that is null (because it does not have the same memory pool allocated).
This results in the following code failing on the FlutterEngine that didn't open the store:
class MyDbService {
static Store _myStore;
Future<void> init() async {
// The store is null in the other entrypoint, since that is an entire new block of memory, owned by another engine.
// This results in the error
// Bad state: failed to create store: 10001 Cannot open store: another store is still open using the same path
_store ??= await openStore();
}
}
Describe the solution you'd like
I'd like to be able to use a Store across different FlutterEngines.
If openStore() would return a new connection, regardless of the FlutterEngine, that would be sufficient I think. (I.e. using a connection pool)
I'd expect the ObjectBox API to work as-is through this connection. I.e. CRUD / observable queries should work on this new connection.
Then I could do something like this to fix my problem:
main.dart
void main() async {
await DbService().initialize(); // Open the database service in the first FlutterEngine
runApp(MyApp()); // Does call to `WorkManager.registerTask()` which invokes the task runner function
}
my_app.dart
class MyApp extends StatefulWidget {
// ...
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(title: Text('My app')),
body: Center(
child: ElevatedButton(
child: Text('Schedule task'),
onPressed: (){
WorkManager().registerTask('MyTask', {'input': 'foo'});
}
),
),
);
}
@override
void dispose() async {
await DbService().close(); //Close the database service in the first FlutterEngine
super.dispose();
}
}
task_runner.dart
@pragma("vm:entry-point")
void _taskRunner(){
WidgetsFlutterBinding.ensureInitialized();
Workmanager().executeTask((taskName, inputData) async {
await DbService().initialize(); // open a new connection in this FlutterEngine
switch(taskName){
case 'MyTask':
// do work
break;
}
await DbService().close(); // open the connection that was closed in this FlutterEngine
}
}
Additional note
This could also benefit the add-to-app use case where people use a FlutterEngine per view they embed into an existing native app.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.