f_orm_m8_sqlite 0.7.0+4

  • Readme
  • Changelog
  • Example
  • Installing
  • 69

Sqlite Scaffolding Generator @ Dart Framework ORM M8 #

Gitter GitHub release pub package Build Status Codecov license

f_orm_m8_sqlite - \fɔːrm meɪt Ess-kjuː-ɛl-aɪ\ A f_orm_m8 implementation for Sqlite, with mapping capability out of the box. Part of Dart Framework ORM M8

Introduction #

Dart package to generate SQLite ready-to-go fixture. Uses Dart Build System builders. Based on f_orm_m8 annotations convention this package generates proxies and database adapter for SQLite.

Dependencies #

It depends on dart package f_orm_m8. Read README.md for implemented annotation convention.

Supported orm-m8 features:

PrefixFramework gemTypeGenerator versionNotes
@DataTableclass annotationv0.1.0
@DataColumnfield annotationv0.1.0
TableMetadata.softDeletableTableMetadatav0.3.0
TableMetadata.trackCreateTableMetadatav0.3.0
TableMetadata.trackUpdateTableMetadatav0.1.0
ColumnMetadata.ignoreColumnMetadatav0.1.0
ColumnMetadata.primaryKeyColumnMetadatav0.1.0
ColumnMetadata.uniqueColumnMetadatav0.1.0
ColumnMetadata.notNullColumnMetadatav0.1.0
ColumnMetadata.autoIncrementColumnMetadatav0.3.0
ColumnMetadata.indexedColumnMetadatav0.7.0
CompositeConstraint.uniqueCompositeConstraintv0.4.0
CompositeConstraint.primaryKeyCompositeConstraintv0.4.0
CompositeConstraint.foreignKeyCompositeConstraint-Planned for v0.9.0
CompositeConstraint.indexedCompositeConstraintv0.7.0
implementsDbOpenEntityentity helperv0.6.3
implementsDbEntityentity helperv0.1.0
implementsDbAccountEntityentity helperv0.1.0
implementsDbAccountRelatedEntityentity helperv0.1.0

