Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MissingPluginException when using sqflite via flutter_isolate #169

Closed
vishna opened this issue Mar 9, 2019 · 14 comments
Closed

MissingPluginException when using sqflite via flutter_isolate #169

vishna opened this issue Mar 9, 2019 · 14 comments

Comments

@vishna
Copy link

vishna commented Mar 9, 2019

Unsure if this is expected but the other plugin I use flutter_secure_storage works just fine in my flutter isolate while sqflite is giving me this error:

MissingPluginException(No implementation found for method getDatabasesPath on channel com.tekartik.sqflite)
#0      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:300:7)
<asynchronous suspension>
#1      invokeMethod (package:sqflite/src/sqflite_impl.dart:27:34)
<asynchronous suspension>
#2      SqfliteDatabaseFactory.invokeMethod (package:sqflite/src/database_factory.dart:117:7)
#3      SqfliteDatabaseFactory.getDatabasesPath.<anonymous closure> (package:sqflite/src/database_factory.dart:214:16)
#4      wrapDatabaseException (package:sqflite/src/exception.dart:122:34)
<asynchronous suspension>
#5      SqfliteDatabaseFactory.getDatabasesPath (package:sqflite/src/database_factory.dart:213:33)
<asynchronous suspension>
#6      getDatabasesPath (package:sqflite/sqflite.dart:398:54)
@alextekartik
Copy link
Contributor

Thanks for the report. I'm actually working on similar issues so good to have something to test with. I don't know the flutter_isolate plugin yet so I guess I can try using it. Do you have a sample (or sample code) to help start me with.

@alextekartik
Copy link
Contributor

I managed to create a test program that works on Android (could not compile FlutterIsolate on iOS). It requires flutter dev version (isolates in plugin are not supported before I think). I could not manage to find a good way to exchange data using flutter_isolate so basically the only proof is that the can be created from an isolate

Test app available here
https://github.com/tekartik/sqflite_more/tree/master/various/sqflite_isolate_test_app

@rmawatson
Copy link

rmawatson commented Mar 9, 2019

@alextekartik you should be able to pass anything to the isolate that you can pass through a SendPort (after fixing the problem @vishna pointed out here)

The only real difference between the flutter_isolate and a normal isolate launched in dart is that it gets created from the a platform plugin and has an associated FlutterBackgroundView/FlutterEngine. This is the bit that's needed for method channels to work with plugins.

When the flutter_isolate is created it passes a uuid string through the platform plugin that provides the key for a SendPort stored with IsolateNameServer. This is retrieved in the new Isolate and used to pass across the userMessage (which could be another SendPort or anything else you can pass across a Dart SendPort).

It should work on iOS in stable. I have just done "flutter channel stable" on OSX and built a test project and it seemed ok. There is some new stuff with regards to background isolates in the dev channel, but I tried to make it work with both. What was the error you got?

@vishna
Copy link
Author

vishna commented Mar 9, 2019

so here's my sample:

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:isolate';
import 'package:flutter_isolate/flutter_isolate.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

void main() {
  runApp(MyApp());
  _createDBIsolate().then((_) {
    print("sending message to db isolate");
    _dbSendPort.send("message from main");
  });
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Text(""),
    );
  }
}

SendPort _dbSendPort;
FlutterIsolate _dbIsolate;

Future<void> _createDBIsolate() async {

  if (_dbIsolate != null) {
    return;
  }

  ReceivePort receivePort = ReceivePort();

  _dbIsolate = await FlutterIsolate.spawn(
    _dbMain,
    receivePort.sendPort,
  );

  _dbSendPort = await receivePort.first;
}


void _dbMain(SendPort callerSendPort) {

  ReceivePort newIsolateReceivePort = ReceivePort();

  callerSendPort.send(newIsolateReceivePort.sendPort);

  newIsolateReceivePort.listen((dynamic message) {
    print("db isolate got message: $message");
    _dbOperation();
  });
}

