mirror of https://github.com/AMT-Cheif/drift.git
Improve docs a bit, refactor onOpen callback
This commit is contained in:
parent
373d7c6478
commit
8204f6b393
|
@ -7,6 +7,7 @@ analyzer:
|
|||
unused_local_variable: error
|
||||
dead_code: error
|
||||
override_on_non_overriding_method: error
|
||||
deprecated_member_use_from_same_package: ignore
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
# Will be analyzed anyway, nobody knows why ¯\_(ツ)_/¯. We're only analyzing lib/ and test/ as a workaround
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<span class="fs-2">
|
||||
Was this page helpful? Please
|
||||
{% assign url = page.url | absolute_url %}
|
||||
{% assign body = 'From documentation URL: ' | append: url %}
|
||||
{% assign body = 'Refers to the documentation: ' | append: url %}
|
||||
<a href="{{site.github_link}}/issues/new?body={{body | url_encode}}">report an issue</a>
|
||||
if you have questions or run into problems.
|
||||
</span>
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Frequently asked questions
|
||||
nav_order: 6
|
||||
permalink: /faq/
|
||||
---
|
||||
|
||||
## Using the database
|
||||
If you've created a `MyDatabase` class by following the [getting started guide]({{site.url}}/getting-started/), you
|
||||
still need to somehow obtain an instance of it. It's recommended to only have one (singleton) instance of your database,
|
||||
so you could store that instance in a global variable:
|
||||
|
||||
### Vanilla flutter
|
||||
```dart
|
||||
MyDatabase database;
|
||||
|
||||
void main() {
|
||||
database = MyDatabase();
|
||||
runApp(MyFlutterApp());
|
||||
}
|
||||
```
|
||||
It would be cleaner to use `InheritedWidgets` for that, and the `provider` package helps here:
|
||||
|
||||
### Provider
|
||||
If you're using the [provider](https://pub.dev/packages/provider) package, you can wrap your top-level widget in a
|
||||
provider that manages the database instance:
|
||||
```dart
|
||||
void main() {
|
||||
runApp(
|
||||
Provider<MyDatabase>(
|
||||
builder: (context) => MyDatabase(),
|
||||
child: MyFlutterApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
Your widgets would then have access to the database using `Provider.of<MyDatabase>(context)`.
|
||||
|
||||
### A more complex architecture
|
||||
If you're strict on keeping your business logic out of the widget layer, you probably use some dependency injection
|
||||
framework like `kiwi` or `get_it` to instantiate services and view models. Creating a singleton instance of `MyDatabase`
|
||||
in your favorite dependency injection framework for flutter hence solves this problem for you.
|
|
@ -8,4 +8,8 @@ permalink: /getting-started/
|
|||
# Getting started
|
||||
{% include content/getting_started.md %}
|
||||
|
||||
Congrats, you are now ready to fully use moor and [write queries]({{site.url}}/queries/).
|
||||
Congratulations, you now have a class which you can use to easily write queries.
|
||||
A detailed guide on how to do that is written [here]({{site.url}}/queries/).
|
||||
|
||||
PS: You might be asking how you would actually obtain an instance of `MyDatabase` for
|
||||
your widgets. If so, [here]({{site.url}}/faq/#using-the-database) is some guidance.
|
|
@ -42,4 +42,3 @@ let me know by [creating an issue]({{site.github_link}}/issues/new)!
|
|||
- VM apps
|
||||
- Web apps via `AlaSQL` or a different engine?
|
||||
- References (can be expressed via custom constraints, see issue [#14](https://github.com/simolus3/moor/issues/14))
|
||||
- When inserts / updates fail due to invalid data, explain why that happened
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:moor/src/runtime/components/component.dart';
|
||||
import 'package:moor/src/runtime/executor/before_open.dart';
|
||||
import 'package:moor/src/runtime/executor/stream_queries.dart';
|
||||
import 'package:moor/src/types/type_system.dart';
|
||||
import 'package:moor/src/runtime/statements/delete.dart';
|
||||
|
@ -199,6 +200,8 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
|
|||
/// changes in your schema, you'll need a custom migration strategy to create
|
||||
/// the new tables or change the columns.
|
||||
MigrationStrategy get migration => MigrationStrategy();
|
||||
MigrationStrategy _cachedMigration;
|
||||
MigrationStrategy get _resolvedMigration => _cachedMigration ??= migration;
|
||||
|
||||
/// A list of tables specified in this database.
|
||||
List<TableInfo> get allTables;
|
||||
|
@ -218,7 +221,7 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
|
|||
/// strategy. This method should not be called by users.
|
||||
Future<void> handleDatabaseCreation({@required SqlExecutor executor}) {
|
||||
final migrator = _createMigrator(executor);
|
||||
return migration.onCreate(migrator);
|
||||
return _resolvedMigration.onCreate(migrator);
|
||||
}
|
||||
|
||||
/// Handles database updates by delegating the work to the [migration]
|
||||
|
@ -226,6 +229,21 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
|
|||
Future<void> handleDatabaseVersionChange(
|
||||
{@required SqlExecutor executor, int from, int to}) {
|
||||
final migrator = _createMigrator(executor);
|
||||
return migration.onUpgrade(migrator, from, to);
|
||||
return _resolvedMigration.onUpgrade(migrator, from, to);
|
||||
}
|
||||
|
||||
/// Handles the before opening callback as set in the [migration]. This method
|
||||
/// is used internally by database implementations and should not be called by
|
||||
/// users.
|
||||
Future<void> beforeOpenCallback(
|
||||
QueryExecutor executor, OpeningDetails details) async {
|
||||
final migration = _resolvedMigration;
|
||||
if (migration.onFinished != null) {
|
||||
await migration.onFinished();
|
||||
}
|
||||
if (migration.beforeOpen != null) {
|
||||
final engine = BeforeOpenEngine(this, executor);
|
||||
await migration.beforeOpen(engine, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'package:moor/src/runtime/executor/stream_queries.dart';
|
||||
|
||||
/// Used internally by moor.
|
||||
class BeforeOpenEngine extends DatabaseConnectionUser with QueryEngine {
|
||||
BeforeOpenEngine(DatabaseConnectionUser other, QueryExecutor executor)
|
||||
: super.delegate(
|
||||
other,
|
||||
executor: executor,
|
||||
streamQueries: _IgnoreStreamQueries(),
|
||||
);
|
||||
}
|
||||
|
||||
class _IgnoreStreamQueries extends StreamQueryStore {
|
||||
@override
|
||||
Stream<T> registerStream<T>(QueryStreamFetcher<T> statement) {
|
||||
throw StateError('Streams cannot be created inside a transaction. See the '
|
||||
'documentation of GeneratedDatabase.transaction for details.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future handleTableUpdates(Set<TableInfo> tables) {
|
||||
return Future.value(null);
|
||||
}
|
||||
}
|
|
@ -10,8 +10,17 @@ typedef Future<void> OnUpgrade(Migrator m, int from, int to);
|
|||
|
||||
/// Signature of a function that's called after a migration has finished and the
|
||||
/// database is ready to be used. Useful to populate data.
|
||||
@deprecated
|
||||
typedef Future<void> OnMigrationFinished();
|
||||
|
||||
/// Signature of a function that's called before a database is marked opened by
|
||||
/// moor, but after migrations took place. This is a suitable callback to to
|
||||
/// populate initial data or issue `PRAGMA` statements that you want to use.
|
||||
/// All queries must be sent to [db] directly, otherwise your code will hang.
|
||||
/// See the discussion at [QueryEngine.transaction] for details.
|
||||
typedef OnBeforeOpen = Future<void> Function(
|
||||
QueryEngine db, OpeningDetails details);
|
||||
|
||||
Future<void> _defaultOnCreate(Migrator m) => m.createAllTables();
|
||||
Future<void> _defaultOnUpdate(Migrator m, int from, int to) async =>
|
||||
throw Exception("You've bumped the schema version for your moor database "
|
||||
|
@ -29,12 +38,21 @@ class MigrationStrategy {
|
|||
/// Executes after the database is ready and all migrations ran, but before
|
||||
/// any other queries will be executed, making this method suitable to
|
||||
/// populate data.
|
||||
@Deprecated('Use beforeOpen instead')
|
||||
final OnMigrationFinished onFinished;
|
||||
|
||||
/// Executes after the database is ready to be used (ie. it has been opened
|
||||
/// and all migrations ran), but before any other queries will be sent. This
|
||||
/// makes it a suitable place to populate data after the database has been
|
||||
/// created or set sqlite `PRAGMAS` that you need.
|
||||
final OnBeforeOpen beforeOpen;
|
||||
|
||||
MigrationStrategy({
|
||||
this.onCreate = _defaultOnCreate,
|
||||
this.onUpgrade = _defaultOnUpdate,
|
||||
this.onFinished,
|
||||
this.beforeOpen,
|
||||
@Deprecated('This callback is broken. Use beforeOpen instead')
|
||||
this.onFinished,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -123,6 +141,25 @@ class Migrator {
|
|||
}
|
||||
}
|
||||
|
||||
/// Provides information about whether migrations ran before opening the
|
||||
/// database.
|
||||
class OpeningDetails {
|
||||
/// The schema version before the database has been opened, or `null` if the
|
||||
/// database has just been created.
|
||||
final int versionBefore;
|
||||
|
||||
/// The schema version after running migrations.
|
||||
final int versionNow;
|
||||
|
||||
/// Whether the database has been created during this session.
|
||||
bool get wasCreated => versionBefore == null;
|
||||
|
||||
/// Whether a schema upgrade was performed while opening the database.
|
||||
bool get hadUpgrade => !wasCreated && versionBefore != versionNow;
|
||||
|
||||
const OpeningDetails(this.versionBefore, this.versionNow);
|
||||
}
|
||||
|
||||
class _SimpleSqlAsQueryExecutor extends QueryExecutor {
|
||||
final SqlExecutor executor;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="example"
|
||||
android:label="Moor example"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -45,7 +45,7 @@ class Database extends _$Database {
|
|||
path: 'db.sqlite', logStatements: true));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
int get schemaVersion => 2;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
|
@ -58,6 +58,27 @@ class Database extends _$Database {
|
|||
await m.addColumn(todos, todos.targetDate);
|
||||
}
|
||||
},
|
||||
beforeOpen: (db, details) async {
|
||||
if (details.wasCreated) {
|
||||
// create default categories and entries
|
||||
final workId =
|
||||
await db.into(categories).insert(Category(description: 'Work'));
|
||||
|
||||
await db.into(todos).insert(TodoEntry(
|
||||
content: 'A first todo entry',
|
||||
category: null,
|
||||
targetDate: DateTime.now(),
|
||||
));
|
||||
|
||||
await db.into(todos).insert(
|
||||
TodoEntry(
|
||||
content: 'Rework persistence code',
|
||||
category: workId,
|
||||
targetDate: DateTime.now().add(const Duration(days: 4)),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,15 +55,16 @@ class HomeScreenState extends State<HomeScreen> {
|
|||
sizeFactor: animation,
|
||||
axis: Axis.vertical,
|
||||
child: AnimatedBuilder(
|
||||
animation:
|
||||
CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||
child: TodoCard(item.entry),
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: animation.value,
|
||||
child: child,
|
||||
);
|
||||
}),
|
||||
animation:
|
||||
CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||
child: TodoCard(item.entry),
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: animation.value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -80,7 +80,9 @@ class FlutterQueryExecutor extends _DatabaseOwner {
|
|||
|
||||
@override
|
||||
s.Database db;
|
||||
Completer<void> _openingCompleter;
|
||||
bool _hadMigration = false;
|
||||
int _versionBefore;
|
||||
|
||||
FlutterQueryExecutor({@required this.path, bool logStatements})
|
||||
: _inDbPath = false,
|
||||
|
@ -93,10 +95,29 @@ class FlutterQueryExecutor extends _DatabaseOwner {
|
|||
|
||||
@override
|
||||
Future<bool> ensureOpen() async {
|
||||
// mechanism to ensure that _openDatabase is only called once, even if we
|
||||
// have many queries calling ensureOpen() repeatedly. _openingCompleter is
|
||||
// set if we're currently in the process of opening the database.
|
||||
if (_openingCompleter != null) {
|
||||
// already opening, wait for that to finish and don't open the database
|
||||
// again
|
||||
await _openingCompleter.future;
|
||||
return true;
|
||||
}
|
||||
if (db != null && db.isOpen) {
|
||||
// database is opened and ready
|
||||
return true;
|
||||
}
|
||||
|
||||
// alright, opening the database
|
||||
_openingCompleter = Completer();
|
||||
await _openDatabase();
|
||||
_openingCompleter.complete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future _openDatabase() async {
|
||||
String resolvedPath;
|
||||
if (_inDbPath) {
|
||||
resolvedPath = join(await s.getDatabasesPath(), path);
|
||||
|
@ -112,19 +133,17 @@ class FlutterQueryExecutor extends _DatabaseOwner {
|
|||
);
|
||||
}, onUpgrade: (db, from, to) {
|
||||
_hadMigration = true;
|
||||
_versionBefore = from;
|
||||
return databaseInfo.handleDatabaseVersionChange(
|
||||
executor: _migrationExecutor(db), from: from, to: to);
|
||||
}, onOpen: (db) async {
|
||||
db = db;
|
||||
// the openDatabase future will resolve later, so we can get an instance
|
||||
// where we can send the queries from the onFinished operation;
|
||||
final fn = databaseInfo.migration.onFinished;
|
||||
if (fn != null && _hadMigration) {
|
||||
await fn();
|
||||
}
|
||||
});
|
||||
final versionNow = await db.getVersion();
|
||||
final resolvedPrevious = _hadMigration ? _versionBefore : versionNow;
|
||||
final details = OpeningDetails(resolvedPrevious, versionNow);
|
||||
|
||||
return true;
|
||||
await databaseInfo.beforeOpenCallback(
|
||||
_BeforeOpenExecutor(db, logStatements), details);
|
||||
});
|
||||
}
|
||||
|
||||
SqlExecutor _migrationExecutor(s.Database db) {
|
||||
|
@ -189,3 +208,19 @@ class _SqfliteTransactionExecutor extends _DatabaseOwner
|
|||
return _sendFuture;
|
||||
}
|
||||
}
|
||||
|
||||
class _BeforeOpenExecutor extends _DatabaseOwner {
|
||||
@override
|
||||
final s.DatabaseExecutor db;
|
||||
|
||||
_BeforeOpenExecutor(this.db, bool logStatements) : super(logStatements);
|
||||
|
||||
@override
|
||||
TransactionExecutor beginTransaction() {
|
||||
throw UnsupportedError(
|
||||
"Transactions can't be started in the befoeOpen callback");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> ensureOpen() => Future.value(true);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ packages:
|
|||
path: "../moor"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.4.0"
|
||||
version: "1.4.0+1"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
Loading…
Reference in New Issue