Flutter Drift Database Implementation

Flutter Drift database :

Flutter Drift database is a reactive library used to store data locally built on top of sqlite database in your mobile apps.

Flutter drift Database play a key role in mobile app implementation by saving the user information and providing when required. Having a flexible db is a boon.

Using flutter drift database we can write safe in dart and sql query’s so that there won’t be any error’s in table creation, updating and all the CRUD operations.

Drift has got all the required features for sqlite which is of course built on top of sqlite.It can filter data use joins and fetch data from multiple tables.

 

Flutter drift video tutorial :

Go through the below tutorial for more details on drift database.

 

pubspec.yaml :

Add the required libraries for the drift.Also make sure you provide latest versions so that there won’t be any conflicts.

dependencies:
  
  drift: ^1.7.1
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.0

 

dev_dependencies:

  drift_dev: ^1.7.0
  build_runner: ^2.1.11

 

data.dart :

Let us create a data file where we provide the required fields to be used in the sqlite db i..e, flutter drift database. We need to provide fields required like id, title, description.

 

Then the required data.g.dart file i..e, generated file is created so that we can access the database to insert, retrieve and perform different operations on db.

 

You can go through the video tutorial for detailed explanation on how the file is generated.

 

 

import 'package:drift/drift.dart';

part 'data.g.dart';

class Products extends Table {

  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text()();
  TextColumn get description => text()();
}

abstract class ProductsView extends View{
  Products get products;

  @override
  Query as() => select([
    products.title
  ]).from(products);
}

@DriftDatabase(tables:[
  Products
], views:[
  ProductsView
])

class Database extends _$Database {
  Database(QueryExecutor e): super(e);

  @override
  int get schemaVersion => 2;
}

 

data.g.dart :

This is the generated file for data.dart file. As we discussed above this is used to perform database operations.

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'data.dart';

// **************************************************************************
// MoorGenerator
// **************************************************************************

// ignore_for_file: type=lint
class Product extends DataClass implements Insertable<Product> {
  final int id;
  final String title;
  final String description;
  Product({required this.id, required this.title, required this.description});
  factory Product.fromData(Map<String, dynamic> data, {String? prefix}) {
    final effectivePrefix = prefix ?? '';
    return Product(
      id: const IntType()
          .mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
      title: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
      description: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}description'])!,
    );
  }
  @override
  Map<String, Expression> toColumns(bool nullToAbsent) {
    final map = <String, Expression>{};
    map['id'] = Variable<int>(id);
    map['title'] = Variable<String>(title);
    map['description'] = Variable<String>(description);
    return map;
  }

  ProductsCompanion toCompanion(bool nullToAbsent) {
    return ProductsCompanion(
      id: Value(id),
      title: Value(title),
      description: Value(description),
    );
  }

  factory Product.fromJson(Map<String, dynamic> json,
      {ValueSerializer? serializer}) {
    serializer ??= driftRuntimeOptions.defaultSerializer;
    return Product(
      id: serializer.fromJson<int>(json['id']),
      title: serializer.fromJson<String>(json['title']),
      description: serializer.fromJson<String>(json['description']),
    );
  }
  @override
  Map<String, dynamic> toJson({ValueSerializer? serializer}) {
    serializer ??= driftRuntimeOptions.defaultSerializer;
    return <String, dynamic>{
      'id': serializer.toJson<int>(id),
      'title': serializer.toJson<String>(title),
      'description': serializer.toJson<String>(description),
    };
  }

  Product copyWith({int? id, String? title, String? description}) => Product(
        id: id ?? this.id,
        title: title ?? this.title,
        description: description ?? this.description,
      );
  @override
  String toString() {
    return (StringBuffer('Product(')
          ..write('id: $id, ')
          ..write('title: $title, ')
          ..write('description: $description')
          ..write(')'))
        .toString();
  }

  @override
  int get hashCode => Object.hash(id, title, description);
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      (other is Product &&
          other.id == this.id &&
          other.title == this.title &&
          other.description == this.description);
}

class ProductsCompanion extends UpdateCompanion<Product> {
  final Value<int> id;
  final Value<String> title;
  final Value<String> description;
  const ProductsCompanion({
    this.id = const Value.absent(),
    this.title = const Value.absent(),
    this.description = const Value.absent(),
  });
  ProductsCompanion.insert({
    this.id = const Value.absent(),
    required String title,
    required String description,
  })  : title = Value(title),
        description = Value(description);
  static Insertable<Product> custom({
    Expression<int>? id,
    Expression<String>? title,
    Expression<String>? description,
  }) {
    return RawValuesInsertable({
      if (id != null) 'id': id,
      if (title != null) 'title': title,
      if (description != null) 'description': description,
    });
  }

  ProductsCompanion copyWith(
      {Value<int>? id, Value<String>? title, Value<String>? description}) {
    return ProductsCompanion(
      id: id ?? this.id,
      title: title ?? this.title,
      description: description ?? this.description,
    );
  }

  @override
  Map<String, Expression> toColumns(bool nullToAbsent) {
    final map = <String, Expression>{};
    if (id.present) {
      map['id'] = Variable<int>(id.value);
    }
    if (title.present) {
      map['title'] = Variable<String>(title.value);
    }
    if (description.present) {
      map['description'] = Variable<String>(description.value);
    }
    return map;
  }