void _dbOperation() async {
  print("db in an isolate");
  try {
    var databasesPath = await getDatabasesPath();
    String path = join(databasesPath, 'demo.db');
    Database database = await openDatabase(path, version: 1,
        onCreate: (Database db, int version) async {
          // When creating the db, create the table
          await db.execute(
              'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
        });

    // Insert some records in a transaction
    await database.transaction((txn) async {
      int id1 = await txn.rawInsert(
          'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
      print('inserted1: $id1');
      int id2 = await txn.rawInsert(
          'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',
          ['another name', 12345678, 3.1416]);
      print('inserted2: $id2');
    });
  } catch (error, stacktrace) {
    print(error);
    print(stacktrace);
  }
}

...and it just works ¯\_(ツ)_/¯ in my sample project.

So now I've added bunch more dependencies from my main project and then it breaks with MissingPluginException so seems this is caused by some other plugin - investigating which combination is causing this, will get back at you.

@vishna
Copy link
Author

vishna commented Mar 9, 2019

Anyway, narrowed the issue down to something 🤔 in pinch_zoom_image plugin. Removed it from dependencies and the problem went away. Still new to this flutter thing so unsure what's happening but I guess this issue shouldn't have been opened here.

@rmawatson
Copy link

rmawatson commented Mar 9, 2019

This is your problem. It uses activity and that is not available in the isolate. see here, and consider using your own custom registrant which only registers the plugins you need to use in the isolate.

...

I'd guess that the list of plugins in GeneratedPluginRegistrant is ordered alphabetically, so sqflite is registered after pinch & zoom, which fails for the above reasons. You will have to make a custom registrant for the isolate to use that excludes pinch and zoom plugin.

Just make a copy of GeneratedPluginRegistrant, name it something else like IsolatePluginRegistrant, remove the registration of plugins you don't need, and in your main activity add a line

FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant.class);

just bear in mind you will have to manually maintain the list of plugins if you add/remove them as GeneratedPluginRegistrant is auto regenerated for you by flutter, but your custom IsolatePluginRegistrant will not be.

This could easily be fixed without a custom registrant if flutter exposed the list of plugins that GeneratedPluginRegistrant is registering. That way the registration for the isolate could continue on if one of them failed.

@alextekartik
Copy link
Contributor

@rmawatson regarding your questions

my issues were:

  • FlutterIsolate.spawn API does not specify what it returns (I would guess a Future<FlutterIsolate> but it is just Future)
  • I was not able to pass anything but a String as message using FlutterIsolate.spawn. All your examples uses String. I wanted to pass a map with some data (and a SendPort) but I could not (it is trying to case as a String at some point). Did I miss something?
  • In all your example I don't see how an application can exchange message with the isolate, maybe a sample would help

When trying to build:
https://github.com/tekartik/sqflite_more/tree/master/various/sqflite_isolate_test_app

On iOS I get:

Launching lib/main.dart on iPhone XR in debug mode...
Running pod install...
Running Xcode build...
Xcode build done.                                            3.3s
Failed to build iOS app
Error output from Xcode build:
↳
    ** BUILD FAILED **

Xcode's output:
↳
    === BUILD TARGET FMDB OF PROJECT Pods WITH CONFIGURATION Debug ===
    Undefined symbols for architecture x86_64:
      "_OBJC_CLASS_$_GeneratedPluginRegistrant", referenced from:
          objc-class-ref in FlutterIsolatePlugin.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

Could not build the application for the simulator.
Error launching application on iPhone XR.

@rmawatson
Copy link

rmawatson commented Mar 9, 2019

  • FlutterIsolate.spawn API does not specify what it returns (I would guess a Future but it is just Future

yes, this does return an instance of FlutterIsolate, it should probably be specified explictly.

  • I was not able to pass anything but a String as message using FlutterIsolate.spawn. All your examples uses String. I wanted to pass a map with some data (and a SendPort) but I could not (it is trying to case as a String at some point). Did I miss something?
  • In all your example I don't see how an application can exchange message with the isolate, maybe a sample would help

this was an error on my part. final String userMessage = args[1]; should be final userMessage = args[1]; - You didn't miss anything, you should be able to pass through anything you can usually pass through to a Dart isolate - that is fixed now.

I didn't actually try this on the simulator, only on an actual iphone. I will take a look at that!

@vishna
Copy link
Author

vishna commented Mar 9, 2019

Thanks for help. It works perfect now!

@vishna vishna closed this as completed Mar 9, 2019
@rmawatson
Copy link

@alextekartik - turns out it was nothing to do with the simulator. I upgraded my flutter install and got the same error as you. Something must have changed in the way the plugins are build, from static to dynamic perhaps.

I changed the isolate to find the GeneratedPluginRegistrant dynamically and that seems to have fixed it. Are you able to upgrade your packages in the example and rerun to see if that works?

Thanks

@alextekartik
Copy link
Contributor

@rmawatson It works! Great! your plugin makes a good solution (I never managed to properly used isolate before with sqflite as I was lost on the registration process) for background data manipulation. Thanks for your update

@jason-cao123
Copy link

@alextekartik Can sqflite be used on Windows? If not, is there any plan to support it? Thank in advance.

@alextekartik
Copy link
Contributor

alextekartik commented Jan 10, 2020

@jason-cao123 Please don't reuse an existing bug on a different issue (you might have got a MissingPluginException but it is not about using isolate).

Yes sqflite will be supported on Windows/Linux desktop. Currently the plugin mechanism is not stable and no official plugin supports Windows/Linux neither so I cannot be ahead of what the google team does. As soon as there is a stable API and a way to integrate Windows/Linux into the same package (likely when Flutter Desktop get into alpha quality like MacOS now), I (or someone else) will work on this.

@jason-cao123
Copy link

@alextekartik Thanks for the quick response. Look forward to the new package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants