flutter-tizen / plugins

Flutter plugins for Tizen
66 stars 47 forks source link

Sqflite malfunction upon restarting the application[Bug?] #598

Closed IhabMks closed 10 months ago

IhabMks commented 1 year ago

So i'm using drift_sqflite, which uses sqflite's SqfliteQueryExecutor, and the rest is pretty much similar as to using the drift library. Starting the application for the first time, works just fine even when doing a hot-restart, but when doing a proper restart i got the below error, and i'm obliged to exit and do another flutter-tizen run.

Error:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: databaseFactory not initialized
databaseFactory is only initialized when using sqflite. When using `sqflite_common_ffi`
You must call `databaseFactory = databaseFactoryFfi;` before using global openDatabase API
#0      databaseFactory.<anonymous closure> (package:sqflite_common/src/sqflite_database_factory.dart:29:7)
#1      databaseFactory (package:sqflite_common/src/sqflite_database_factory.dart:33:6)
#2      getDatabasesPath (package:sqflite_common/sqflite.dart:105:38)
#3      _SqfliteDelegate.open (package:drift_sqflite/drift_sqflite.dart:46:35)
#4      DelegatedDatabase.ensureOpen.<anonymous closure> (package:drift/src/runtime/executor/helpers/engines.dart:424:22)
<asynchronous suspension>
#5      Selectable.getSingleOrNull (package:drift/src/runtime/query_builder/statements/query.dart:240:18)
<asynchronous suspension>
#6      MyDatabase.getUpdatedAtValue (package:tizen_poke/database/database.dart:176:20)
<asynchronous suspension>
#7      CategoriesDataProvider._dataStillValid (package:tizen_poke/network/categories_data_provider.dart:47:33)
<asynchronous suspension>
#8      CategoriesDataProvider._fetchData (package:tizen_poke/network/categories_data_provider.dart:61:26)
<asynchronous suspension>
#9      BasicLock.synchronized (package:synchronized/src/basic_lock.dart:33:16)
<asynchronous suspension>
swift-kim commented 1 year ago

Hmm. Does your app depend on the latest sqflite_tizen (0.1.2) which includes this patch: https://github.com/flutter-tizen/plugins/commit/fba3848106a122fe8586cbbbf42da11efc90b654?

IhabMks commented 1 year ago

@swift-kim Yes it does, here's my pubspec:

  drift: ^2.8.0
  drift_sqflite: ^2.0.1
  sqflite_tizen: ^0.1.2
swift-kim commented 1 year ago

Do you have sample code for reproduction?

IhabMks commented 1 year ago

Here's a code example:

@DataClassName('EntityA')
class TableA extends Table {
  TextColumn get name => text()();
  IntColumn get something => integer().nullable()();

  @override
  Set<Column<Object>>? get primaryKey => {name};
}

@DriftDatabase(
    tables: [TableA, TableB, TableC, ...])
class MyDatabase extends _$MyDatabase {
  MyDatabase() : super(_openConnection());

  // you should bump this number whenever you change or add a table definition.
  @override
  int get schemaVersion => 1;
  }

QueryExecutor _openConnection() {
  return SqfliteQueryExecutor.inDatabaseFolder(path: _dbName);
}

class MyProvider extends ChangeNotifier {
  final MyDatabase _database = MyDatabase();
  int? something = await _database.getSomething();
}

When doing a flutter-tizen run for the first time, it runs fine, but when you do for example changes that needs a reset instead of a hot-reload, the error pops up.

swift-kim commented 1 year ago

I modified the drift example app slightly to make it dependent on drift_sqflite (by replacing impl.connect() with a SqfliteQueryExecutor), but I'm getting a different error when trying to relaunch the app after installing.

flutter: DatabaseException(trigger todos_insert already exists (code 1))
flutter: #0      wrapDatabaseException (package:sqflite/src/exception_impl.dart:11:7)
flutter: <asynchronous suspension>
flutter: #1      BasicLock.synchronized (package:synchronized/src/basic_lock.dart:33:16)
flutter: <asynchronous suspension>
flutter: #2      SqfliteDatabaseMixin.txnSynchronized (package:sqflite_common/src/database_mixin.dart:490:14)
flutter: <asynchronous suspension>
flutter: #3      Migrator.create (package:drift/src/runtime/query_builder/migration.dart:86:7)
flutter: <asynchronous suspension>
flutter: #4      Migrator.createAll (package:drift/src/runtime/query_builder/migration.dart:76:7)
flutter: <asynchronous suspension>
flutter: #5      GeneratedDatabase.beforeOpen.<anonymous closure> (package:drift/src/runtime/api/db_base.dart:126:9)
flutter: <asynchronous suspension>
flutter: #6      DelegatedDatabase._runMigrations (package:drift/src/runtime/executor/helpers/engines.dart:458:5)
flutter: <asynchronous suspension>
flutter: #7      DelegatedDatabase.ensureOpen.<anonymous closure> (package:drift/src/runtime/executor/helpers/engines.dart:426:7)
flutter: <asynchronous suspension>
flutter: #8      NonNullableCancellationExtension.resultOrNullIfCancelled (package:drift/src/runtime/cancellation_zone.dart:62:24)
flutter: <asynchronous suspension>
flutter: #9      QueryStream.fetchAndEmitData (package:drift/src/runtime/executor/stream_queries.dart:313:20)
flutter: <asynchronous suspension>

