Merge remote-tracking branch 'simolus3/develop' into unique-constraint

# Conflicts:
#	drift/lib/src/ffi/database.dart
This commit is contained in:
westito 2022-04-02 20:13:44 +02:00
commit ff1912fc07
212 changed files with 5588 additions and 413 deletions

View File

@ -200,7 +200,7 @@ targets:
In all files that use generated drift code, you'll have to replace `part 'filename.g.dart'` with `part 'filename.drift.dart'`.
If you use drift _and_ another builder in the same file, you'll need both `.g.dart` and `.drift.dart` as part-files.
A full example is available as part of [the drift repo](https://github.com/simolus3/drift/tree/develop/extras/with_built_value).
A full example is available as part of [the drift repo](https://github.com/simolus3/drift/tree/develop/examples/with_built_value).
If you run into any problems with this approach, feel free to open an issue on drift.

View File

@ -249,7 +249,7 @@ By using those test databases, drift can help you test migrations from and to an
{% block "blocks/alert" title="Complex topic ahead" %}
> Writing schema tests is an advanced topic that requires a fairly complex setup described here.
If you get stuck along the way, don't hesitate to [open a discussion about it](https://github.com/simolus3/drift/discussions).
Also, there's a working example [in the drift repository](https://github.com/simolus3/drift/tree/latest-release/extras/migrations_example).
Also, there's a working example [in the drift repository](https://github.com/simolus3/drift/tree/latest-release/examples/migrations_example).
{% endblock %}
### Setup
@ -340,11 +340,11 @@ import 'package:drift_dev/api/migrations.dart';
import 'generated_migrations/schema.dart';
void main() {
SchemaVerifier verifier;
late SchemaVerifier verifier;
setUpAll(() {
// GeneratedHelper() was generated by drift, the verifier is an api
// provided by drift_generator.
// provided by drift_dev.
verifier = SchemaVerifier(GeneratedHelper());
});

View File

@ -199,7 +199,7 @@ You can pass that `DatabaseConnection` to your database by enabling the
For more information on the `DatabaseConnection` class, see the documentation on
[isolates]({{ "../Advanced Features/isolates.md" | pageUrl }}).
A small, but working example is available under [extras/web_worker_example](https://github.com/simolus3/drift/tree/develop/extras/web_worker_example)
A small, but working example is available under [examples/web_worker_example](https://github.com/simolus3/drift/tree/develop/examples/web_worker_example)
in the drift repository.
### Flutter
@ -208,7 +208,7 @@ Flutter web doesn't compile `.dart` files in web folder and won't use `.js` file
`build_web_compilers` either. Instead, we'll use Dart's build system to manually compile the worker to a
JavaScript file before using Flutter-specific tooling.
Example is available under [extras/flutter_web_worker_example](https://github.com/simolus3/drift/tree/develop/extras/flutter_web_worker_example)
Example is available under [examples/flutter_web_worker_example](https://github.com/simolus3/drift/tree/develop/examples/flutter_web_worker_example)
in the drift repository.
First, add [build_web_compilers](https://pub.dev/packages/build_web_compilers) to the project:

View File

@ -16,6 +16,19 @@ outside of Flutter. When writing drift apps, prefer to mainly use the apis in
`package:drift/drift.dart` as they are guaranteed to work across all platforms.
Depending on your platform, you can choose a different `QueryExecutor`.
## Overview
This table list all supported drift implementations and on which platforms they run on.
| Implementation | Supported platforms | Notes |
|----------------|---------------------|-------|
| `SqfliteQueryExecutor` from `package:drift_sqflite` | Android, iOS | Uses platform channels, Flutter only, no isolate support, doesn't support `flutter test`. Formerly known as `moor_flutter` |
| `NativeDatabase` from `package:drift/native.dart` | Android, iOS, Windows, Linux, macOS | No further setup is required for Flutter users. For support outside of Flutter, or in `flutter test`, see the [desktop](#desktop) section below. Usage in a [isolate]({{ 'Advanced Features/isolates.md' | pageUrl }}) is recommended. Formerly known as `package:moor/ffi.dart`. |
| `WebDatabase` from `package:drift/web.dart` | Web | Works with or without Flutter. A bit of [additional setup]({{ 'Other engines/web.md' | pageUrl }}) is required. |
To support all platforms in a shared codebase, you only need to change how you open your database, all other usages can stay the same.
[This repository](https://github.com/rodydavis/moor_shared) gives an example on how to do that with conditional imports.
## Mobile (Android and iOS)
There are two drift implementations for mobile that you can use:
@ -74,14 +87,24 @@ setup might be required:
### Windows
On Windows, you can [download sqlite](https://www.sqlite.org/download.html) and extract
`sqlite3.dll` into a folder that's in your `PATH` environment variable to use drift.
For Flutter apps, depending on the `sqlite3_flutter_libs` package is enough. It will automatically
bundle the latest sqlite3 version with your app as a DLL, and drift will automatically use that
version.
If you don't want to use `sqlite3_flutter_libs`, or if you're not running as a Flutter app
(keep in mind that `flutter test` does not run as a full Flutter app!), you can [download sqlite](https://www.sqlite.org/download.html)
and extract`sqlite3.dll` into a folder that's in your `PATH` environment variable to use drift.
You can also ship a custom `sqlite3.dll` along with your app. See the section below for
details.
### Linux
When depending on `sqlite3_flutter_libs` in your pubspec and using Flutter, no additional setup
is necessary.
When not running as a Flutter app (this includes `flutter test`!), you need to either use a
`sqlite3` build from your distribution or include a custom `libsqlite3.so`.
On most distributions, `libsqlite3.so` is installed already. If you only need to use drift for
development, you can just install the sqlite3 libraries. On Ubuntu and other Debian-based
distros, you can install the `libsqlite3-dev` package for this. Virtually every other distribution

View File

@ -1,3 +1,11 @@
## 1.6.0-dev
- Add the very experimental `package:drift/wasm.dart` library. It uses WebAssembly
to access sqlite3 without any external JavaScript libraries, but requires you to
add a [WebAssembly module](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3#wasm-web-support)
to the `web/` folder.
- Internally use `package:js` to wrap sql.js.
## 1.5.0
- Add `DataClassName.extending` to control the superclass of generated row

View File

@ -6,7 +6,7 @@ part of 'main.dart';
// MoorGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this
// ignore_for_file: type=lint
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
final int id;
final String name;

View File

@ -9,7 +9,160 @@
/// For more information other platforms, see [other engines](https://drift.simonbinder.eu/docs/other-engines/vm/).
library drift.ffi;
import 'src/ffi/database.dart';
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:sqlite3/common.dart';
import 'package:sqlite3/sqlite3.dart';
import 'backends.dart';
import 'src/sqlite3/database.dart';
import 'src/sqlite3/database_tracker.dart';
export 'package:sqlite3/sqlite3.dart' show SqliteException;
export 'src/ffi/database.dart';
/// Signature of a function that can perform setup work on a [database] before
/// drift is fully ready.
///
/// This could be used to, for instance, set encryption keys for SQLCipher
/// implementations.
typedef DatabaseSetup = void Function(Database database);
/// A drift database implementation based on `dart:ffi`, running directly in a
/// Dart VM or an AOT compiled Dart/Flutter application.
class NativeDatabase extends DelegatedDatabase {
NativeDatabase._(DatabaseDelegate delegate, bool logStatements)
: super(delegate, isSequential: true, logStatements: logStatements);
/// Creates a database that will store its result in the [file], creating it
/// if it doesn't exist.
///
/// {@template drift_vm_database_factory}
/// If [logStatements] is true (defaults to `false`), generated sql statements
/// will be printed before executing. This can be useful for debugging.
/// The optional [setup] function can be used to perform a setup just after
/// the database is opened, before drift is fully ready. This can be used to
/// add custom user-defined sql functions or to provide encryption keys in
/// SQLCipher implementations.
/// {@endtemplate}
factory NativeDatabase(File file,
{bool logStatements = false, DatabaseSetup? setup}) {
return NativeDatabase._(_NativeDelegate(file, setup), logStatements);
}
/// Creates an in-memory database won't persist its changes on disk.
///
/// {@macro drift_vm_database_factory}
factory NativeDatabase.memory(
{bool logStatements = false, DatabaseSetup? setup}) {
return NativeDatabase._(_NativeDelegate(null, setup), logStatements);
}
/// Creates a drift executor for an opened [database] from the `sqlite3`
/// package.
///
/// When the [closeUnderlyingOnClose] argument is set (which is the default),
/// calling [QueryExecutor.close] on the returned [NativeDatabase] will also
/// [CommonDatabase.dispose] the [database] passed to this constructor.
///
/// Using [NativeDatabase.opened] may be useful when you want to use the same
/// underlying [Database] in multiple drift connections. Drift uses this
/// internally when running [integration tests for migrations](https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-migrations).
///
/// {@macro drift_vm_database_factory}
factory NativeDatabase.opened(Database database,
{bool logStatements = false,
DatabaseSetup? setup,
bool closeUnderlyingOnClose = true}) {
return NativeDatabase._(
_NativeDelegate.opened(database, setup, closeUnderlyingOnClose),
logStatements);
}
/// Disposes resources allocated by all `VmDatabase` instances of this
/// process.
///
/// This method will call `sqlite3_close_v2` for every `VmDatabase` that this
/// process has opened without closing later.
///
/// __Warning__: This functionality appears to cause crashes on iOS, and it
/// does nothing on Android. It's mainly intended for Desktop operating
/// systems, so try to avoid calling it where it's not necessary.
/// For safety measures, avoid calling [closeExistingInstances] in release
/// builds.
///
/// Ideally, all databases should be closed properly in Dart. In that case,
/// it's not necessary to call [closeExistingInstances]. However, features
/// like hot (stateless) restart can make it impossible to reliably close
/// every database. In that case, we leak native sqlite3 database connections
/// that aren't referenced by any Dart object. Drift can track those
/// connections across Dart VM restarts by storing them in an in-memory sqlite
/// database.
/// Calling this method can cleanup resources and database locks after a
/// restart.
///
/// Note that calling [closeExistingInstances] when you're still actively
/// using a [NativeDatabase] can lead to crashes, since the database would
/// then attempt to use an invalid connection.
/// This, this method should only be called when you're certain that there
/// aren't any active [NativeDatabase]s, not even on another isolate.
///
/// A suitable place to call [closeExistingInstances] is at an early stage
/// of your `main` method, before you're using drift.
///
/// ```dart
/// void main() {
/// // Guard against zombie database connections caused by hot restarts
/// assert(() {
/// VmDatabase.closeExistingInstances();
/// return true;
/// }());
///
/// runApp(MyApp());
/// }
/// ```
///
/// For more information, see [issue 835](https://github.com/simolus3/drift/issues/835).
@experimental
static void closeExistingInstances() {
tracker.closeExisting();
}
}
class _NativeDelegate extends Sqlite3Delegate<Database> {
final File? file;
_NativeDelegate(this.file, DatabaseSetup? setup) : super(setup);
_NativeDelegate.opened(
Database db, DatabaseSetup? setup, bool closeUnderlyingWhenClosed)
: file = null,
super.opened(db, setup, closeUnderlyingWhenClosed);
@override
Database openDatabase() {
final file = this.file;
Database db;
if (file != null) {
// Create the parent directory if it doesn't exist. sqlite will emit
// confusing misuse warnings otherwise
final dir = file.parent;
if (!dir.existsSync()) {
dir.createSync(recursive: true);
}
db = sqlite3.open(file.path);
tracker.markOpened(file.path, db);
} else {
db = sqlite3.openInMemory();
}
return db;
}
@override
void beforeClose(Database database) {
tracker.markClosed(database);
}
}

View File

@ -144,7 +144,7 @@ extension BuildColumn<T> on ColumnBuilder<T> {
/// BlobColumn get rawData => blob();
///
/// @override
/// Set<Column> get primaryKey = {id};
/// Set<Column> get primaryKey => {id};
/// }
/// ```
///

View File

@ -1,260 +0,0 @@
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:sqlite3/sqlite3.dart';
import '../../backends.dart';
import 'database_tracker.dart';
import 'native_functions.dart';
/// Signature of a function that can perform setup work on a [database] before
/// drift is fully ready.
///
/// This could be used to, for instance, set encryption keys for SQLCipher
/// implementations.
typedef DatabaseSetup = void Function(Database database);
/// A drift database implementation based on `dart:ffi`, running directly in a
/// Dart VM or an AOT compiled Dart/Flutter application.
class NativeDatabase extends DelegatedDatabase {
NativeDatabase._(DatabaseDelegate delegate, bool logStatements)
: super(delegate, isSequential: true, logStatements: logStatements);
/// Creates a database that will store its result in the [file], creating it
/// if it doesn't exist.
///
/// {@template drift_vm_database_factory}
/// If [logStatements] is true (defaults to `false`), generated sql statements
/// will be printed before executing. This can be useful for debugging.
/// The optional [setup] function can be used to perform a setup just after
/// the database is opened, before drift is fully ready. This can be used to
/// add custom user-defined sql functions or to provide encryption keys in
/// SQLCipher implementations.
/// {@endtemplate}
factory NativeDatabase(File file,
{bool logStatements = false, DatabaseSetup? setup}) {
return NativeDatabase._(_VmDelegate(file, setup), logStatements);
}
/// Creates an in-memory database won't persist its changes on disk.
///
/// {@macro drift_vm_database_factory}
factory NativeDatabase.memory(
{bool logStatements = false, DatabaseSetup? setup}) {
return NativeDatabase._(_VmDelegate(null, setup), logStatements);
}
/// Creates a drift executor for an opened [database] from the `sqlite3`
/// package.
///
/// When the [closeUnderlyingOnClose] argument is set (which is the default),
/// calling [QueryExecutor.close] on the returned [NativeDatabase] will also
/// [Database.dispose] the [database] passed to this constructor.
///
/// Using [NativeDatabase.opened] may be useful when you want to use the same
/// underlying [Database] in multiple drift connections. Drift uses this
/// internally when running [integration tests for migrations](https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-migrations).
///
/// {@macro drift_vm_database_factory}
factory NativeDatabase.opened(Database database,
{bool logStatements = false,
DatabaseSetup? setup,
bool closeUnderlyingOnClose = true}) {
return NativeDatabase._(
_VmDelegate._opened(database, setup, closeUnderlyingOnClose),
logStatements);
}
/// Disposes resources allocated by all `VmDatabase` instances of this
/// process.
///
/// This method will call `sqlite3_close_v2` for every `VmDatabase` that this
/// process has opened without closing later.
///
/// __Warning__: This functionality appears to cause crashes on iOS, and it
/// does nothing on Android. It's mainly intended for Desktop operating
/// systems, so try to avoid calling it where it's not necessary.
/// For safety measures, avoid calling [closeExistingInstances] in release
/// builds.
///
/// Ideally, all databases should be closed properly in Dart. In that case,
/// it's not necessary to call [closeExistingInstances]. However, features
/// like hot (stateless) restart can make it impossible to reliably close
/// every database. In that case, we leak native sqlite3 database connections
/// that aren't referenced by any Dart object. Drift can track those
/// connections across Dart VM restarts by storing them in an in-memory sqlite
/// database.
/// Calling this method can cleanup resources and database locks after a
/// restart.
///
/// Note that calling [closeExistingInstances] when you're still actively
/// using a [NativeDatabase] can lead to crashes, since the database would
/// then attempt to use an invalid connection.
/// This, this method should only be called when you're certain that there
/// aren't any active [NativeDatabase]s, not even on another isolate.
///
/// A suitable place to call [closeExistingInstances] is at an early stage
/// of your `main` method, before you're using drift.
///
/// ```dart
/// void main() {
/// // Guard against zombie database connections caused by hot restarts
/// assert(() {
/// VmDatabase.closeExistingInstances();
/// return true;
/// }());
///
/// runApp(MyApp());
/// }
/// ```
///
/// For more information, see [issue 835](https://github.com/simolus3/drift/issues/835).
@experimental
static void closeExistingInstances() {
tracker.closeExisting();
}
}
class _VmDelegate extends DatabaseDelegate {
late Database _db;
bool _hasCreatedDatabase = false;
bool _isOpen = false;
final File? file;
final DatabaseSetup? setup;
final bool closeUnderlyingWhenClosed;
_VmDelegate(this.file, this.setup) : closeUnderlyingWhenClosed = true;
_VmDelegate._opened(this._db, this.setup, this.closeUnderlyingWhenClosed)
: file = null,
_hasCreatedDatabase = true {
_initializeDatabase();
}
@override
TransactionDelegate get transactionDelegate => const NoTransactionDelegate();
@override
late DbVersionDelegate versionDelegate;
@override
Future<bool> get isOpen => Future.value(_isOpen);
@override
Future<void> open(QueryExecutorUser user) async {
if (!_hasCreatedDatabase) {
_createDatabase();
_initializeDatabase();
}
_isOpen = true;
return Future.value();
}
void _createDatabase() {
assert(!_hasCreatedDatabase);
_hasCreatedDatabase = true;
final file = this.file;
if (file != null) {
// Create the parent directory if it doesn't exist. sqlite will emit
// confusing misuse warnings otherwise
final dir = file.parent;
if (!dir.existsSync()) {
dir.createSync(recursive: true);
}
_db = sqlite3.open(file.path);
tracker.markOpened(file.path, _db);
} else {
_db = sqlite3.openInMemory();
}
}
void _initializeDatabase() {
_db.useNativeFunctions();
setup?.call(_db);
versionDelegate = _VmVersionDelegate(_db);
}
@override
Future<void> runBatched(BatchedStatements statements) async {
final prepared = [
for (final stmt in statements.statements)
_db.prepare(stmt, checkNoTail: true),
];
for (final application in statements.arguments) {
final stmt = prepared[application.statementIndex];
stmt.execute(application.arguments);
}
for (final stmt in prepared) {
stmt.dispose();
}
return Future.value();
}
Future _runWithArgs(String statement, List<Object?> args) async {
if (args.isEmpty) {
_db.execute(statement);
} else {
final stmt = _db.prepare(statement, checkNoTail: true);
stmt.execute(args);
stmt.dispose();
}
}
@override
Future<void> runCustom(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
}
@override
Future<int> runInsert(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
return _db.lastInsertRowId;
}
@override
Future<int> runUpdate(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
return _db.getUpdatedRows();
}
@override
Future<QueryResult> runSelect(String statement, List<Object?> args) async {
final stmt = _db.prepare(statement, checkNoTail: true);
final result = stmt.select(args);
stmt.dispose();
return Future.value(QueryResult.fromRows(result.toList()));
}
@override
Future<void> close() async {
if (closeUnderlyingWhenClosed) {
_db.dispose();
tracker.markClosed(_db);
}
}
}
class _VmVersionDelegate extends DynamicVersionDelegate {
final Database database;
_VmVersionDelegate(this.database);
@override
Future<int> get schemaVersion => Future.value(database.userVersion);
@override
Future<void> setSchemaVersion(int version) {
database.userVersion = version;
return Future.value();
}
}

View File

@ -48,7 +48,7 @@ class Batch {
{InsertMode? mode, UpsertClause<T, D>? onConflict}) {
_addUpdate(table, UpdateKind.insert);
final actualMode = mode ?? InsertMode.insert;
final context = InsertStatement<Table, D>(_user, table)
final context = InsertStatement<T, D>(_user, table)
.createContext(row, actualMode, onConflict: onConflict);
_addContext(context);
}

View File

@ -0,0 +1,152 @@
@internal
import 'package:meta/meta.dart';
import 'package:sqlite3/common.dart';
import '../../backends.dart';
import 'native_functions.dart';
/// Common database implementation based on the `sqlite3` database.
///
/// Depending on the actual platform (reflected by [DB]), the database is either
/// a native database accessed through `dart:ffi` or a WASM database accessed
/// through `package:js`.
abstract class Sqlite3Delegate<DB extends CommonDatabase>
extends DatabaseDelegate {
late DB _db;
bool _hasCreatedDatabase = false;
bool _isOpen = false;
final void Function(DB)? _setup;
final bool _closeUnderlyingWhenClosed;
/// A delegate that will call [openDatabase] to open the database.
Sqlite3Delegate(this._setup) : _closeUnderlyingWhenClosed = true;
/// A delegate using an underlying sqlite3 database object that has alreaddy
/// been opened.
Sqlite3Delegate.opened(this._db, this._setup, this._closeUnderlyingWhenClosed)
: _hasCreatedDatabase = true {
_initializeDatabase();
}
/// This method is overridden by the platform-specific implementation to open
/// the right sqlite3 database instance.
DB openDatabase();
/// This method may optionally be overridden by the platform-specific
/// implementation to get notified before a database would be closed.
void beforeClose(DB database) {}
@override
TransactionDelegate get transactionDelegate => const NoTransactionDelegate();
@override
late DbVersionDelegate versionDelegate;
@override
Future<bool> get isOpen => Future.value(_isOpen);
@override
Future<void> open(QueryExecutorUser db) async {
if (!_hasCreatedDatabase) {
_createDatabase();
_initializeDatabase();
}
_isOpen = true;
return Future.value();
}
void _createDatabase() {
assert(!_hasCreatedDatabase);
_hasCreatedDatabase = true;
_db = openDatabase();
}
void _initializeDatabase() {
_db.useNativeFunctions();
_setup?.call(_db);
versionDelegate = _VmVersionDelegate(_db);
}
@override
Future<void> runBatched(BatchedStatements statements) async {
final prepared = [
for (final stmt in statements.statements)
_db.prepare(stmt, checkNoTail: true),
];
for (final application in statements.arguments) {
final stmt = prepared[application.statementIndex];
stmt.execute(application.arguments);
}
for (final stmt in prepared) {
stmt.dispose();
}
return Future.value();
}
Future _runWithArgs(String statement, List<Object?> args) async {
if (args.isEmpty) {
_db.execute(statement);
} else {
final stmt = _db.prepare(statement, checkNoTail: true);
stmt.execute(args);
stmt.dispose();
}
}
@override
Future<void> runCustom(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
}
@override
Future<int> runInsert(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
return _db.lastInsertRowId;
}
@override
Future<int> runUpdate(String statement, List<Object?> args) async {
await _runWithArgs(statement, args);
return _db.getUpdatedRows();
}
@override
Future<QueryResult> runSelect(String statement, List<Object?> args) async {
final stmt = _db.prepare(statement, checkNoTail: true);
final result = stmt.select(args);
stmt.dispose();
return Future.value(QueryResult.fromRows(result.toList()));
}
@override
Future<void> close() async {
if (_closeUnderlyingWhenClosed) {
beforeClose(_db);
_db.dispose();
}
}
}
class _VmVersionDelegate extends DynamicVersionDelegate {
final CommonDatabase database;
_VmVersionDelegate(this.database);
@override
Future<int> get schemaVersion => Future.value(database.userVersion);
@override
Future<void> setSchemaVersion(int version) {
database.userVersion = version;
return Future.value();
}
}

View File

@ -1,11 +1,11 @@
import 'dart:math';
import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3/common.dart';
// ignore_for_file: avoid_returning_null, only_throw_errors
/// Extension to register moor-specific sql functions.
extension EnableNativeFunctions on Database {
extension EnableNativeFunctions on CommonDatabase {
/// Enables moor-specific sql functions on this database.
void useNativeFunctions() {
createFunction(

View File

@ -1,7 +1,17 @@
@JS()
import 'dart:async';
import 'dart:js';
import 'dart:typed_data';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
@JS('initSqlJs')
external Object /*Promise<_SqlJs>*/ _initSqlJs();
@JS('undefined')
external Null get _undefined;
// We write our own mapping code to js instead of depending on package:js
// This way, projects using drift can run on flutter as long as they don't
// import this file.
@ -21,24 +31,53 @@ Future<SqlJsModule> initSqlJs() {
'The drift documentation contains instructions on how to setup drift '
'the web, which might help you fix this.'));
} else {
(context.callMethod('initSqlJs') as JsObject)
.callMethod('then', [allowInterop(_handleModuleResolved)]);
completer
.complete(promiseToFuture<_SqlJs>(_initSqlJs()).then(SqlJsModule._));
}
return _moduleCompleter!.future;
}
// We're extracting this into its own method so that we don't have to call
// [allowInterop] on this method or a lambda.
// todo figure out why dart2js generates invalid js when wrapping this in
// allowInterop
void _handleModuleResolved(dynamic module) {
_moduleCompleter!.complete(SqlJsModule._(module as JsObject));
@JS()
@anonymous
class _SqlJs {
// ignore: non_constant_identifier_names
external Object get Database;
}
@JS()
@anonymous
class _SqlJsDatabase {
external int getRowsModified();
external void run(String sql, List<Object?>? args);
external List<_QueryExecResult> exec(String sql, List<Object?>? params);
external _SqlJsStatement prepare(String sql);
external Uint8List export();
external void close();
}
@JS()
@anonymous
class _QueryExecResult {
external List<String> get columns;
external List<List<Object?>> get values;
}
@JS()
@anonymous
class _SqlJsStatement {
external void bind(List<Object?> values);
external bool step();
external List<Object?> get();
external List<String> getColumnNames();
external void free();
}
/// `sql.js` module from the underlying library
class SqlJsModule {
final JsObject _obj;
final _SqlJs _obj;
SqlJsModule._(this._obj);
/// Constructs a new [SqlJsDatabase], optionally from the [data] blob.
@ -53,20 +92,18 @@ class SqlJsModule {
return SqlJsDatabase._(dbObj);
}
JsObject _createInternally(Uint8List? data) {
final constructor = _obj['Database'] as JsFunction;
_SqlJsDatabase _createInternally(Uint8List? data) {
if (data != null) {
return JsObject(constructor, [data]);
return callConstructor<_SqlJsDatabase>(_obj.Database, [data]);
} else {
return JsObject(constructor);
return callConstructor<_SqlJsDatabase>(_obj.Database, const []);
}
}
}
/// Dart wrapper around a sql database provided by the sql.js library.
class SqlJsDatabase {
final JsObject _obj;
final _SqlJsDatabase _obj;
SqlJsDatabase._(this._obj);
/// Returns the `user_version` pragma from sqlite.
@ -81,13 +118,12 @@ class SqlJsDatabase {
/// Calls `prepare` on the underlying js api
PreparedStatement prepare(String sql) {
final obj = _obj.callMethod('prepare', [sql]) as JsObject;
return PreparedStatement._(obj);
return PreparedStatement._(_obj.prepare(sql));
}
/// Calls `run(sql)` on the underlying js api
void run(String sql) {
_obj.callMethod('run', [sql]);
_obj.run(sql, _undefined);
}
/// Calls `run(sql, args)` on the underlying js api
@ -96,18 +132,15 @@ class SqlJsDatabase {
// Call run without providing arguments. sql.js will then use sqlite3_exec
// internally, which supports running multiple statements at once. This
// matches the behavior from a `NativeDatabase`.
_obj.callMethod('run', [sql]);
_obj.run(sql, _undefined);
} else {
final ar = JsArray.from(args);
_obj.callMethod('run', [sql, ar]);
_obj.run(sql, args);
}
}
/// Returns the amount of rows affected by the most recent INSERT, UPDATE or
/// DELETE statement.
int lastModifiedRows() {
return _obj.callMethod('getRowsModified') as int;
}
int lastModifiedRows() => _obj.getRowsModified();
/// The row id of the last inserted row. This counter is reset when calling
/// [export].
@ -117,52 +150,39 @@ class SqlJsDatabase {
}
dynamic _selectSingleRowAndColumn(String sql) {
final results = _obj.callMethod('exec', [sql]) as JsArray;
final row = results.first as JsObject;
final data = (row['values'] as JsArray).first as JsArray;
return data.first;
final results = _obj.exec(sql, _undefined);
final result = results.first;
final row = result.values.first;
return row.first;
}
/// Runs `export` on the underlying js api
Uint8List export() {
return _obj.callMethod('export') as Uint8List;
}
Uint8List export() => _obj.export();
/// Runs `close` on the underlying js api
void close() {
_obj.callMethod('close');
}
void close() => _obj.close();
}
/// Dart api wrapping an underlying prepared statement object from the sql.js
/// library.
class PreparedStatement {
final JsObject _obj;
final _SqlJsStatement _obj;
PreparedStatement._(this._obj);
/// Executes this statement with the bound [args].
void executeWith(List<dynamic> args) {
_obj.callMethod('bind', [JsArray.from(args)]);
}
void executeWith(List<dynamic> args) => _obj.bind(args);
/// Performs `step` on the underlying js api
bool step() {
return _obj.callMethod('step') as bool;
}
bool step() => _obj.step();
/// Reads the current from the underlying js api
List<dynamic> currentRow() {
return _obj.callMethod('get') as JsArray;
}
List<dynamic> currentRow() => _obj.get();
/// The columns returned by this statement. This will only be available after
/// [step] has been called once.
List<String> columnNames() {
return (_obj.callMethod('getColumnNames') as JsArray).cast<String>();
}
List<String> columnNames() => _obj.getColumnNames();
/// Calls `free` on the underlying js api
void free() {
_obj.callMethod('free');
}
void free() => _obj.free();
}

71
drift/lib/wasm.dart Normal file
View File

@ -0,0 +1,71 @@
/// A (very!) experimental web-compatible version of drift that doesn't depend
/// on external JavaScript sources.
///
/// This library is highly experimental and not production readdy at the moment.
/// It exists for development and testing purposes for interested users.
///
/// To use drift on the web, use the `package:drift/web.dart` library as
/// described in the [documentation](https://drift.simonbinder.eu/web/).
@experimental
library drift.wasm;
import 'package:meta/meta.dart';
import 'package:sqlite3/common.dart';
import 'backends.dart';
import 'src/sqlite3/database.dart';
/// Signature of a function that can perform setup work on a [database] before
/// drift is fully ready.
///
/// This could be used to, for instance, set encryption keys for SQLCipher
/// implementations.
typedef WasmDatabaseSetup = void Function(CommonDatabase database);
/// An experimental, WebAssembly based implementation of a drift sqlite3
/// database.
///
/// Using this database requires adding a WebAssembly file for sqlite3 to your
/// app. Details for that are available [here](https://github.com/simolus3/sqlite3.dart/).
class WasmDatabase extends DelegatedDatabase {
WasmDatabase._(DatabaseDelegate delegate, bool logStatements)
: super(delegate, isSequential: true, logStatements: logStatements);
/// Creates a wasm database at [path] in the virtual file system of the
/// [sqlite3] module.
factory WasmDatabase({
required CommmonSqlite3 sqlite3,
required String path,
WasmDatabaseSetup? setup,
bool logStatements = false,
}) {
return WasmDatabase._(_WasmDelegate(sqlite3, path, setup), logStatements);
}
/// Creates an in-memory database in the loaded [sqlite3] database.
factory WasmDatabase.inMemory(
CommmonSqlite3 sqlite3, {
WasmDatabaseSetup? setup,
bool logStatements = false,
}) {
return WasmDatabase._(_WasmDelegate(sqlite3, null, setup), logStatements);
}
}
class _WasmDelegate extends Sqlite3Delegate<CommonDatabase> {
final CommmonSqlite3 _sqlite3;
final String? _path;
_WasmDelegate(this._sqlite3, this._path, WasmDatabaseSetup? setup)
: super(setup);
@override
CommonDatabase openDatabase() {
final path = _path;
if (path == null) {
return _sqlite3.openInMemory();
} else {
return _sqlite3.open(path);
}
}
}

View File

@ -12,6 +12,7 @@ dependencies:
async: ^2.5.0
convert: ^3.0.0
collection: ^1.15.0
js: ^0.6.3
meta: ^1.3.0
stream_channel: ^2.1.0
sqlite3: ^1.5.1

View File

@ -121,6 +121,41 @@ void main() {
)));
});
test('insert with where clause and excluded table', () async {
// https://github.com/simolus3/drift/issues/1781
final entries = [
CategoriesCompanion.insert(description: 'first'),
CategoriesCompanion.insert(description: 'second'),
];
await db.batch((batch) {
batch.insertAll<Categories, Category>(
db.categories,
entries,
onConflict: DoUpdate.withExcluded(
(old, excluded) => CategoriesCompanion.custom(
description: old.description.dartCast(),
priority: excluded.priority.dartCast(),
),
where: (old, excluded) =>
old.id.dartCast<int>().isBiggerOrEqual(excluded.id.dartCast()),
),
);
});
verify(executor.transactions.runBatched(BatchedStatements(
[
('INSERT INTO categories ("desc") VALUES (?) ON CONFLICT(id) '
'DO UPDATE SET "desc" = categories."desc", '
'priority = excluded.priority WHERE categories.id >= excluded.id')
],
[
ArgumentsForBatchedStatement(0, ['first']),
ArgumentsForBatchedStatement(0, ['second']),
],
)));
});
test('can re-use an outer transaction', () async {
await db.transaction(() async {
await db.batch((b) {});

View File

@ -6,7 +6,7 @@ part of 'custom_tables.dart';
// MoorGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this
// ignore_for_file: type=lint
class Config extends DataClass implements Insertable<Config> {
final String configKey;
final String? configValue;

View File

@ -6,7 +6,7 @@ part of 'todos.dart';
// MoorGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this
// ignore_for_file: type=lint
class Category extends DataClass implements Insertable<Category> {
final int id;
final String description;

View File

@ -1,7 +1,7 @@
@TestOn('vm')
import 'dart:math';
import 'package:drift/src/ffi/native_functions.dart';
import 'package:drift/src/sqlite3/native_functions.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:test/test.dart';

View File

@ -3,18 +3,6 @@ import 'package:drift_dev/src/backends/build/moor_builder.dart';
import 'package:drift_dev/writer.dart';
import 'package:source_gen/source_gen.dart';
const _ignoredLints = [
'unnecessary_brace_in_string_interps',
'unnecessary_this',
// more style rules from the Flutter repo we're violating. Should we fix
// those?
/*
'always_specify_types',
'implicit_dynamic_parameter',
'sort_constructors_first',
'lines_longer_than_80_chars',*/
];
const _targetMajorVersion = 2;
const _targetMinorVersion = 6;
@ -29,8 +17,8 @@ class MoorGenerator extends Generator implements BaseGenerator {
builder.createWriter(nnbd: library.element.isNonNullableByDefault);
if (parsed.declaredDatabases.isNotEmpty) {
final ignore = '// ignore_for_file: ${_ignoredLints.join(', ')}\n';
writer.leaf().write(ignore);
const ignore = '// ignore_for_file: type=lint';
writer.leaf().writeln(ignore);
}
for (final db in parsed.declaredDatabases) {
@ -49,7 +37,7 @@ class MoorGenerator extends Generator implements BaseGenerator {
if (major < _targetMajorVersion ||
(major == _targetMajorVersion && minor < _targetMinorVersion)) {
log.warning('The language version of this file is Dart $major.$minor. '
'Moor generates code for Dart $expected or later. Please consider '
'Drift generates code for Dart $expected or later. Please consider '
'raising the minimum SDK version in your pubspec.yaml to at least '
'$expected.0.');
}

View File

@ -111,9 +111,9 @@ extension OperationOnTypes on HasType {
final converter = typeConverter;
if (converter != null) {
final needsSuffix = options.nnbd &&
(options.nullAwareTypeConverters
? converter.hasNullableDartType
: (nullable && !converter.hasNullableDartType));
!options.nullAwareTypeConverters &&
nullable &&
!converter.hasNullableDartType;
final baseType = converter.mappedType.codeString(options);
final inner = needsSuffix ? '$baseType?' : baseType;

9
examples/README.md Normal file
View File

@ -0,0 +1,9 @@
## Examples using drift
This collection of examples demonstrates how to use some advanced drift features.
- `app`: A cross-platform Flutter app built with recommended drift options.
- `flutter_web_worker_example`: Asynchronously run a drift database through a web worker with Fluter.
- `migrations_example`: Example showing to how to generate test utilities to verify schema migration.
- `web_worker_-_example`: Asynchronously run a drift database through a web worker, without Flutter.
- `with_built_value`: Configure `build_runner` so that drift-generated classes can be used by `build_runner`.

47
examples/app/.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
web/shared_worker.dart.js
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

10
examples/app/.metadata Normal file
View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 7e9793dee1b85a243edd0e06cb1658e98b077561
channel: stable
project_type: app

60
examples/app/README.md Normal file
View File

@ -0,0 +1,60 @@
# app
A cross-platform todo app using drift for local persistence.
## Supported platforms
This app runs on
- Android
- iOS
- macOS
- Linux
- Windows
- Web
When running the app, either with `flutter run` or by running the outputs of
`flutter build`, native sqlite3 dependencies should be set up automatically.
When running the app in a regular Dart VM, for instance through `flutter test`,
you need to ensure that sqlite3 is available yourself. See the [documentation](https://drift.simonbinder.eu/docs/platforms/#desktop)
for more details on this.
## Development
As this app uses drift, it depends on code-generation.
Use `flutter pub run build_runner build` to automatically build the generated
code.
### Testing
Drift databases don't depend on platform-channels or Flutter-specific features
by default. This means that they can easily be used in unit tests.
One such test is in `test/database_test.dart`
#### Testing migrations
After changing the structure of your database schema, for instance by adding
new tables or altering columns, you need to write a migration to ensure that
existing users of your app can convert their database to the latest version.
Drift contains [builtin APIs](https://drift.simonbinder.eu/docs/advanced-features/migrations/)
for common migrations.
Also, it includes builtin tools to verify that migrations are doing what they're
supposed to do.
To write such tests, run the following command after making schema changes and
incrementing your schema version. It will export the current schema of the
database as a JSON file. You should check those generated files into source control.
```
flutter pub run drift_dev schema dump lib/database/database.dart drift_schemas/
```
Then, run the following command to automatically generate test utilities which
you can use to write unit tests for schema migrations:
```
flutter pub run drift_dev schema generate drift_schemas/ test/generated_migrations/
```
An example for a schema test is in `test/migration_test.dart`.

View File

@ -0,0 +1,7 @@
include: package:flutter_lints/flutter.yaml
# Don't warn about generated code
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"

13
examples/app/android/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
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

@ -0,0 +1,68 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
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'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.app"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<application
android:label="app"
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"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package com.example.app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: 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

@ -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:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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 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 -->
<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.Black.NoTitleBar">
<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

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

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

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

56
examples/app/build.yaml Normal file
View File

@ -0,0 +1,56 @@
# This configures how `build_runner` and associated builders should behave.
# For more information, see https://pub.dev/packages/build_config
targets:
$default:
# Reducing sources makes the build slightly faster (some of these are required
# to exist in the default target).
sources:
- lib/**
- web/**
- "tool/**"
- pubspec.yaml
- lib/$lib$
- $package$
builders:
drift_dev:
# These options change how drift generates code
options:
# Drift analyzes SQL queries at compile-time. For this purpose, it needs to know which sqlite3
# features will be available. We depend on `sqlite3_flutter_libs`, which lets us use the latest
# version with fts5 enabled.
sql:
dialect: sqlite
options:
version: "3.38"
modules: [fts5]
# This allows us to share a drift database across isolates (or different tabs on the web)
generate_connect_constructor: true
# These options are generally recommended: https://drift.simonbinder.eu/docs/advanced-features/builder_options/#recommended-options
apply_converters_on_variables: true
generate_values_in_copy_with: true
new_sql_code_generation: true
scoped_dart_components: true
# Configuring this builder isn't required for most apps. In our case, we
# want to compile the web worker in `web/worker.dart` to JS and we use the
# build system for that.
build_web_compilers|entrypoint:
generate_for:
- web/worker.dart
options:
compiler: dart2js
"|copy_compiled_worker_js":
enabled: true
# build_web_compilers writes a hidden asset, but we want an asset in `web/` for
# flutter to see. So, copy that output. Again, this is not needed for most apps.
builders:
copy_compiled_worker_js:
import: 'tool/builder.dart'
builder_factories: ["CopyCompiledJs.new"]
build_to: source
build_extensions: {'web/worker.dart.js': ['web/shared_worker.dart.js']}
required_inputs: [".dart.js"]

View File

@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for moor.","version":"0.1.0-dev-preview"},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"categories","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment","primary-key"]},{"name":"name","getter_name":"name","moor_type":"ColumnType.text","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ColorConverter()","dart_type_name":"Color"}}],"is_virtual":false}},{"id":1,"references":[0],"type":"table","data":{"name":"todo_entries","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment","primary-key"]},{"name":"description","getter_name":"description","moor_type":"ColumnType.text","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"ColumnType.integer","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false}},{"id":2,"references":[],"type":"table","data":{"name":"text_entries","was_declared_in_moor":true,"columns":[{"name":"description","getter_name":"description","moor_type":"ColumnType.text","nullable":false,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":true,"create_virtual_stmt":"CREATE VIRTUAL TABLE text_entries USING fts5 (\n description,\n content=todo_entries,\n content_rowid=id\n);"}},{"id":3,"references":[1,2],"type":"trigger","data":{"on":1,"refences_in_body":[2,1],"name":"todos_insert","sql":"CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN\n INSERT INTO text_entries(rowid, description) VALUES (new.id, new.description);\nEND;"}}]}

View File

@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for moor.","version":"0.1.0-dev-preview"},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"categories","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment","primary-key"]},{"name":"name","getter_name":"name","moor_type":"ColumnType.text","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ColorConverter()","dart_type_name":"Color"}}],"is_virtual":false}},{"id":1,"references":[0],"type":"table","data":{"name":"todo_entries","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"ColumnType.integer","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment","primary-key"]},{"name":"description","getter_name":"description","moor_type":"ColumnType.text","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"ColumnType.integer","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"due_date","getter_name":"dueDate","moor_type":"ColumnType.datetime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false}},{"id":2,"references":[],"type":"table","data":{"name":"text_entries","was_declared_in_moor":true,"columns":[{"name":"description","getter_name":"description","moor_type":"ColumnType.text","nullable":false,"customConstraints":"","default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":true,"create_virtual_stmt":"CREATE VIRTUAL TABLE text_entries USING fts5 (\n description,\n content=todo_entries,\n content_rowid=id\n);"}},{"id":3,"references":[1,2],"type":"trigger","data":{"on":1,"refences_in_body":[2,1],"name":"todos_insert","sql":"CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN\n INSERT INTO text_entries(rowid, description) VALUES (new.id, new.description);\nEND;"}}]}

34
examples/app/ios/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1,481 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,5 @@
// We use a conditional export to expose the right connection factory depending
// on the platform.
export 'unsupported.dart'
if (dart.library.js) 'web.dart'
if (dart.library.ffi) 'native.dart';

View File

@ -0,0 +1,60 @@
import 'dart:io';
import 'dart:isolate';
import 'package:drift/drift.dart';
import 'package:drift/isolate.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
/// Obtains a database connection for running drift in a Dart VM.
///
/// The [NativeDatabase] from drift will synchronously use sqlite3's C APIs.
/// To move synchronous database work off the main thread, we use a
/// [DriftIsolate], which can run queries in a background isolate under the
/// hood.
DatabaseConnection connect() {
return DatabaseConnection.delayed(Future.sync(() async {
// Background isolates can't use platform channels, so let's use
// `path_provider` in the main isolate and just send the result containing
// the path over to the background isolate.
final appDir = await getApplicationDocumentsDirectory();
final dbPath = p.join(appDir.path, 'todos.db');
final receiveDriftIsolate = ReceivePort();
await Isolate.spawn(_entrypointForDriftIsolate,
_IsolateStartRequest(receiveDriftIsolate.sendPort, dbPath));
final driftIsolate = await receiveDriftIsolate.first as DriftIsolate;
return driftIsolate.connect();
}));
}
/// The entrypoint of isolates can only take a single message, but we need two
/// (a send port to reach the originating isolate and the database's path that
/// should be opened on the background isolate). So, we bundle this information
/// in a single class.
class _IsolateStartRequest {
final SendPort talkToMain;
final String databasePath;
_IsolateStartRequest(this.talkToMain, this.databasePath);
}
/// The entrypoint for a background isolate launching a drift server.
///
/// The main isolate can then connect to that isolate server to transparently
/// run queries in the background.
void _entrypointForDriftIsolate(_IsolateStartRequest request) {
// The native database synchronously uses sqlite3's C API with `dart:ffi` for
// a fast database implementation that doesn't require platform channels.
final databaseImpl = NativeDatabase(File(request.databasePath));
// We can use DriftIsolate.inCurrent because this function is the entrypoint
// of a background isolate itself.
final driftServer = DriftIsolate.inCurrent(
() => DatabaseConnection.fromExecutor(databaseImpl));
// Inform the main isolate about the server we just created.
request.talkToMain.send(driftServer);
}

View File

@ -0,0 +1,6 @@
import 'package:drift/drift.dart';
DatabaseConnection connect() {
throw UnsupportedError(
'No suitable database implementation was found on this platform.');
}

View File

@ -0,0 +1,38 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'package:drift/drift.dart';
import 'package:drift/remote.dart';
import 'package:drift/web.dart';
import 'package:drift/wasm.dart';
import 'package:http/http.dart' as http;
import 'package:sqlite3/wasm.dart';
const _useWorker = true;
/// Obtains a database connection for running drift on the web.
DatabaseConnection connect({bool isInWebWorker = false}) {
if (_useWorker && !isInWebWorker) {
final worker = SharedWorker('shared_worker.dart.js');
return remote(worker.port!.channel());
} else {
return DatabaseConnection.delayed(Future.sync(() async {
// We're using the experimental wasm support in Drift because this gives us
// a recent sqlite3 version with fts5 support.
// This is still experimental, so consider using the approach described in
// https://drift.simonbinder.eu/web/ instead.
final response = await http.get(Uri.parse('sqlite3.wasm'));
final fs = await IndexedDbFileSystem.load('/drift/my_app/');
final sqlite3 = await WasmSqlite3.load(
response.bodyBytes,
SqliteEnvironment(fileSystem: fs),
);
final databaseImpl =
WasmDatabase(sqlite3: sqlite3, path: '/drift/my_app/app.db');
return DatabaseConnection.fromExecutor(databaseImpl);
}));
}
}

View File

@ -0,0 +1,129 @@
import 'package:drift/drift.dart';
import 'package:flutter/material.dart' show Colors;
import 'package:riverpod/riverpod.dart';
import 'connection/connection.dart' as impl;
import 'tables.dart';
// Generated by drift_dev when running `build_runner build`
part 'database.g.dart';
@DriftDatabase(tables: [TodoEntries, Categories], include: {'sql.drift'})
class AppDatabase extends _$AppDatabase {
AppDatabase() : super.connect(impl.connect());
AppDatabase.forTesting(DatabaseConnection connection)
: super.connect(connection);
@override
int get schemaVersion => 2;
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: ((m, from, to) async {
if (from == 1) {
// The todoEntries.dueDate column was added in version 2.
await m.addColumn(todoEntries, todoEntries.dueDate);
}
}),
beforeOpen: (details) async {
// Make sure that foreign keys are enabled
await customStatement('PRAGMA foreign_keys = ON');
if (details.wasCreated) {
// Create a bunch of default values so the app doesn't look too empty
// on the first start.
await batch((b) {
b.insert(
categories,
CategoriesCompanion.insert(name: 'Important', color: Colors.red),
);
b.insertAll(todoEntries, [
TodoEntriesCompanion.insert(description: 'Check out drift'),
TodoEntriesCompanion.insert(
description: 'Fix session invalidation bug',
category: const Value(1)),
TodoEntriesCompanion.insert(
description: 'Add favorite movies to home page'),
]);
});
}
},
);
}
Future<List<TodoEntryWithCategory>> search(String query) {
return _search(query).map((row) {
return TodoEntryWithCategory(entry: row.todos, category: row.cat);
}).get();
}
Stream<List<CategoryWithCount>> categoriesWithCount() {
// the _categoriesWithCount method has been generated automatically based
// on the query declared in the @DriftDatabase annotation
return _categoriesWithCount().map((row) {
final hasId = row.id != null;
final category = hasId
? Category(id: row.id!, name: row.name!, color: row.color!)
: null;
return CategoryWithCount(category, row.amount);
}).watch();
}
/// Returns an auto-updating stream of all todo entries in a given category
/// id.
Stream<List<TodoEntryWithCategory>> entriesInCategory(int? categoryId) {
final query = select(todoEntries).join(
[leftOuterJoin(categories, categories.id.equalsExp(todoEntries.id))]);
if (categoryId != null) {
query.where(categories.id.equals(categoryId));
} else {
query.where(categories.id.isNull());
}
return query.map((row) {
return TodoEntryWithCategory(
entry: row.readTable(todoEntries),
category: row.readTableOrNull(categories),
);
}).watch();
}
Future<void> deleteCategory(Category category) {
return transaction(() async {
// First, move todo entries that might remain into the default category
await (todoEntries.update()
..where((todo) => todo.category.equals(category.id)))
.write(const TodoEntriesCompanion(category: Value(null)));
// Then, delete the category
await categories.deleteOne(category);
});
}
static Provider<AppDatabase> provider = Provider((ref) {
final database = AppDatabase();
ref.onDispose(database.close);
return database;
});
}
class TodoEntryWithCategory {
final TodoEntry entry;
final Category? category;
TodoEntryWithCategory({required this.entry, this.category});
}
class CategoryWithCount {
// can be null, in which case we count how many entries don't have a category
final Category? category;
final int count; // amount of entries in this category
CategoryWithCount(this.category, this.count);
}

View File

@ -0,0 +1,712 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// **************************************************************************
// MoorGenerator
// **************************************************************************
// ignore_for_file: type=lint
class Category extends DataClass implements Insertable<Category> {
final int id;
final String name;
final Color color;
Category({required this.id, required this.name, required this.color});
factory Category.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return Category(
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
name: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}name'])!,
color: $CategoriesTable.$converter0.mapToDart(const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}color']))!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['name'] = Variable<String>(name);
{
final converter = $CategoriesTable.$converter0;
map['color'] = Variable<int>(converter.mapToSql(color)!);
}
return map;
}
CategoriesCompanion toCompanion(bool nullToAbsent) {
return CategoriesCompanion(
id: Value(id),
name: Value(name),
color: Value(color),
);
}
factory Category.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return Category(
id: serializer.fromJson<int>(json['id']),
name: serializer.fromJson<String>(json['name']),
color: serializer.fromJson<Color>(json['color']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'name': serializer.toJson<String>(name),
'color': serializer.toJson<Color>(color),
};
}
Category copyWith({int? id, String? name, Color? color}) => Category(
id: id ?? this.id,
name: name ?? this.name,
color: color ?? this.color,
);
@override
String toString() {
return (StringBuffer('Category(')
..write('id: $id, ')
..write('name: $name, ')
..write('color: $color')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, name, color);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Category &&
other.id == this.id &&
other.name == this.name &&
other.color == this.color);
}
class CategoriesCompanion extends UpdateCompanion<Category> {
final Value<int> id;
final Value<String> name;
final Value<Color> color;
const CategoriesCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
this.color = const Value.absent(),
});
CategoriesCompanion.insert({
this.id = const Value.absent(),
required String name,
required Color color,
}) : name = Value(name),
color = Value(color);
static Insertable<Category> custom({
Expression<int>? id,
Expression<String>? name,
Expression<Color>? color,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (name != null) 'name': name,
if (color != null) 'color': color,
});
}
CategoriesCompanion copyWith(
{Value<int>? id, Value<String>? name, Value<Color>? color}) {
return CategoriesCompanion(
id: id ?? this.id,
name: name ?? this.name,
color: color ?? this.color,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (color.present) {
final converter = $CategoriesTable.$converter0;
map['color'] = Variable<int>(converter.mapToSql(color.value)!);
}
return map;
}
@override
String toString() {
return (StringBuffer('CategoriesCompanion(')
..write('id: $id, ')
..write('name: $name, ')
..write('color: $color')
..write(')'))
.toString();
}
}
class $CategoriesTable extends Categories
with TableInfo<$CategoriesTable, Category> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$CategoriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _colorMeta = const VerificationMeta('color');
@override
late final GeneratedColumnWithTypeConverter<Color, int?> color =
GeneratedColumn<int?>('color', aliasedName, false,
type: const IntType(), requiredDuringInsert: true)
.withConverter<Color>($CategoriesTable.$converter0);
@override
List<GeneratedColumn> get $columns => [id, name, color];
@override
String get aliasedName => _alias ?? 'categories';
@override
String get actualTableName => 'categories';
@override
VerificationContext validateIntegrity(Insertable<Category> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('name')) {
context.handle(
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
} else if (isInserting) {
context.missing(_nameMeta);
}
context.handle(_colorMeta, const VerificationResult.success());
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Category map(Map<String, dynamic> data, {String? tablePrefix}) {
return Category.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$CategoriesTable createAlias(String alias) {
return $CategoriesTable(attachedDatabase, alias);
}
static TypeConverter<Color, int> $converter0 = const ColorConverter();
}
class TodoEntry extends DataClass implements Insertable<TodoEntry> {
final int id;
final String description;
final int? category;
final DateTime? dueDate;
TodoEntry(
{required this.id,
required this.description,
this.category,
this.dueDate});
factory TodoEntry.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoEntry(
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
description: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}description'])!,
category: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}category']),
dueDate: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}due_date']),
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['description'] = Variable<String>(description);
if (!nullToAbsent || category != null) {
map['category'] = Variable<int?>(category);
}
if (!nullToAbsent || dueDate != null) {
map['due_date'] = Variable<DateTime?>(dueDate);
}
return map;
}
TodoEntriesCompanion toCompanion(bool nullToAbsent) {
return TodoEntriesCompanion(
id: Value(id),
description: Value(description),
category: category == null && nullToAbsent
? const Value.absent()
: Value(category),
dueDate: dueDate == null && nullToAbsent
? const Value.absent()
: Value(dueDate),
);
}
factory TodoEntry.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TodoEntry(
id: serializer.fromJson<int>(json['id']),
description: serializer.fromJson<String>(json['description']),
category: serializer.fromJson<int?>(json['category']),
dueDate: serializer.fromJson<DateTime?>(json['dueDate']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'description': serializer.toJson<String>(description),
'category': serializer.toJson<int?>(category),
'dueDate': serializer.toJson<DateTime?>(dueDate),
};
}
TodoEntry copyWith(
{int? id,
String? description,
Value<int?> category = const Value.absent(),
Value<DateTime?> dueDate = const Value.absent()}) =>
TodoEntry(
id: id ?? this.id,
description: description ?? this.description,
category: category.present ? category.value : this.category,
dueDate: dueDate.present ? dueDate.value : this.dueDate,
);
@override
String toString() {
return (StringBuffer('TodoEntry(')
..write('id: $id, ')
..write('description: $description, ')
..write('category: $category, ')
..write('dueDate: $dueDate')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, description, category, dueDate);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TodoEntry &&
other.id == this.id &&
other.description == this.description &&
other.category == this.category &&
other.dueDate == this.dueDate);
}
class TodoEntriesCompanion extends UpdateCompanion<TodoEntry> {
final Value<int> id;
final Value<String> description;
final Value<int?> category;
final Value<DateTime?> dueDate;
const TodoEntriesCompanion({
this.id = const Value.absent(),
this.description = const Value.absent(),
this.category = const Value.absent(),
this.dueDate = const Value.absent(),
});
TodoEntriesCompanion.insert({
this.id = const Value.absent(),
required String description,
this.category = const Value.absent(),
this.dueDate = const Value.absent(),
}) : description = Value(description);
static Insertable<TodoEntry> custom({
Expression<int>? id,
Expression<String>? description,
Expression<int?>? category,
Expression<DateTime?>? dueDate,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (description != null) 'description': description,
if (category != null) 'category': category,
if (dueDate != null) 'due_date': dueDate,
});
}
TodoEntriesCompanion copyWith(
{Value<int>? id,
Value<String>? description,
Value<int?>? category,
Value<DateTime?>? dueDate}) {
return TodoEntriesCompanion(
id: id ?? this.id,
description: description ?? this.description,
category: category ?? this.category,
dueDate: dueDate ?? this.dueDate,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (description.present) {
map['description'] = Variable<String>(description.value);
}
if (category.present) {
map['category'] = Variable<int?>(category.value);
}
if (dueDate.present) {
map['due_date'] = Variable<DateTime?>(dueDate.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TodoEntriesCompanion(')
..write('id: $id, ')
..write('description: $description, ')
..write('category: $category, ')
..write('dueDate: $dueDate')
..write(')'))
.toString();
}
}
class $TodoEntriesTable extends TodoEntries
with TableInfo<$TodoEntriesTable, TodoEntry> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$TodoEntriesTable(this.attachedDatabase, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _descriptionMeta =
const VerificationMeta('description');
@override
late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'description', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _categoryMeta = const VerificationMeta('category');
@override
late final GeneratedColumn<int?> category = GeneratedColumn<int?>(
'category', aliasedName, true,
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'REFERENCES categories (id)');
final VerificationMeta _dueDateMeta = const VerificationMeta('dueDate');
@override
late final GeneratedColumn<DateTime?> dueDate = GeneratedColumn<DateTime?>(
'due_date', aliasedName, true,
type: const IntType(), requiredDuringInsert: false);
@override
List<GeneratedColumn> get $columns => [id, description, category, dueDate];
@override
String get aliasedName => _alias ?? 'todo_entries';
@override
String get actualTableName => 'todo_entries';
@override
VerificationContext validateIntegrity(Insertable<TodoEntry> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('description')) {
context.handle(
_descriptionMeta,
description.isAcceptableOrUnknown(
data['description']!, _descriptionMeta));
} else if (isInserting) {
context.missing(_descriptionMeta);
}
if (data.containsKey('category')) {
context.handle(_categoryMeta,
category.isAcceptableOrUnknown(data['category']!, _categoryMeta));
}
if (data.containsKey('due_date')) {
context.handle(_dueDateMeta,
dueDate.isAcceptableOrUnknown(data['due_date']!, _dueDateMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
TodoEntry map(Map<String, dynamic> data, {String? tablePrefix}) {
return TodoEntry.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$TodoEntriesTable createAlias(String alias) {
return $TodoEntriesTable(attachedDatabase, alias);
}
}
class TextEntrie extends DataClass implements Insertable<TextEntrie> {
final String description;
TextEntrie({required this.description});
factory TextEntrie.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return TextEntrie(
description: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}description'])!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['description'] = Variable<String>(description);
return map;
}
TextEntriesCompanion toCompanion(bool nullToAbsent) {
return TextEntriesCompanion(
description: Value(description),
);
}
factory TextEntrie.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TextEntrie(
description: serializer.fromJson<String>(json['description']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'description': serializer.toJson<String>(description),
};
}
TextEntrie copyWith({String? description}) => TextEntrie(
description: description ?? this.description,
);
@override
String toString() {
return (StringBuffer('TextEntrie(')
..write('description: $description')
..write(')'))
.toString();
}
@override
int get hashCode => description.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TextEntrie && other.description == this.description);
}
class TextEntriesCompanion extends UpdateCompanion<TextEntrie> {
final Value<String> description;
const TextEntriesCompanion({
this.description = const Value.absent(),
});
TextEntriesCompanion.insert({
required String description,
}) : description = Value(description);
static Insertable<TextEntrie> custom({
Expression<String>? description,
}) {
return RawValuesInsertable({
if (description != null) 'description': description,
});
}
TextEntriesCompanion copyWith({Value<String>? description}) {
return TextEntriesCompanion(
description: description ?? this.description,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (description.present) {
map['description'] = Variable<String>(description.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TextEntriesCompanion(')
..write('description: $description')
..write(')'))
.toString();
}
}
class TextEntries extends Table
with
TableInfo<TextEntries, TextEntrie>,
VirtualTableInfo<TextEntries, TextEntrie> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
TextEntries(this.attachedDatabase, [this._alias]);
final VerificationMeta _descriptionMeta =
const VerificationMeta('description');
late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
'description', aliasedName, false,
type: const StringType(),
requiredDuringInsert: true,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [description];
@override
String get aliasedName => _alias ?? 'text_entries';
@override
String get actualTableName => 'text_entries';
@override
VerificationContext validateIntegrity(Insertable<TextEntrie> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('description')) {
context.handle(
_descriptionMeta,
description.isAcceptableOrUnknown(
data['description']!, _descriptionMeta));
} else if (isInserting) {
context.missing(_descriptionMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
TextEntrie map(Map<String, dynamic> data, {String? tablePrefix}) {
return TextEntrie.fromData(data,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
TextEntries createAlias(String alias) {
return TextEntries(attachedDatabase, alias);
}
@override
bool get dontWriteConstraints => true;
@override
String get moduleAndArgs =>
'fts5(description, content=todo_entries, content_rowid=id)';
}
abstract class _$AppDatabase extends GeneratedDatabase {
_$AppDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
_$AppDatabase.connect(DatabaseConnection c) : super.connect(c);
late final $CategoriesTable categories = $CategoriesTable(this);
late final $TodoEntriesTable todoEntries = $TodoEntriesTable(this);
late final TextEntries textEntries = TextEntries(this);
late final Trigger todosInsert = Trigger(
'CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN INSERT INTO text_entries ("rowid", description) VALUES (new.id, new.description);END',
'todos_insert');
Selectable<CategoriesWithCountResult> _categoriesWithCount() {
return customSelect(
'SELECT c.*, (SELECT COUNT(*) FROM todo_entries WHERE category = c.id) AS amount FROM categories AS c UNION ALL SELECT NULL, NULL, NULL, (SELECT COUNT(*) FROM todo_entries WHERE category IS NULL)',
variables: [],
readsFrom: {
todoEntries,
categories,
}).map((QueryRow row) {
return CategoriesWithCountResult(
id: row.read<int?>('id'),
name: row.read<String?>('name'),
color: $CategoriesTable.$converter0.mapToDart(row.read<int?>('color')),
amount: row.read<int>('amount'),
);
});
}
Selectable<SearchResult> _search(String query) {
return customSelect(
'SELECT"todos"."id" AS "nested_0.id", "todos"."description" AS "nested_0.description", "todos"."category" AS "nested_0.category", "todos"."due_date" AS "nested_0.due_date","cat"."id" AS "nested_1.id", "cat"."name" AS "nested_1.name", "cat"."color" AS "nested_1.color" FROM text_entries INNER JOIN todo_entries AS todos ON todos.id = text_entries."rowid" LEFT OUTER JOIN categories AS cat ON cat.id = todos.category WHERE text_entries MATCH ?1 ORDER BY rank',
variables: [
Variable<String>(query)
],
readsFrom: {
textEntries,
todoEntries,
categories,
}).map((QueryRow row) {
return SearchResult(
todos: todoEntries.mapFromRow(row, tablePrefix: 'nested_0'),
cat: categories.mapFromRowOrNull(row, tablePrefix: 'nested_1'),
);
});
}
@override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[categories, todoEntries, textEntries, todosInsert];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation(
on: TableUpdateQuery.onTableName('todo_entries',
limitUpdateKind: UpdateKind.insert),
result: [
TableUpdate('text_entries', kind: UpdateKind.insert),
],
),
],
);
}
class CategoriesWithCountResult {
final int? id;
final String? name;
final Color? color;
final int amount;
CategoriesWithCountResult({
this.id,
this.name,
this.color,
required this.amount,
});
}
class SearchResult {
final TodoEntry todos;
final Category? cat;
SearchResult({
required this.todos,
this.cat,
});
}

View File

@ -0,0 +1,43 @@
-- .drift files can contain SQL that is analyzed at compile time.
-- First, let's import the Dart tables so that we can reference them here.
import 'tables.dart';
-- Create a text index of todo entries, see https://www.sqlite.org/fts5.html#external_content_tables
CREATE VIRTUAL TABLE text_entries USING fts5 (
description,
content=todo_entries,
content_rowid=id
);
-- Triggers to keep todo entries and fts5 index in sync.
CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN
INSERT INTO text_entries(rowid, description) VALUES (new.id, new.description);
END;
-- todo: Investigate why these two triggers are causing problems
/*
CREATE TRIGGER todos_delete AFTER INSERT ON todo_entries BEGIN
INSERT INTO text_entries(text_entries, rowid, description) VALUES ('delete', new.id, new.description);
END;
CREATE TRIGGER todos_update AFTER INSERT ON todo_entries BEGIN
INSERT INTO text_entries(text_entries, rowid, description) VALUES ('delete', new.id, new.description);
INSERT INTO text_entries(rowid, description) VALUES (new.id, new.description);
END;
*/
-- Queries can also be defined here, they're then added as methods to the database.
_categoriesWithCount: SELECT c.*,
(SELECT COUNT(*) FROM todo_entries WHERE category = c.id) AS amount
FROM categories c
UNION ALL
SELECT null, null, null, (SELECT COUNT(*) FROM todo_entries WHERE category IS NULL);
-- The `.**` syntax instructs drift to generate nested result set classes.
_search: SELECT todos.**, cat.** FROM text_entries
INNER JOIN todo_entries todos ON todos.id = text_entries.rowid
LEFT OUTER JOIN categories cat ON cat.id = todos.category
WHERE text_entries MATCH :query
ORDER BY rank;

View File

@ -0,0 +1,46 @@
import 'dart:ui';
export 'dart:ui' show Color;
import 'package:drift/drift.dart';
@DataClassName('TodoEntry')
class TodoEntries extends Table with AutoIncrementingPrimaryKey {
TextColumn get description => text()();
// Todo entries can optionally be in a category.
IntColumn get category => integer().nullable().references(Categories, #id)();
// Assume that this column didn't exist in the first version of the app, it
// was added later.
// After adding it, the `schemaVersion` in the database class was incremented
// to 2 and a migration was written.
//
// With drift, database migrations can be unit-tested. See the readme of this
// example for details.
DateTimeColumn get dueDate => dateTime().nullable()();
}
@DataClassName('Category')
class Categories extends Table with AutoIncrementingPrimaryKey {
TextColumn get name => text()();
// We can use type converters to store custom classes in tables.
// Here, we're storing colors as integers.
IntColumn get color => integer().map(const ColorConverter())();
}
// Tables can mix-in common definitions if needed
mixin AutoIncrementingPrimaryKey on Table {
IntColumn get id => integer().autoIncrement()();
}
class ColorConverter extends NullAwareTypeConverter<Color, int> {
const ColorConverter();
@override
Color requireMapToDart(int fromDb) => Color(fromDb);
@override
int requireMapToSql(Color value) => value.value;
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'screens/home.dart';
import 'screens/search.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (_, __) => const HomePage(),
routes: [
GoRoute(
path: 'search',
builder: (_, __) => const SearchPage(),
),
],
),
],
);
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp.router(
title: 'Drift Todos',
theme: ThemeData(
primarySwatch: Colors.amber,
typography: Typography.material2018(),
),
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
),
);
}
}

View File

@ -0,0 +1,109 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../database/database.dart';
import 'home/card.dart';
import 'home/drawer.dart';
import 'home/state.dart';
class HomePage extends ConsumerStatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
ConsumerState<HomePage> createState() => _HomePageState();
}
class _HomePageState extends ConsumerState<HomePage> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _addTodoEntry() {
if (_controller.text.isNotEmpty) {
// We write the entry here. Notice how we don't have to call setState()
// or anything - drift will take care of updating the list automatically.
final database = ref.read(AppDatabase.provider);
final currentCategory = ref.read(activeCategory);
database.todoEntries.insertOne(TodoEntriesCompanion.insert(
description: _controller.text,
category: Value(currentCategory?.id),
));
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
final currentEntries = ref.watch(entriesInCategory);
return Scaffold(
appBar: AppBar(
title: const Text('Drift Todo list'),
actions: [
IconButton(
onPressed: () => context.go('/search'),
icon: const Icon(Icons.search),
tooltip: 'Search',
),
],
),
drawer: const CategoriesDrawer(),
body: currentEntries.when(
data: (entries) {
return ListView.builder(
itemCount: entries.length,
itemBuilder: (context, index) {
return TodoCard(entries[index].entry);
},
);
},
error: (e, s) {
debugPrintStack(label: e.toString(), stackTrace: s);
return const Text('An error has occured');
},
loading: () => const Align(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
),
bottomSheet: Material(
elevation: 12,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('What needs to be done?'),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _controller,
onSubmitted: (_) => _addTodoEntry(),
),
),
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.secondary,
onPressed: _addTodoEntry,
),
],
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,72 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../../database/database.dart';
import 'todo_edit_dialog.dart';
final DateFormat _format = DateFormat.yMMMd();
/// Card that displays an entry and an icon button to delete that entry
class TodoCard extends ConsumerWidget {
final TodoEntry entry;
TodoCard(this.entry) : super(key: ObjectKey(entry.id));
@override
Widget build(BuildContext context, WidgetRef ref) {
final dueDate = entry.dueDate;
return Card(
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(entry.description),
if (dueDate != null)
Text(
_format.format(dueDate),
style: const TextStyle(fontSize: 12),
)
else
const Text(
'No due date set',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
IconButton(
icon: const Icon(Icons.edit),
color: Colors.blue,
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => TodoEditDialog(entry: entry),
);
},
),
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
onPressed: () {
// We delete the entry here. Again, notice how we don't have to
// call setState() or inform the parent widget. Drift will take
// care of updating the underlying data automatically
ref.read(AppDatabase.provider).todoEntries.deleteOne(entry);
},
)
],
),
),
);
}
}

View File

@ -0,0 +1,207 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../database/database.dart';
import 'state.dart';
class CategoriesDrawer extends ConsumerWidget {
const CategoriesDrawer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Drawer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DrawerHeader(
child: Text(
'Todo-List Demo with drift',
style: Theme.of(context)
.textTheme
.subtitle1
?.copyWith(color: Colors.white),
),
decoration: const BoxDecoration(color: Colors.orange),
),
Flexible(
child: StreamBuilder<List<CategoryWithCount>>(
stream: ref.watch(AppDatabase.provider).categoriesWithCount(),
builder: (context, snapshot) {
final categories = snapshot.data ?? <CategoryWithCount>[];
return ListView.builder(
itemBuilder: (context, index) {
return _CategoryDrawerEntry(entry: categories[index]);
},
itemCount: categories.length,
);
},
),
),
],
),
);
}
}
class _CategoryDrawerEntry extends ConsumerWidget {
final CategoryWithCount entry;
const _CategoryDrawerEntry({Key? key, required this.entry}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final category = entry.category;
final isActive = ref.watch(activeCategory)?.id == category?.id;
String title;
if (category == null) {
title = 'No category';
} else {
title = category.name;
}
final rowContent = [
if (category != null)
Padding(
padding: const EdgeInsets.only(right: 10),
child: GestureDetector(
onTap: () async {
final newColor = await _selectColor(context, category.color);
if (newColor != null) {
final update = ref
.read(AppDatabase.provider)
.categories
.update()
..whereSamePrimaryKey(category);
await update.write(CategoriesCompanion(color: Value(newColor)));
}
},
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: category.color,
),
child: const SizedBox.square(dimension: 20),
),
),
),
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
color:
isActive ? Theme.of(context).colorScheme.secondary : Colors.black,
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text('${entry.count} entries'),
),
];
// also show a delete button if the category can be deleted
if (category != null) {
rowContent.addAll([
const Spacer(),
IconButton(
icon: const Icon(Icons.delete_outline),
color: Colors.red,
onPressed: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Delete'),
content: Text('Really delete category $title?'),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
child: const Text('Delete'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.red),
),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
},
);
// can be null when the dialog is dismissed
if (confirmed == true) {
ref.read(AppDatabase.provider).deleteCategory(category);
}
},
),
]);
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Material(
color: isActive
? Colors.orangeAccent.withOpacity(0.3)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: () {
ref.read(activeCategory.notifier).state = category;
Navigator.pop(context); // close the navigation drawer
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: rowContent,
),
),
),
),
);
}
}
Future<Color?> _selectColor(BuildContext context, Color initial) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Pick a color!'),
content: SingleChildScrollView(
child: BlockPicker(
pickerColor: initial,
onColorChanged: (color) => Navigator.pop(context, color),
),
// Use Material color picker:
//
// child: MaterialPicker(
// pickerColor: pickerColor,
// onColorChanged: changeColor,
// showLabel: true, // only on portrait mode
// ),
//
// Use Block color picker:
//
// child: BlockPicker(
// pickerColor: currentColor,
// onColorChanged: changeColor,
// ),
//
// child: MultipleChoiceBlockPicker(
// pickerColors: currentColors,
// onColorsChanged: changeColors,
// ),
),
);
},
);
}

View File

@ -0,0 +1,12 @@
import 'package:riverpod/riverpod.dart';
import '../../database/database.dart';
final activeCategory = StateProvider<Category?>((_) => null);
final entriesInCategory = StreamProvider((ref) {
final database = ref.watch(AppDatabase.provider);
final current = ref.watch(activeCategory)?.id;
return database.entriesInCategory(current);
});

Some files were not shown because too many files have changed in this diff Show More