  @override
  String toString() {
    return (StringBuffer('ProductsCompanion(')
          ..write('id: $id, ')
          ..write('title: $title, ')
          ..write('description: $description')
          ..write(')'))
        .toString();
  }
}

class $ProductsTable extends Products with TableInfo<$ProductsTable, Product> {
  @override
  final GeneratedDatabase attachedDatabase;
  final String? _alias;
  $ProductsTable(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 _titleMeta = const VerificationMeta('title');
  @override
  late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
      'title', aliasedName, false,
      type: const StringType(), requiredDuringInsert: true);
  final VerificationMeta _descriptionMeta =
      const VerificationMeta('description');
  @override
  late final GeneratedColumn<String?> description = GeneratedColumn<String?>(
      'description', aliasedName, false,
      type: const StringType(), requiredDuringInsert: true);
  @override
  List<GeneratedColumn> get $columns => [id, title, description];
  @override
  String get aliasedName => _alias ?? 'products';
  @override
  String get actualTableName => 'products';
  @override
  VerificationContext validateIntegrity(Insertable<Product> 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('title')) {
      context.handle(
          _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta));
    } else if (isInserting) {
      context.missing(_titleMeta);
    }
    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 => {id};
  @override
  Product map(Map<String, dynamic> data, {String? tablePrefix}) {
    return Product.fromData(data,
        prefix: tablePrefix != null ? '$tablePrefix.' : null);
  }

  @override
  $ProductsTable createAlias(String alias) {
    return $ProductsTable(attachedDatabase, alias);
  }
}

class ProductsViewData extends DataClass {
  final String title;
  ProductsViewData({required this.title});
  factory ProductsViewData.fromData(Map<String, dynamic> data,
      {String? prefix}) {
    final effectivePrefix = prefix ?? '';
    return ProductsViewData(
      title: const StringType()
          .mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
    );
  }
  factory ProductsViewData.fromJson(Map<String, dynamic> json,
      {ValueSerializer? serializer}) {
    serializer ??= driftRuntimeOptions.defaultSerializer;
    return ProductsViewData(
      title: serializer.fromJson<String>(json['title']),
    );
  }
  @override
  Map<String, dynamic> toJson({ValueSerializer? serializer}) {
    serializer ??= driftRuntimeOptions.defaultSerializer;
    return <String, dynamic>{
      'title': serializer.toJson<String>(title),
    };
  }

  ProductsViewData copyWith({String? title}) => ProductsViewData(
        title: title ?? this.title,
      );
  @override
  String toString() {
    return (StringBuffer('ProductsViewData(')
          ..write('title: $title')
          ..write(')'))
        .toString();
  }

  @override
  int get hashCode => title.hashCode;
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      (other is ProductsViewData && other.title == this.title);
}

class $ProductsViewView extends ViewInfo<$ProductsViewView, ProductsViewData>
    implements HasResultSet {
  final String? _alias;
  @override
  final _$Database attachedDatabase;
  $ProductsViewView(this.attachedDatabase, [this._alias]);
  $ProductsTable get products => attachedDatabase.products.createAlias('t0');
  @override
  List<GeneratedColumn> get $columns => [products.title];
  @override
  String get aliasedName => _alias ?? entityName;
  @override
  String get entityName => 'products_view';
  @override
  String? get createViewStmt => null;
  @override
  $ProductsViewView get asDslTable => this;
  @override
  ProductsViewData map(Map<String, dynamic> data, {String? tablePrefix}) {
    return ProductsViewData.fromData(data,
        prefix: tablePrefix != null ? '$tablePrefix.' : null);
  }

  late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
      'title', aliasedName, false,
      type: const StringType());
  @override
  $ProductsViewView createAlias(String alias) {
    return $ProductsViewView(attachedDatabase, alias);
  }

  @override
  Query? get query =>
      (attachedDatabase.selectOnly(products, includeJoinedTableColumns: false)
        ..addColumns($columns));
  @override
  Set<String> get readTables => const {'products'};
}

abstract class _$Database extends GeneratedDatabase {
  _$Database(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
  late final $ProductsTable products = $ProductsTable(this);
  late final $ProductsViewView productsView = $ProductsViewView(this);
  @override
  Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
  @override
  List<DatabaseSchemaEntity> get allSchemaEntities => [products, productsView];
}

 

main.dart :

Providing the full code for implementation for drift database. Using this file we can access our database created above.

We have not created any user interface so this file will access the database but prints the data in logs.

You can go through our flutter tutorial collections so that you can create your own UI.

 

import 'package:drift/native.dart';
import 'data.dart';

Future<void> main() async {

  final db = Database(NativeDatabase.memory());

  await db.into(db.products).insert(ProductsCompanion.insert(title: "flutter drift",
      description: "Drift database"));
  await db.into(db.products).insert(ProductsCompanion.insert(title: "tutorial on drift",
      description: "Drift database"));
  (await db.select(db.products).get()).forEach(print);
}

 

 

Flutter drift output :

Flutter Drift

 

If you have any query’s in this tutorial on flutter drift implementation do let us know in the comment section below.If you like this tutorial do like and share us for more interesting updates.

 

Show Buttons
Hide Buttons
Read previous post:
flutter matcher
Flutter Matcher | Test your widget before using it !!

  Flutter Matcher : Flutter matcher, apps are built with widgets and yes every screen uses these widgets to make...

Close