Add `drift_sqflite` to replace `moor_flutter`

This commit is contained in:
Simon Binder 2022-03-14 20:25:21 +01:00
parent b0ab9ba777
commit bc5295492d
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
68 changed files with 305 additions and 48 deletions

View File

@ -0,0 +1 @@
include: package:flutter_lints/flutter.yaml

View File

@ -0,0 +1,215 @@
/// Flutter implementation for the drift database packages.
///
/// The [SqfliteQueryExecutor] class can be used as a drift database
/// implementation based on the `sqflite` package.
library drift_sqflite;
import 'dart:async';
import 'dart:io';
import 'package:drift/backends.dart';
import 'package:drift/drift.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart' as s;
/// Signature of a function that runs when a database doesn't exist on file.
/// This can be useful to, for instance, load the database from an asset if it
/// doesn't exist.
typedef DatabaseCreator = FutureOr<void> Function(File file);
class _SqfliteDelegate extends DatabaseDelegate with _SqfliteExecutor {
@override
late s.Database db;
bool _isOpen = false;
final bool inDbFolder;
final String path;
bool singleInstance;
final DatabaseCreator? creator;
_SqfliteDelegate(this.inDbFolder, this.path,
{this.singleInstance = true, this.creator});
@override
late final DbVersionDelegate versionDelegate = _SqfliteVersionDelegate(db);
@override
TransactionDelegate get transactionDelegate =>
_SqfliteTransactionDelegate(this);
@override
bool get isOpen => _isOpen;
@override
Future<void> open(QueryExecutorUser user) async {
String resolvedPath;
if (inDbFolder) {
resolvedPath = join(await s.getDatabasesPath(), path);
} else {
resolvedPath = path;
}
final file = File(resolvedPath);
if (creator != null && !await file.exists()) {
await creator!(file);
}
// default value when no migration happened
db = await s.openDatabase(
resolvedPath,
singleInstance: singleInstance,
);
_isOpen = true;
}
@override
Future<void> close() {
return db.close();
}
}
class _SqfliteVersionDelegate extends DynamicVersionDelegate {
final s.Database _db;
_SqfliteVersionDelegate(this._db);
@override
Future<int> get schemaVersion async {
final result = await _db.rawQuery('PRAGMA user_version;');
return result.single.values.first as int;
}
@override
Future<void> setSchemaVersion(int version) async {
await _db.rawUpdate('PRAGMA user_version = $version;');
}
}
class _SqfliteTransactionDelegate extends SupportedTransactionDelegate {
final _SqfliteDelegate delegate;
_SqfliteTransactionDelegate(this.delegate);
@override
void startTransaction(Future<void> Function(QueryDelegate) run) {
delegate.db.transaction((transaction) async {
final executor = _SqfliteTransactionExecutor(transaction);
await run(executor);
}).catchError((_) {
// Ignore the error! We send a fake exception to indicate a rollback.
// sqflite will rollback, but the exception will bubble up. Here we stop
// the exception.
});
}
}
class _SqfliteTransactionExecutor extends QueryDelegate with _SqfliteExecutor {
@override
final s.DatabaseExecutor db;
_SqfliteTransactionExecutor(this.db);
}
mixin _SqfliteExecutor on QueryDelegate {
s.DatabaseExecutor get db;
@override
Future<void> runBatched(BatchedStatements statements) async {
final batch = db.batch();
for (final arg in statements.arguments) {
batch.execute(statements.statements[arg.statementIndex], arg.arguments);
}
await batch.commit(noResult: true);
}
@override
Future<void> runCustom(String statement, List<Object?> args) {
return db.execute(statement, args);
}
@override
Future<int> runInsert(String statement, List<Object?> args) {
return db.rawInsert(statement, args);
}
@override
Future<QueryResult> runSelect(String statement, List<Object?> args) async {
final result = await db.rawQuery(statement, args);
return QueryResult.fromRows(result);
}
@override
Future<int> runUpdate(String statement, List<Object?> args) {
return db.rawUpdate(statement, args);
}
}
/// A query executor that uses sqflite internally.
class SqfliteQueryExecutor extends DelegatedDatabase {
/// A query executor that will store the database in the file declared by
/// [path]. If [logStatements] is true, statements sent to the database will
/// be [print]ed, which can be handy for debugging. The [singleInstance]
/// parameter sets the corresponding parameter on [s.openDatabase].
/// The [creator] will be called when the database file doesn't exist. It can
/// be used to, for instance, populate default data from an asset. Note that
/// migrations might behave differently when populating the database this way.
/// For instance, a database created by an [creator] will not receive the
/// [MigrationStrategy.onCreate] callback because it hasn't been created by
/// moor.
SqfliteQueryExecutor(
{required String path,
bool? logStatements,
bool singleInstance = true,
DatabaseCreator? creator})
: super(
_SqfliteDelegate(false, path,
singleInstance: singleInstance, creator: creator),
logStatements: logStatements);
/// A query executor that will store the database in the file declared by
/// [path], which will be resolved relative to [s.getDatabasesPath()].
/// If [logStatements] is true, statements sent to the database will
/// be [print]ed, which can be handy for debugging. The [singleInstance]
/// parameter sets the corresponding parameter on [s.openDatabase].
/// The [creator] will be called when the database file doesn't exist. It can
/// be used to, for instance, populate default data from an asset. Note that
/// migrations might behave differently when populating the database this way.
/// For instance, a database created by an [creator] will not receive the
/// [MigrationStrategy.onCreate] callback because it hasn't been created by
/// moor.
SqfliteQueryExecutor.inDatabaseFolder(
{required String path,
bool? logStatements,
bool singleInstance = true,
DatabaseCreator? creator})
: super(
_SqfliteDelegate(true, path,
singleInstance: singleInstance, creator: creator),
logStatements: logStatements);
/// The underlying sqflite [s.Database] object used by moor to send queries.
///
/// Using the sqflite database can cause unexpected behavior in moor. For
/// instance, stream queries won't update for updates sent to the [s.Database]
/// directly.
/// For this reason, projects shouldn't use this getter unless they absolutely
/// need to. The database is exposed to make migrating from sqflite to moor
/// easier.
///
/// Note that this returns null until the moor database has been opened.
/// A moor database is opened lazily when the first query runs.
s.Database? get sqfliteDb {
final sqfliteDelegate = delegate as _SqfliteDelegate;
return sqfliteDelegate.isOpen ? sqfliteDelegate.db : null;
}
@override
// We're not really required to be sequential since sqflite has an internal
// lock to bring statements into a sequential order.
// Setting isSequential here helps with moor cancellations in stream queries
// though.
bool get isSequential => true;
}