Usage #

  1. Create a flutter project

  2. Add f_orm_m8, sqflite, build_runner, f_orm_m8_sqlite dependencies to pubspec.yaml

    • Before

        dependencies:
            flutter:
                sdk: flutter
      
            cupertino_icons: ^0.1.2
      
        dev_dependencies:
            flutter_test:
                sdk: flutter
      
    • After

        dependencies:
            f_orm_m8: ^0.8.0
            sqflite: ^1.1.0
            flutter:
                sdk: flutter
      
            cupertino_icons: ^0.1.2
      
        dev_dependencies:
            build_runner: ^1.0.0
            f_orm_m8_sqlite: ^0.7.0
            flutter_test:
                sdk: flutter
      
      
  3. Add build.yaml file with the following content

     targets:
         $default:
             builders:
                 f_orm_m8_sqlite|orm_m8:
                     generate_for:
                         - lib/models/*.dart
                         - lib/main.dart
    
  4. Refresh packages

    flutter packages get
    
  5. In the lib folder create a new one named models

  6. In the models folder add model classes for your business objects.

  7. Using f_orm_m8 annotations convention, mark:

    • classes with @DataTable
    • fields with @DataColumn
  8. Run the build_runner clean. This step is absolutely mandatory.

  flutter packages pub run build_runner clean
  1. Run the build_runner build
  flutter packages pub run build_runner build --delete-conflicting-outputs

The build_runner will generate:

  • in models folder, a *.g.m8.dart file for each model file
  • in lib folder, a main.adapter.g.m8.dart file
  1. Use the generated proxies and adapter in order to easily develop CRUD behavior. See the example project for a trivial usage.

Example - Gymspector application #

A full, flutter working example is maintained on https://github.com/matei-tm/f-orm-m8-sqlite/tree/master/example.
The example presents different approaches to solve CRUD functionality for models that adhere to f_orm_m8 annotation framework.

usecase000

UserAccount - A DbAccountEntity implementation #

The example has a common UserAccount model that implements DbAccountEntity

usecase001

The model #

The model file models/user_account.dart has the following content:

import 'package:f_orm_m8/f_orm_m8.dart';

@DataTable("user_account")
class UserAccount implements DbAccountEntity {
  @DataColumn("id",
      metadataLevel: ColumnMetadata.primaryKey |
          ColumnMetadata.unique |
          ColumnMetadata.autoIncrement)
  int id;

  @DataColumn("description")
  String description;

  @DataColumn("my_future_column7",
      metadataLevel: ColumnMetadata.ignore | ColumnMetadata.unique)
  int futureData;

  @DataColumn("abbreviation",
      metadataLevel: ColumnMetadata.notNull | ColumnMetadata.unique)
  String abbreviation;

  @DataColumn("email", metadataLevel: ColumnMetadata.notNull)
  String email;

  @DataColumn("user_name",
      metadataLevel: ColumnMetadata.notNull | ColumnMetadata.unique)
  String userName;

  @DataColumn("is_current")
  bool isCurrent;
}

The generated code #

From the model, the builder creates models/user_account.g.m8.dart file with following content

// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-17 13:54:19.192616

// **************************************************************************
// Generator: OrmM8GeneratorForAnnotation
// **************************************************************************

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:sqlite_m8_demo/models/user_account.dart';

class UserAccountProxy extends UserAccount {
  UserAccountProxy({abbreviation, email, userName}) {
    this.abbreviation = abbreviation;
    this.email = email;
    this.userName = userName;
  }

  Map<String, dynamic> toMap() {
    var map = Map<String, dynamic>();
    map['id'] = id;
    map['description'] = description;
    map['abbreviation'] = abbreviation;
    map['email'] = email;
    map['user_name'] = userName;
    map['is_current'] = isCurrent ? 1 : 0;

    return map;
  }

  UserAccountProxy.fromMap(Map<String, dynamic> map) {
    this.id = map['id'];
    this.description = map['description'];
    this.abbreviation = map['abbreviation'];
    this.email = map['email'];
    this.userName = map['user_name'];
    this.isCurrent = map['is_current'] == 1 ? true : false;
  }
}

mixin UserAccountDatabaseProvider {
  Future<Database> db;
  final theUserAccountColumns = [
    "id",
    "description",
    "abbreviation",
    "email",
    "user_name",
    "is_current"
  ];

  final String theUserAccountTableHandler = 'user_account';
  Future createUserAccountTable(Database db) async {
    await db.execute('''CREATE TABLE $theUserAccountTableHandler (
    id INTEGER  PRIMARY KEY AUTOINCREMENT,
    description TEXT ,
    abbreviation TEXT  NOT NULL,
    email TEXT  NOT NULL,
    user_name TEXT  NOT NULL,
    is_current INTEGER ,
    UNIQUE (id),
    UNIQUE (abbreviation),
    UNIQUE (user_name)
    )''');
  }

  Future<int> saveUserAccount(UserAccountProxy instanceUserAccount) async {
    var dbClient = await db;

    var result = await dbClient.insert(
        theUserAccountTableHandler, instanceUserAccount.toMap());
    return result;
  }

  Future<List<UserAccountProxy>> getUserAccountProxiesAll() async {
    var dbClient = await db;
    var result = await dbClient.query(theUserAccountTableHandler,
        columns: theUserAccountColumns, where: '1');

    return result.map((e) => UserAccountProxy.fromMap(e)).toList();
  }

  Future<int> getUserAccountProxiesCount() async {
    var dbClient = await db;
    return Sqflite.firstIntValue(await dbClient
        .rawQuery('SELECT COUNT(*) FROM $theUserAccountTableHandler  WHERE 1'));
  }

  Future<UserAccountProxy> getUserAccount(int id) async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(theUserAccountTableHandler,
        columns: theUserAccountColumns, where: '1 AND id = ?', whereArgs: [id]);

    if (result.length > 0) {
      return UserAccountProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> deleteUserAccount(int id) async {
    var dbClient = await db;
    return await dbClient
        .delete(theUserAccountTableHandler, where: 'id = ?', whereArgs: [id]);
  }

  Future<bool> deleteUserAccountProxiesAll() async {
    var dbClient = await db;
    await dbClient.delete(theUserAccountTableHandler);
    return true;
  }

  Future<int> updateUserAccount(UserAccountProxy instanceUserAccount) async {
    var dbClient = await db;

    return await dbClient.update(
        theUserAccountTableHandler, instanceUserAccount.toMap(),
        where: "id = ?", whereArgs: [instanceUserAccount.id]);
  }

  Future<UserAccountProxy> getCurrentUserAccount() async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(theUserAccountTableHandler,
        columns: theUserAccountColumns, where: '1 AND is_current = 1');

    if (result.length > 0) {
      return UserAccountProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> setCurrentUserAccount(int id) async {
    var dbClient = await db;

    var map = Map<String, dynamic>();
    map['is_current'] = 0;

    await dbClient.update(theUserAccountTableHandler, map,
        where: "is_current = 1");

    map['is_current'] = 1;
    return await dbClient.update(theUserAccountTableHandler, map,
        where: "1 AND id = ?", whereArgs: [id]);
  }
}
import 'package:f_orm_m8/f_orm_m8.dart';

@DataTable(
    "health_entry", TableMetadata.trackCreate | TableMetadata.trackUpdate)
class HealthEntry implements DbAccountRelatedEntity {
  @DataColumn("id",
      metadataLevel: ColumnMetadata.primaryKey |
          ColumnMetadata.unique |
          ColumnMetadata.autoIncrement)
  int id;

  @DataColumn("diagnosys_date")
  DateTime diagnosysDate;

  @override
  @DataColumn("account_id",
      metadataLevel: ColumnMetadata.notNull,
      compositeConstraints: [
        CompositeConstraint(
            name: "uq_account_entry",
            constraintType: CompositeConstraintType.unique),
        CompositeConstraint(
            name: "ix_account_entry",
            constraintType: CompositeConstraintType.indexed)
      ])
  int accountId;

  @DataColumn("description",
      metadataLevel: ColumnMetadata.notNull,
      compositeConstraints: [
        CompositeConstraint(
            name: "uq_account_entry",
            constraintType: CompositeConstraintType.unique)
      ])
  String description;

  @DataColumn("my_future_column7",
      metadataLevel: ColumnMetadata.ignore | ColumnMetadata.unique)
  int futureData;
}

The generated code #

From the model, the builder creates models/health_entry.g.m8.dart file with content

// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-17 13:54:19.192616

// **************************************************************************
// Generator: OrmM8GeneratorForAnnotation
// **************************************************************************

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:sqlite_m8_demo/models/health_entry.dart';

class HealthEntryProxy extends HealthEntry {
  DateTime dateCreate;
  DateTime dateUpdate;

  HealthEntryProxy({accountId, description}) {
    this.accountId = accountId;
    this.description = description;
  }

  Map<String, dynamic> toMap() {
    var map = Map<String, dynamic>();
    map['id'] = id;
    map['diagnosys_date'] = diagnosysDate.millisecondsSinceEpoch;
    map['account_id'] = accountId;
    map['description'] = description;
    map['date_create'] = dateCreate.millisecondsSinceEpoch;
    map['date_update'] = dateUpdate.millisecondsSinceEpoch;

    return map;
  }

  HealthEntryProxy.fromMap(Map<String, dynamic> map) {
    this.id = map['id'];
    this.diagnosysDate =
        DateTime.fromMillisecondsSinceEpoch(map['diagnosys_date']);
    this.accountId = map['account_id'];
    this.description = map['description'];
    this.dateCreate = DateTime.fromMillisecondsSinceEpoch(map['date_create']);
    this.dateUpdate = DateTime.fromMillisecondsSinceEpoch(map['date_update']);
  }
}

mixin HealthEntryDatabaseProvider {
  Future<Database> db;
  final theHealthEntryColumns = [
    "id",
    "diagnosys_date",
    "account_id",
    "description",
    "date_create",
    "date_update"
  ];

  final String theHealthEntryTableHandler = 'health_entry';
  Future createHealthEntryTable(Database db) async {
    await db.execute('''CREATE TABLE $theHealthEntryTableHandler (
    id INTEGER  PRIMARY KEY AUTOINCREMENT,
    diagnosys_date INTEGER ,
    account_id INTEGER  NOT NULL,
    description TEXT  NOT NULL,
    date_create INTEGER,
    date_update INTEGER,
    UNIQUE (id, account_id),
    UNIQUE (account_id, description, account_id)
    )''');
    await db.execute(
        '''CREATE INDEX ix_${theHealthEntryTableHandler}_ix_account_entry ON $theHealthEntryTableHandler (account_id)''');
  }

  Future<int> saveHealthEntry(HealthEntryProxy instanceHealthEntry) async {
    var dbClient = await db;

    instanceHealthEntry.dateCreate = DateTime.now();
    instanceHealthEntry.dateUpdate = DateTime.now();

    var result = await dbClient.insert(
        theHealthEntryTableHandler, instanceHealthEntry.toMap());
    return result;
  }

  Future<List<HealthEntryProxy>> getHealthEntryProxiesAll() async {
    var dbClient = await db;
    var result = await dbClient.query(theHealthEntryTableHandler,
        columns: theHealthEntryColumns, where: '1');

    return result.map((e) => HealthEntryProxy.fromMap(e)).toList();
  }

  Future<int> getHealthEntryProxiesCount() async {
    var dbClient = await db;
    return Sqflite.firstIntValue(await dbClient
        .rawQuery('SELECT COUNT(*) FROM $theHealthEntryTableHandler  WHERE 1'));
  }

  Future<HealthEntryProxy> getHealthEntry(int id) async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(theHealthEntryTableHandler,
        columns: theHealthEntryColumns, where: '1 AND id = ?', whereArgs: [id]);

    if (result.length > 0) {
      return HealthEntryProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> deleteHealthEntry(int id) async {
    var dbClient = await db;
    return await dbClient
        .delete(theHealthEntryTableHandler, where: 'id = ?', whereArgs: [id]);
  }

  Future<bool> deleteHealthEntryProxiesAll() async {
    var dbClient = await db;
    await dbClient.delete(theHealthEntryTableHandler);
    return true;
  }

  Future<int> updateHealthEntry(HealthEntryProxy instanceHealthEntry) async {
    var dbClient = await db;

    instanceHealthEntry.dateUpdate = DateTime.now();

    return await dbClient.update(
        theHealthEntryTableHandler, instanceHealthEntry.toMap(),
        where: "id = ?", whereArgs: [instanceHealthEntry.id]);
  }

  Future<List<HealthEntryProxy>> getHealthEntryProxiesByAccountId(
      int accountId) async {
    var dbClient = await db;
    var result = await dbClient.query(theHealthEntryTableHandler,
        columns: theHealthEntryColumns,
        where: 'account_id = ? AND 1',
        whereArgs: [accountId]);

    return result.map((e) => HealthEntryProxy.fromMap(e)).toList();
  }
}

GymLocation - A DbEntity implementation #

To demonstrate how to use a generic model, we added a GymLocation model that implements DbEntity.

usecase003

The model #

The model file models/gym_location.dart has the following content:

import 'package:f_orm_m8/f_orm_m8.dart';

@DataTable(
    "gym_location", TableMetadata.trackCreate | TableMetadata.trackUpdate)
class GymLocation implements DbEntity {
  @DataColumn("id",
      metadataLevel: ColumnMetadata.primaryKey |
          ColumnMetadata.unique |
          ColumnMetadata.autoIncrement)
  int id;

  @DataColumn("description", metadataLevel: ColumnMetadata.unique)
  String description;

  @DataColumn("rating")
  int rating;

  @DataColumn("my_future_column7",
      metadataLevel: ColumnMetadata.ignore | ColumnMetadata.unique)
  int futureData;
}

The generated code #

From the model, the builder creates models/gym_location.g.m8.dart file with content

// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-17 13:54:19.192616

// **************************************************************************
// Generator: OrmM8GeneratorForAnnotation
// **************************************************************************

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:sqlite_m8_demo/models/gym_location.dart';

class GymLocationProxy extends GymLocation {
  DateTime dateCreate;
  DateTime dateUpdate;

  GymLocationProxy();

  Map<String, dynamic> toMap() {
    var map = Map<String, dynamic>();
    map['id'] = id;
    map['description'] = description;
    map['rating'] = rating;
    map['date_create'] = dateCreate.millisecondsSinceEpoch;
    map['date_update'] = dateUpdate.millisecondsSinceEpoch;

    return map;
  }

  GymLocationProxy.fromMap(Map<String, dynamic> map) {
    this.id = map['id'];
    this.description = map['description'];
    this.rating = map['rating'];
    this.dateCreate = DateTime.fromMillisecondsSinceEpoch(map['date_create']);
    this.dateUpdate = DateTime.fromMillisecondsSinceEpoch(map['date_update']);
  }
}

mixin GymLocationDatabaseProvider {
  Future<Database> db;
  final theGymLocationColumns = [
    "id",
    "description",
    "rating",
    "date_create",
    "date_update"
  ];

  final String theGymLocationTableHandler = 'gym_location';
  Future createGymLocationTable(Database db) async {
    await db.execute('''CREATE TABLE $theGymLocationTableHandler (
    id INTEGER  PRIMARY KEY AUTOINCREMENT,
    description TEXT ,
    rating INTEGER ,
    date_create INTEGER,
    date_update INTEGER,
    UNIQUE (id),
    UNIQUE (description)
    )''');
  }

  Future<int> saveGymLocation(GymLocationProxy instanceGymLocation) async {
    var dbClient = await db;

    instanceGymLocation.dateCreate = DateTime.now();
    instanceGymLocation.dateUpdate = DateTime.now();

    var result = await dbClient.insert(
        theGymLocationTableHandler, instanceGymLocation.toMap());
    return result;
  }

  Future<List<GymLocationProxy>> getGymLocationProxiesAll() async {
    var dbClient = await db;
    var result = await dbClient.query(theGymLocationTableHandler,
        columns: theGymLocationColumns, where: '1');

    return result.map((e) => GymLocationProxy.fromMap(e)).toList();
  }

  Future<int> getGymLocationProxiesCount() async {
    var dbClient = await db;
    return Sqflite.firstIntValue(await dbClient
        .rawQuery('SELECT COUNT(*) FROM $theGymLocationTableHandler  WHERE 1'));
  }

  Future<GymLocationProxy> getGymLocation(int id) async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(theGymLocationTableHandler,
        columns: theGymLocationColumns, where: '1 AND id = ?', whereArgs: [id]);

    if (result.length > 0) {
      return GymLocationProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> deleteGymLocation(int id) async {
    var dbClient = await db;
    return await dbClient
        .delete(theGymLocationTableHandler, where: 'id = ?', whereArgs: [id]);
  }

  Future<bool> deleteGymLocationProxiesAll() async {
    var dbClient = await db;
    await dbClient.delete(theGymLocationTableHandler);
    return true;
  }

  Future<int> updateGymLocation(GymLocationProxy instanceGymLocation) async {
    var dbClient = await db;

    instanceGymLocation.dateUpdate = DateTime.now();

    return await dbClient.update(
        theGymLocationTableHandler, instanceGymLocation.toMap(),
        where: "id = ?", whereArgs: [instanceGymLocation.id]);
  }
}


Receipt - A DbEntity implementation #

For a more detailed model with all supported fields type, we added a Receipt model that implements DbEntity.

usecase004

The model #

The model file models/receipt.dart has the following content:

import 'package:f_orm_m8/f_orm_m8.dart';

@DataTable("receipt", TableMetadata.trackCreate | TableMetadata.trackUpdate)
class Receipt implements DbEntity {
  @DataColumn("id",
      metadataLevel: ColumnMetadata.primaryKey |
          ColumnMetadata.unique |
          ColumnMetadata.autoIncrement)
  int id;

  @DataColumn("number_of_molecules", metadataLevel: ColumnMetadata.notNull)
  BigInt numberOfMolecules;

  @DataColumn("is_bio", metadataLevel: ColumnMetadata.notNull)
  bool isBio;

  @DataColumn("expiration_date", metadataLevel: ColumnMetadata.notNull)
  DateTime expirationDate;

  @DataColumn("quantity", metadataLevel: ColumnMetadata.notNull)
  double quantity;

  @DataColumn("decomposing_duration", metadataLevel: ColumnMetadata.notNull)
  Duration decomposingDuration;

  @DataColumn("number_of_items", metadataLevel: ColumnMetadata.notNull)
  int numberOfItems;

  @DataColumn("storage_temperature", metadataLevel: ColumnMetadata.notNull)
  num storageTemperature;

  @DataColumn("description",
      metadataLevel: ColumnMetadata.unique | ColumnMetadata.notNull)
  String description;

  @DataColumn("my_future_column7",
      metadataLevel: ColumnMetadata.ignore | ColumnMetadata.unique)
  int futureData;
}

The generated code #

From the model, the builder creates models/receipt.g.m8.dart file with content

// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-17 13:54:19.192616

// **************************************************************************
// Generator: OrmM8GeneratorForAnnotation
// **************************************************************************

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:sqlite_m8_demo/models/receipt.dart';

class ReceiptProxy extends Receipt {
  DateTime dateCreate;
  DateTime dateUpdate;

  ReceiptProxy(
      {numberOfMolecules,
      isBio,
      expirationDate,
      quantity,
      decomposingDuration,
      numberOfItems,
      storageTemperature,
      description}) {
    this.numberOfMolecules = numberOfMolecules;
    this.isBio = isBio;
    this.expirationDate = expirationDate;
    this.quantity = quantity;
    this.decomposingDuration = decomposingDuration;
    this.numberOfItems = numberOfItems;
    this.storageTemperature = storageTemperature;
    this.description = description;
  }

  Map<String, dynamic> toMap() {
    var map = Map<String, dynamic>();
    map['id'] = id;
    map['number_of_molecules'] = numberOfMolecules.toString();
    map['is_bio'] = isBio ? 1 : 0;
    map['expiration_date'] = expirationDate.millisecondsSinceEpoch;
    map['quantity'] = quantity;
    map['decomposing_duration'] = decomposingDuration.inMilliseconds;
    map['number_of_items'] = numberOfItems;
    map['storage_temperature'] = storageTemperature;
    map['description'] = description;
    map['date_create'] = dateCreate.millisecondsSinceEpoch;
    map['date_update'] = dateUpdate.millisecondsSinceEpoch;

    return map;
  }

  ReceiptProxy.fromMap(Map<String, dynamic> map) {
    this.id = map['id'];
    this.numberOfMolecules = BigInt.parse(map['number_of_molecules']);
    this.isBio = map['is_bio'] == 1 ? true : false;
    this.expirationDate =
        DateTime.fromMillisecondsSinceEpoch(map['expiration_date']);
    this.quantity = map['quantity'];
    this.decomposingDuration =
        Duration(milliseconds: map['decomposing_duration']);
    this.numberOfItems = map['number_of_items'];
    this.storageTemperature = map['storage_temperature'];
    this.description = map['description'];
    this.dateCreate = DateTime.fromMillisecondsSinceEpoch(map['date_create']);
    this.dateUpdate = DateTime.fromMillisecondsSinceEpoch(map['date_update']);
  }
}

mixin ReceiptDatabaseProvider {
  Future<Database> db;
  final theReceiptColumns = [
    "id",
    "number_of_molecules",
    "is_bio",
    "expiration_date",
    "quantity",
    "decomposing_duration",
    "number_of_items",
    "storage_temperature",
    "description",
    "date_create",
    "date_update"
  ];

  final String theReceiptTableHandler = 'receipt';
  Future createReceiptTable(Database db) async {
    await db.execute('''CREATE TABLE $theReceiptTableHandler (
    id INTEGER  PRIMARY KEY AUTOINCREMENT,
    number_of_molecules TEXT  NOT NULL,
    is_bio INTEGER  NOT NULL,
    expiration_date INTEGER  NOT NULL,
    quantity REAL  NOT NULL,
    decomposing_duration INTEGER  NOT NULL,
    number_of_items INTEGER  NOT NULL,
    storage_temperature NUMERIC  NOT NULL,
    description TEXT  NOT NULL,
    date_create INTEGER,
    date_update INTEGER,
    UNIQUE (id),
    UNIQUE (description)
    )''');
  }

  Future<int> saveReceipt(ReceiptProxy instanceReceipt) async {
    var dbClient = await db;

    instanceReceipt.dateCreate = DateTime.now();
    instanceReceipt.dateUpdate = DateTime.now();

    var result =
        await dbClient.insert(theReceiptTableHandler, instanceReceipt.toMap());
    return result;
  }

  Future<List<ReceiptProxy>> getReceiptProxiesAll() async {
    var dbClient = await db;
    var result = await dbClient.query(theReceiptTableHandler,
        columns: theReceiptColumns, where: '1');

    return result.map((e) => ReceiptProxy.fromMap(e)).toList();
  }

  Future<int> getReceiptProxiesCount() async {
    var dbClient = await db;
    return Sqflite.firstIntValue(await dbClient
        .rawQuery('SELECT COUNT(*) FROM $theReceiptTableHandler  WHERE 1'));
  }

  Future<ReceiptProxy> getReceipt(int id) async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(theReceiptTableHandler,
        columns: theReceiptColumns, where: '1 AND id = ?', whereArgs: [id]);

    if (result.length > 0) {
      return ReceiptProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> deleteReceipt(int id) async {
    var dbClient = await db;
    return await dbClient
        .delete(theReceiptTableHandler, where: 'id = ?', whereArgs: [id]);
  }

  Future<bool> deleteReceiptProxiesAll() async {
    var dbClient = await db;
    await dbClient.delete(theReceiptTableHandler);
    return true;
  }

  Future<int> updateReceipt(ReceiptProxy instanceReceipt) async {
    var dbClient = await db;

    instanceReceipt.dateUpdate = DateTime.now();

    return await dbClient.update(
        theReceiptTableHandler, instanceReceipt.toMap(),
        where: "id = ?", whereArgs: [instanceReceipt.id]);
  }
}


The database adapter #

For the all models the builder will generate a common database adapter file main.adapter.g.m8.dart. The file contains two classes:

  • DatabaseAdapter to handle the initialization of the database singleton
  • DatabaseProvider to handle the models CRUD methods
// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-17 13:54:19.192616

// **************************************************************************
// DatabaseProviderGenerator
// **************************************************************************

import 'dart:async';

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

import 'package:sqlite_m8_demo/models/gym_location.g.m8.dart';
import 'package:sqlite_m8_demo/models/health_entry.g.m8.dart';
import 'package:sqlite_m8_demo/models/receipt.g.m8.dart';
import 'package:sqlite_m8_demo/models/to_do.g.m8.dart';
import 'package:sqlite_m8_demo/models/user_account.g.m8.dart';

enum InitMode { developmentAlwaysReinitDb, testingMockDb, production }

class DatabaseAdapter {
  InitMode _initMode;
  static InitMode _startInitMode;
  static final DatabaseAdapter _instance = DatabaseAdapter._internal();
  static Database _db;

  /// Default initMode is production
  /// [developmentAlwaysReinitDb] then the database will be deleteted on each init
  /// [testingMockDb] then the database will be initialized as mock
  /// [production] then the database will be initialized as production
  factory DatabaseAdapter([InitMode initMode = InitMode.production]) {
    _startInitMode = initMode;
    return _instance;
  }

  DatabaseAdapter._internal() {
    if (_initMode == null) {
      _initMode = _startInitMode;
    }
  }

  InitMode get initMode => _initMode;

  Future<Database> getDb(dynamic _onCreate) async {
    if (_db != null) {
      return _db;
    }
    String databasesPath = await getDatabasesPath();
    String path = join(databasesPath, 'm8_store_0.2.0.db');

    if (_startInitMode == InitMode.developmentAlwaysReinitDb) {
      await deleteDatabase(path);
    }

    _db = await openDatabase(path, version: 2, onCreate: _onCreate);
    return _db;
  }
}

class DatabaseProvider
    with
        GymLocationDatabaseProvider,
        HealthEntryDatabaseProvider,
        ReceiptDatabaseProvider,
        ToDoDatabaseProvider,
        UserAccountDatabaseProvider {
  static final DatabaseProvider _instance = DatabaseProvider._internal();
  static Database _db;

  static DatabaseAdapter _dbBuilder;

  factory DatabaseProvider(DatabaseAdapter dbBuilder) {
    _dbBuilder = dbBuilder;
    return _instance;
  }

  DatabaseProvider._internal();

  Future<Database> get db async {
    if (_db != null) {
      return _db;
    }
    _db = await _dbBuilder.getDb(_onCreate);

    return _db;
  }

  void _onCreate(Database db, int newVersion) async {
    await createGymLocationTable(db);
    await createHealthEntryTable(db);
    await createReceiptTable(db);
    await createToDoTable(db);
    await createUserAccountTable(db);
  }

  Future close() async {
    var dbClient = await db;
    return dbClient.close();
  }

  Future deleteAll() async {
    await deleteGymLocationProxiesAll();
    await deleteHealthEntryProxiesAll();
    await deleteReceiptProxiesAll();
    await deleteToDoProxiesAll();
    await deleteUserAccountProxiesAll();
  }
}


Changelog #

[Unreleased]

[0.7.0+4] - 2019-06-25

Changed #

  • test reference version to ^1.6.0
  • CI pipeline: flutter sdk updated for 1.5.4-hotfix.2-stable

Fixed #

  • CI pipeline: authentication codes were disabled on coverage with --disable-service-auth-codes
  • CI pipeline: travis_flutter.sh reviewed

[0.7.0+3] - 2019-06-24

Changed #

  • Analyzer version to 0.36.0

[0.7.0+2] - 2019-05-23

Fixed #

  • Dartlang publish warnings

Added #

  • More tests on example project in order to obtain a test coverage greater than 95%

[0.7.0+1] - 2019-05-18

Changed #

  • Update to Example project. Change the theme color, updating docs animated gif's

[0.7.0] - 2019-05-17

Fixed #

  • Issue #97. The generator must validate models with multiple PKs. If the model is incorectly annotated with PKs the generator emit a wrong mixin.

Added #

  • Validation for Primary Key combination
  • Implementation for Indexed annotation from MetadataLevel or from Composites in any combination

Changed #

  • The softDelete annotation generates a combination of unique key with other uniqe annotated fields

[0.6.3] - 2019-05-16

Added #

  • Implementation for mapping Duration fields. Currently handled in milliseconds and saved to database as INTEGER
  • Implementation for mapping BigInt fields. Saved to database as TEXT to keep precision
  • Implementation for DbOpenEntity scaffolding

Changed #

  • If the Model class does not implement allowed interfaces (DbEntity or DbOpenEntity) the parser will handle as validation error (not Exception)

[0.6.2+1] - 2019-05-15

Changed #

  • Refactoring entity attribute classes to closer OOP

Fixed #

  • Analysis warnings

[0.6.2] - 2019-05-15

Added #

  • Test for Account entity raw output generation
  • Test the multiple DataColumn annotations on the same field!
  • Validation on post extraction for models with fields that do not pass validation at all
  • A unified header to all generated files
  • A DatabaseAdapter to handle the database singleton in different init stages (dev/ci/prod)
  • Tests for CRUD operations on DatabaseProvider

Changed #

  • Throw Exception switched to Validation design pattern
  • If the model is not valid, then the generated file will contain the validation issues in a comment block
  • In *DatabaseProvider mixins, the the*TableHandler fields to public
  • Renamed DatabaseHelper as DatabaseProvider

[0.6.1] - 2019-05-06

Added #

  • Default name generation when DataTable name is missing. The name is the model name prefixed with "M8"
  • Tests for generator exceptions handling

[0.6.0+2] - 2019-05-05

Changed #

  • README.md reviewed. Typo fixes. Updating dependencies table. Adding codecov badge

Added #

  • Widget tests on example project

[0.6.0+1] - 2019-05-03

Changed #

  • README.md reviewed

[0.6.0] - 2019-05-03

Changed #

  • The github project was renamed from flutter-sqlite-m8-generator to f-orm-m8-sqlite
  • It package was renamed from flutter_sqlite_m8_generator to f_orm_m8_sqlite
  • The dart package was published as f_orm_m8_sqlite
  • See f_orm_m8_sqlite for new updated framework

[0.5.0] - 2019-05-03

Changed #

  • Switched from flutter_orm_m8 to the de facto successor f_orm_m8
  • Aligned generators with f_orm_m8 v0.8.0
  • Softdeletable meta determines the generation of a DateTime dateDelete field
  • The tests were refactored with caliber files

Added #

  • A custom header with timestamp on the generated files
  • Test coverage reporting to the CI pipeline
  • The example project integration tests were wired the CI pipeline

[0.4.0] - 2019-05-01

Changed #

  • Aligned generators with flutter-orm-m8 v0.7.1
  • Example project: HealthEntry model received a composite unique constraint

Added #

  • Generator support for CompositeConstraint.unique
  • Generator support for CompositeConstraint.primaryKey
  • Tests for CompositeConstraint generation
  • Flutter Driver tests on example project for all UI use cases

[0.3.0+1] - 2019-04-25

Fixed #

  • Documentation

[0.3.0] - 2019-04-23

Changed #

  • Aligned generators with flutter-orm-m8 v0.6.0
  • Default constructor on proxy entities has named parameters
  • All db helpers get*all methods return a list of proxy entities
  • The generator handles DbAccountEntity isCurrent field
  • The pluralize for model switched from "${modelName}s" to "${modelName}Proxies"
  • Example project was extended with implementations for:
    • DbEntity
    • DbAccountEntity
    • DbAccountRelatedEntity

Fixed #

  • Wrong attribute emission on accountId, for account related entities

[0.2.3+1] - 2019-04-15

Fixed #

  • Fix typo in save and update methods for trackable entities

Changed #

  • The showcase in the example project. All models are closer to a real life application

[0.2.3] - 2019-04-14

Fixed #

  • The generated entity database helper does not contain the meta columns
  • Fixed test in the example project

Changed #

  • The showcase in the example project

[0.2.2] - 2019-04-12

Added #

  • Bidirectional mapping for Model DateTime to Database Integer
  • Complete implementation for trackable, update and create, fields
  • Boolean extremeDevelopmentMode field to DatabaseProvider to control regeneration of database in dev mode

[0.2.1] - 2019-04-09

Changed #

  • Emitting only public fields

Fixed #

  • Missing metadataLevel on DataColumn annotated model fields, generates wrong emition

[0.2.0] - 2019-04-07

Added #

  • A full implementation in the example project for a dbEntity CRUD

Fixed #

  • Returning all rows on softDeletable entities

[0.1.1] - 2019-04-06

Changed #

  • FileStamp for database file

Fixed #

  • Fixing format and analyzer issues recommended after publish
  • Fixing readme formatting issues

[0.1.0] - 2019-04-05

Added #

  • Chaining database adapter builder with annotation helpers builder
  • Tests
  • A proxy default constructor with mandatory parameters
  • A database adapter

Changed #

  • Generated files extension

[0.0.7] - 2019-04-03

Added #

  • Working generator, ready to be published
  • Soft delete annotation implemented
  • Track create annotation implemented
  • Track update annotation implemented

[0.0.6] - 2019-04-02

Added #

  • Proxy writer

[0.0.5] - 2019-04-01

Added #

  • A simple fields enumeration with model type
  • A basic type mapper for Sqlite types
  • Expanding metadata in column definition
  • Extracting the primary key name

[0.0.4] - 2019-03-22

Added #

  • An entity factory for account related, account and independent entities

Changed #

  • The build output is routed to cache

[0.0.3] - 2019-03-14

Added #

  • A weak try to expand some information
  • Tests

[0.0.2] - 2019-03-14

Fixed #

  • Fixing analyzer issues

[0.0.1] - 2019-03-03

Added #

  • Basic generator

example/README.md

Example - Gymspector application #

A full, flutter working example is maintained on https://github.com/matei-tm/f-orm-m8-sqlite/tree/master/example.
The example presents different approaches to solve CRUD functionality for models that adhere to f_orm_m8 annotation framework.

HealthEntry - A DbAccountRelatedEntity implementation #

To demonstrate how to use a model that is dependent to UserAccount, we use a HealthEntry model that implements DbAccountRelatedEntity. The model detain a composite unique constraint based on accountId and description.

usecase002

The model #

The model file models/health_entry.dart has the following content:

import 'package:f_orm_m8/f_orm_m8.dart';

@DataTable(
    "health_entry", TableMetadata.trackCreate | TableMetadata.trackUpdate)
class HealthEntry implements DbAccountRelatedEntity {
  @DataColumn("id",
      metadataLevel: ColumnMetadata.primaryKey |
          ColumnMetadata.unique |
          ColumnMetadata.autoIncrement)
  int id;

  @DataColumn("diagnosys_date")
  DateTime diagnosysDate;

  @override
  @DataColumn("account_id",
      metadataLevel: ColumnMetadata.notNull,
      compositeConstraints: [
        CompositeConstraint(
            name: "uq_account_entry",
            constraintType: CompositeConstraintType.unique),
        CompositeConstraint(
            name: "ix_account_entry",
            constraintType: CompositeConstraintType.indexed)
      ])
  int accountId;

  @DataColumn("description",
      metadataLevel: ColumnMetadata.notNull,
      compositeConstraints: [
        CompositeConstraint(
            name: "uq_account_entry",
            constraintType: CompositeConstraintType.unique)
      ])
  String description;

  @DataColumn("my_future_column7",
      metadataLevel: ColumnMetadata.ignore | ColumnMetadata.unique)
  int futureData;
}

The generated code #

From the model, the builder creates models/health_entry.g.m8.dart file with content

// GENERATED CODE - DO NOT MODIFY BY HAND
// Emitted on: 2019-05-12 22:58:06.362172

// **************************************************************************
// Generator: OrmM8GeneratorForAnnotation
// **************************************************************************

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'package:sqlite_m8_demo/models/health_entry.dart';

class HealthEntryProxy extends HealthEntry {
  DateTime dateCreate;
  DateTime dateUpdate;

  HealthEntryProxy({accountId, description}) {
    this.accountId = accountId;
    this.description = description;
  }

  Map<String, dynamic> toMap() {
    var map = Map<String, dynamic>();
    map['id'] = id;
    map['diagnosys_date'] = diagnosysDate.millisecondsSinceEpoch;
    map['account_id'] = accountId;
    map['description'] = description;
    map['date_create'] = dateCreate.millisecondsSinceEpoch;
    map['date_update'] = dateUpdate.millisecondsSinceEpoch;

    return map;
  }

  HealthEntryProxy.fromMap(Map<String, dynamic> map) {
    this.id = map['id'];
    this.diagnosysDate =
        DateTime.fromMillisecondsSinceEpoch(map['diagnosys_date']);
    this.accountId = map['account_id'];
    this.description = map['description'];
    this.dateCreate = DateTime.fromMillisecondsSinceEpoch(map['date_create']);
    this.dateUpdate = DateTime.fromMillisecondsSinceEpoch(map['date_update']);
  }
}

mixin HealthEntryDatabaseProvider {
  Future<Database> db;
  final theHealthEntryColumns = [
    "id",
    "diagnosys_date",
    "account_id",
    "description",
    "date_create",
    "date_update"
  ];

  final String _theHealthEntryTableHandler = 'health_entry';
  Future createHealthEntryTable(Database db) async {
    await db.execute('''CREATE TABLE $_theHealthEntryTableHandler (
    id INTEGER  PRIMARY KEY AUTOINCREMENT UNIQUE,
    diagnosys_date INTEGER ,
    account_id INTEGER  NOT NULL,
    description TEXT  NOT NULL,
    date_create INTEGER,
    date_update INTEGER    ,
    UNIQUE(account_id, description)
)''');
  }

  Future<int> saveHealthEntry(HealthEntryProxy instanceHealthEntry) async {
    var dbClient = await db;

    instanceHealthEntry.dateCreate = DateTime.now();
    instanceHealthEntry.dateUpdate = DateTime.now();

    var result = await dbClient.insert(
        _theHealthEntryTableHandler, instanceHealthEntry.toMap());
    return result;
  }

  Future<List<HealthEntry>> getHealthEntryProxiesAll() async {
    var dbClient = await db;
    var result = await dbClient.query(_theHealthEntryTableHandler,
        columns: theHealthEntryColumns, where: '1');

    return result.map((e) => HealthEntryProxy.fromMap(e)).toList();
  }

  Future<int> getHealthEntryProxiesCount() async {
    var dbClient = await db;
    return Sqflite.firstIntValue(await dbClient.rawQuery(
        'SELECT COUNT(*) FROM $_theHealthEntryTableHandler  WHERE 1'));
  }

  Future<HealthEntry> getHealthEntry(int id) async {
    var dbClient = await db;
    List<Map> result = await dbClient.query(_theHealthEntryTableHandler,
        columns: theHealthEntryColumns, where: '1 AND id = ?', whereArgs: [id]);

    if (result.length > 0) {
      return HealthEntryProxy.fromMap(result.first);
    }

    return null;
  }

  Future<int> deleteHealthEntry(int id) async {
    var dbClient = await db;
    return await dbClient
        .delete(_theHealthEntryTableHandler, where: 'id = ?', whereArgs: [id]);
  }

  Future<bool> deleteHealthEntryProxiesAll() async {
    var dbClient = await db;
    await dbClient.delete(_theHealthEntryTableHandler);
    return true;
  }

  Future<int> updateHealthEntry(HealthEntryProxy instanceHealthEntry) async {
    var dbClient = await db;

    instanceHealthEntry.dateUpdate = DateTime.now();

    return await dbClient.update(
        _theHealthEntryTableHandler, instanceHealthEntry.toMap(),
        where: "id = ?", whereArgs: [instanceHealthEntry.id]);
  }

  Future<List<HealthEntry>> getHealthEntryProxiesByAccountId(
      int accountId) async {
    var dbClient = await db;
    var result = await dbClient.query(_theHealthEntryTableHandler,
        columns: theHealthEntryColumns,
        where: 'account_id = ? AND 1',
        whereArgs: [accountId]);

    return result.map((e) => HealthEntryProxy.fromMap(e)).toList();
  }
}

GymLocation - A DbEntity implementation #

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  f_orm_m8_sqlite: ^0.7.0+4

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:f_orm_m8_sqlite/builder.dart';
import 'package:f_orm_m8_sqlite/exceptions/exception_expander.dart';
import 'package:f_orm_m8_sqlite/exceptions/field_parse_exception.dart';
import 'package:f_orm_m8_sqlite/generator/core.dart';
import 'package:f_orm_m8_sqlite/generator/database_helper_generator.dart';
import 'package:f_orm_m8_sqlite/generator/emitted_entity.dart';
import 'package:f_orm_m8_sqlite/generator/entity_writer.dart';
import 'package:f_orm_m8_sqlite/generator/model_parser.dart';
import 'package:f_orm_m8_sqlite/generator/orm_m8_generator_for_annotation.dart';
import 'package:f_orm_m8_sqlite/generator/utils/attribute_metadata_parser.dart';
import 'package:f_orm_m8_sqlite/generator/utils/db_entity_type_enum.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_factory.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_bigint.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_bool.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_datetime.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_double.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_duration.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_int.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_not_implemented.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_num.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_from_string.dart';
import 'package:f_orm_m8_sqlite/generator/utils/entity_attribute_types/entity_attribute_types.dart';
import 'package:f_orm_m8_sqlite/generator/utils/format_utils.dart';
import 'package:f_orm_m8_sqlite/generator/utils/type/checkers.dart';
import 'package:f_orm_m8_sqlite/generator/utils/type/column_assertions.dart';
import 'package:f_orm_m8_sqlite/generator/utils/type/table_assertions.dart';
import 'package:f_orm_m8_sqlite/generator/utils/type_utils.dart';
import 'package:f_orm_m8_sqlite/generator/utils/utils.dart';
import 'package:f_orm_m8_sqlite/generator/utils/validators/validation_collectable.dart';
import 'package:f_orm_m8_sqlite/generator/utils/validators/validation_issue.dart';
import 'package:f_orm_m8_sqlite/generator/validation_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/account_entity_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/account_related_entity_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/attribute_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/database_helper_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/entity_writer_factory.dart';
import 'package:f_orm_m8_sqlite/generator/writers/independent_entity_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/open_entity_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/proxy_writer.dart';
import 'package:f_orm_m8_sqlite/generator/writers/sql_definition_writer.dart';
import 'package:f_orm_m8_sqlite/m8_builder.dart';
import 'package:f_orm_m8_sqlite/orm_m8_generator.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
42
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
69
Learn more about scoring.

We analyzed this package on Dec 4, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.12.21

Platforms

Detected platforms: other

Platform components identified in package: build, io, mirrors.

Health issues and suggestions

Document public APIs. (-0.22 points)

290 out of 298 API elements have no dartdoc comment.Providing good documentation for libraries, classes, functions, and other API elements improves code readability and helps developers find and use your API.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (analyzer).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
analyzer ^0.36.0 0.36.4 0.39.1
build >=0.12.0 <2.0.0 1.2.2
f_orm_m8 ^0.8.0 0.8.0+3
path ^1.6.2 1.6.4
source_gen ^0.9.0 0.9.4+4 0.9.4+6
Transitive dependencies
args 1.5.2
async 2.4.0
charcode 1.1.2
collection 1.14.12
convert 2.1.1
crypto 2.1.3
csslib 0.16.1
dart_style 1.2.9 1.3.3
front_end 0.1.19 0.1.29
glob 1.2.0
html 0.14.0+3
js 0.6.1+1
kernel 0.3.19 0.3.29
logging 0.11.3+2
meta 1.1.8
node_interop 1.0.3
node_io 1.0.1+2
package_config 1.1.0
pedantic 1.8.0+1
pub_semver 1.4.2
source_span 1.5.5
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
watcher 0.9.7+13
yaml 2.2.0
Dev dependencies
build_runner ^1.0.0
build_test ^0.10.0
build_verify ^1.1.0
source_gen_test ^0.1.0
test ^1.6.0