I have no idea what is causing the error since I'm not familiar with the drift library, but anyway, according to the stack trace, the sqflite plugin was loaded correctly (no "databaseFactory not initialized" error). If you still get the "databaseFactory not initialized" error even after updating sqflite_tizen to 0.1.2, consider removing the drift_sqflite dependency itself from your app. As far as I understand (from its README), you don't need the dependency to use the drift package.

All you need to do is to add a path_provider_tizen dependency to your app

dependencies:
  path_provider_tizen: ^2.1.0

and add the following code to main() (before calling runApp()).

open.overrideFor(OperatingSystem.linux, () {
  return DynamicLibrary.open('/usr/share/dotnet.tizen/lib/libsqlite3.so');
});

Make sure not to use NativeDatabase.createBackgroundConnection() (which implicitly spawns an isolate) to open a database connection. Instead, use sqlite3.open().

// DON'T:
return NativeDatabase.createBackgroundConnection(await databaseFile);

// DO:
return DatabaseConnection(NativeDatabase.opened(sqlite3.open((await databaseFile).path)));

Reference: https://github.com/flutter-tizen/plugins/issues/277#issuecomment-992126520

IhabMks commented 1 year ago

Hi, @swift-kim I tried your solution, it works, but this time, upon restarting the app, i'm getting an error related to a specefic method : "getApplicationDocumentsDirectory", which i tried to avoid before.

Packages used: path_provider: ^2.0.7 path_provider_tizen: ^2.1.0

Here's the error:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider)
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:313:7)
<asynchronous suspension>
#1      getApplicationDocumentsDirectory (package:path_provider/path_provider.dart:120:24)
<asynchronous suspension>
#2      _openConnection.<anonymous closure> (package:tizen_poke/database/database.dart:197:22)
<asynchronous suspension>
#3      LazyDatabase._awaitOpened.<anonymous closure> (package:drift/src/utils/lazy_database.dart:47:32)
<asynchronous suspension>
swift-kim commented 1 year ago

Could you try explicitly calling getApplicationDocumentsDirectory() from your app code to see if the path_provider plugin works?

Note that path_provider_tizen doesn't work in secondary (non-root) isolates by default so make sure all the code is run on the root isolate.

IhabMks commented 1 year ago

@swift-kim I did like you asked:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dbFolder = await getApplicationDocumentsDirectory();
  open.overrideFor(OperatingSystem.linux, () {
    return DynamicLibrary.open('/usr/share/dotnet.tizen/lib/libsqlite3.so');
  });
  runApp(ChangeNotifierProvider(
      create: (context) => MyProvider(dbFolder),
      child: MaterialApp(...)

First application start works fine, but after a restart, still got the same error:

[E] [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider)
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:313:7)
<asynchronous suspension>
#1      getApplicationDocumentsDirectory (package:path_provider/path_provider.dart:120:24)
<asynchronous suspension>
#2      main (package:tizen_poke/main.dart:35:20)
<asynchronous suspension>
swift-kim commented 1 year ago

Well, I can't reproduce the error, and I can't guess what has gone wrong. You should have the file tizen/flutter/generated_main.dart in your project, and it should have a plugin registrant entrypoint function which calls PathProviderPlugin.register(). You will get the error if this function is not called on app startup. The code is already compiled into your app package so it shouldn't matter if it's a first-time launch or a restart.

A simple workaround is to call the function directly in your app code (maybe in main()),

import 'package:path_provider_tizen/path_provider_tizen.dart';

PathProviderPlugin.register();

but, if possible, I'd like to know what the actual cause of the bug is so that we can fix it.

IhabMks commented 1 year ago

@swift-kim I just tried calling it from the main method, and it works fine:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  PathProviderPlugin.register();
  final dbFolder = await getApplicationDocumentsDirectory();
  open.overrideFor(OperatingSystem.linux, () {
    return DynamicLibrary.open('/usr/share/dotnet.tizen/lib/libsqlite3.so');
  });
...

But as you said, that part of the code is already compiled into the app package, so it wouldn't need to be called again after a restart as it already did the first time.

I'll keep trying different approaches, and it see what the actual cause is.

swift-kim commented 10 months ago

Please let us know if the problem still persists.