View File

@ -0,0 +1,21 @@
name: drift_sqflite
description: A Flutter-only implementation of a drift database, based on the `sqflite` package.
version: 1.0.0
repository: https://github.com/simolus3/drift
homepage: https://drift.simonbinder.eu/
issue_tracker: https://github.com/simolus3/drift/issues
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
drift: ^1.0.0
sqflite: ^2.0.0+3
path: ^1.8.0
flutter:
sdk: flutter
dev_dependencies:
flutter_lints: ^1.0.4
flutter_test:
sdk: flutter

View File

@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -26,21 +26,26 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.moor_flutter"
minSdkVersion 16
targetSdkVersion 28
applicationId "com.example.sqflite"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moor_flutter">
package="com.example.sqflite">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,16 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moor_flutter">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="moor_flutter"
package="com.example.sqflite">
<application
android:label="sqflite"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@ -24,15 +20,6 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -1,4 +1,4 @@
package com.example.moor_flutter
package com.example.sqflite
import io.flutter.embedding.android.FlutterActivity

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
@ -10,9 +10,9 @@
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.moor_flutter">
package="com.example.sqflite">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@ -1,7 +1,3 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:drift_sqflite/drift_sqflite.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' show WidgetsFlutterBinding;
import 'package:moor_flutter/moor_flutter.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart' show getDatabasesPath;
import 'package:test/test.dart';
@ -12,7 +12,7 @@ class SqfliteExecutor extends TestExecutor {
@override
DatabaseConnection createConnection() {
return DatabaseConnection.fromExecutor(
FlutterQueryExecutor.inDatabaseFolder(
SqfliteQueryExecutor.inDatabaseFolder(
path: 'app.db',
singleInstance: false,
),
@ -44,7 +44,7 @@ Future<void> main() async {
}
var didCallCreator = false;
final executor = FlutterQueryExecutor(
final executor = SqfliteQueryExecutor(
path: dbFile.path,
singleInstance: true,
creator: (file) async {

View File

@ -1,4 +1,4 @@
name: moor_flutter_integration_test
name: drift_sqflite_integration_tests
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
@ -9,7 +9,8 @@ environment:
dependencies:
flutter:
sdk: flutter
moor_flutter:
drift_sqflite:
path: ../../../drift_sqflite
tests:
path: ../tests
encrypted_moor:
@ -28,4 +29,4 @@ dependency_overrides:
flutter:
assets:
- test_asset.db
- test_asset.db