How to do a database query with SQFlite in Flutter - sqlite

How do you query data from SQLite database in Flutter using the SQFlite plugin?
I have been working on learning this recently, so I am adding my answer below as a means to help me learn and also as a quick reference for others in the future.

Add the dependencies
Open pubspec.yaml and in the dependencies section add the following lines:
sqflite: ^1.0.0
path_provider: ^0.4.1
The sqflite is the SQFlite plugin of course and the path_provider will help us get the user directory on Android and iPhone. You can check the most up-to-date version numbers here: sqflite and path_provider.
Make a database helper class
I'm keeping a global reference to the database in a singleton class. This will prevent concurrency issues and data leaks. You can also add helper methods (like query) in here for accessing the database.
Create a new file called database_helper.dart and paste in the following code:
import 'dart:io' show Directory;
import 'package:path/path.dart' show join;
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart' show getApplicationDocumentsDirectory;
class DatabaseHelper {
static final _databaseName = "MyDatabase.db";
static final _databaseVersion = 1;
static final table = 'my_table';
static final columnId = '_id';
static final columnName = 'name';
static final columnAge = 'age';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY,
$columnName TEXT NOT NULL,
$columnAge INTEGER NOT NULL
)
''');
// prepopulate a few rows (consider using a transaction)
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Bob", 23)');
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Mary", 32)');
await db.rawInsert('INSERT INTO $table ($columnName, $columnAge) VALUES("Susan", 12)');
}
}
Note that when the database is created I pre-populated a few rows. This is so that we have something to work with in the query examples below.
Query data
We'll use an async method to do our query because database operations can be expensive.
Get all rows
To do a SELECT * and return everything in the table you just pass in the table name.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get all rows
List<Map> result = await db.query(DatabaseHelper.table);
// print the results
result.forEach((row) => print(row));
// {_id: 1, name: Bob, age: 23}
// {_id: 2, name: Mary, age: 32}
// {_id: 3, name: Susan, age: 12}
}
Get a single row
We can pass an argument in for the where parameter to select specific rows that meet our criteria. In this example we will query the row with an ID of 1.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get single row
List<String> columnsToSelect = [
DatabaseHelper.columnId,
DatabaseHelper.columnName,
DatabaseHelper.columnAge,
];
String whereString = '${DatabaseHelper.columnId} = ?';
int rowId = 1;
List<dynamic> whereArguments = [rowId];
List<Map> result = await db.query(
DatabaseHelper.table,
columns: columnsToSelect,
where: whereString,
whereArgs: whereArguments);
// print the results
result.forEach((row) => print(row));
// {_id: 1, name: Bob, age: 23}
}
The items in the whereArguments list get substituted in place of the ?s in the whereString. In this case there was only one ? so the whereArguments only had one item. If there were two ?s (for example an integer and a string), then you would have two items in the list.
Raw query
If you prefer the familiarity or flexibility of SQL code itself, you can do a raw query. In this example we will select any row whose name column is 'Mary'.
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// raw query
List<Map> result = await db.rawQuery('SELECT * FROM my_table WHERE name=?', ['Mary']);
// print the results
result.forEach((row) => print(row));
// {_id: 2, name: Mary, age: 32}
}
Be sure to use data binding using ? string replacements. This will guard against SQL injection attacks.
Notes
You will have to import the DatabaseHelper class and sqflite if you are in another file (like main.dart).
The SQFlite plugin uses a Map<String, dynamic> to map the column names to the data in each row.
Supplemental code
For your copy-and-paste convenience, here is the layout code for main.dart:
import 'package:flutter/material.dart';
// I called my project 'flutter_database_operations'. You can update for yours.
import 'package:flutter_database_operations/database_helper.dart';
import 'package:sqflite/sqflite.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQFlite Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqflite'),
),
body: RaisedButton(
child: Text('query', style: TextStyle(fontSize: 20),),
onPressed: () {_query();},
),
);
}
_query() async {
// get a reference to the database
Database db = await DatabaseHelper.instance.database;
// get all rows
List<Map> result = await db.query(DatabaseHelper.table);
// get single row
//List<Map> result = await db.query(DatabaseHelper.table,
// columns: [DatabaseHelper.columnId, DatabaseHelper.columnName, DatabaseHelper.columnAge],
// where: '${DatabaseHelper.columnId} = ?',
// whereArgs: [1]);
// raw query
//List<Map> result = await db.rawQuery('SELECT * FROM my_table WHERE name=?', ['Mary']);
// get each row in the result list and print it
result.forEach((row) => print(row));
}
}
Going on
This post is a development from my previous post: Simple SQFlite database example in Flutter. See that post for other SQL operations and advice.

Related

Fluttet sqfLite delete

I am trying to build a note app. I watch some tutorials on sqflite in order to save data after the app is terminated. I have managed to save the data but i cant delete data.
Here is the DBelper class i have created:
import 'package:sqflite/sqflite.dart' as sql;
import 'package:path/path.dart' as path;
import 'package:sqflite/sqlite_api.dart';
class DBHelper {
static Future<Database> databse() async {
final dbPath = await sql.getDatabasesPath();
return sql.openDatabase(path.join(dbPath, 'notes.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE user_notes(id TEXT PRIMARY KEY, title TEXT, text TEXT)');
}, version: 1);
}
static Future<void> insert(String table, Map<String, Object> data) async {
final db = await DBHelper.databse();
db.insert(
table,
data,
);
}
static Future<List<Map<String, dynamic>>> getData(String table) async {
final db = await DBHelper.databse();
return db.query(table);
}
}
And this is how i save the data :
onPressed: () {
Provider.of<Notes>(context, listen: false)
.addNote(newNoteTitle, newNoteText);
DBHelper.insert('user_notes',
{'title': newNoteTitle, 'text': newNoteText});
Navigator.pop(context);
},
I have been trying to create the DBHelper.deleteNote but even if i manage to write some code without errors nothing gets deleted. Thanks in advance for any help
If you simply want to delete a note then you can use its id, something like this:
deleteNote(String id) async {
final db = await DBHelper.databse();
db.delete('user_notes', where: 'id = ?', whereArgs: [id]);
}

Flutter: Why are the methods inside this function not accessing the database variable?

I have a file named database_services.dart. This file have a class with various methods. The first method open the database and store the reference, while the other methods make inclusions, updates and deletes on this database. The problem is that the other methods are unable to see the database created by the first method. What am I missing?
Here is the code:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'counter.dart';
class DatabaseServices {
initDatabase() async {
// Open the database and store the reference.
final Future<Database> database = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
}
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await database;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await database;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
The database variable is only stored in the initDatabase method instead of on the DatabaseServices class, which means that it will only be accessible from the initDatabase method.
The code sample below shows how you could store the database as a property on the DatabaseServices class, which would allow it to be used by all the methods inside that class.
class DatabaseServices {
Future<Database> _db;
Future<void> initDatabase() async {
// Open the database and store the reference.
_db = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
}
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final db = await _db;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final db = await _db;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await _db;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await _db;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
You can find more information about opening a database here.

How to change the datetime format in sqflite database so that it displays only date?

I'm hoping you can help me figure this out! I've been tearing my hair out for days with this, so any help would be much appreciated.
I want to add a "date" column to the sqflite table in my current project. Here's the code for the database_helper.dart:
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseHelper {
static final _databaseName = "MyDatabase.db";
static final _databaseVersion = 1;
static final table = 'my_table';
static final columnId = '_id';
static final columnName = 'name';
static final columnAge = 'age';
static final columnColour = 'colour';
static final columnDateTime = 'datetime';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY,
$columnName TEXT NOT NULL,
$columnAge INTEGER NOT NULL,
$columnColour TEXT NOT NULL,
$columnDateTime TEXT NOT NULL
)
''');
}
// Helper methods
// Inserts a row in the database where each key in the Map is a column name
// and the value is the column value. The return value is the id of the
// inserted row.
Future<int> insert(Map<String, dynamic> row) async {
Database db = await instance.database;
return await db.insert(table, row);
}
// All of the rows are returned as a list of maps, where each map is
// a key-value list of columns.
Future<List<Map<String, dynamic>>> queryAllRows() async {
Database db = await instance.database;
return await db.query(table);
}
// All of the methods (insert, query, update, delete) can also be done using
// raw SQL commands. This method uses a raw query to give the row count.
Future<int> queryRowCount() async {
Database db = await instance.database;
return Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM $table'));
}
// We are assuming here that the id column in the map is set. The other
// column values will be used to update the row.
Future<int> update(Map<String, dynamic> row) async {
Database db = await instance.database;
int id = row[columnId];
return await db.update(table, row, where: '$columnId = ?', whereArgs: [id]);
}
// Deletes the row specified by the id. The number of affected rows is
// returned. This should be 1 as long as the row exists.
Future<int> delete(int id) async {
Database db = await instance.database;
return await db.delete(table, where: '$columnId = ?', whereArgs: [id]);
}
}
And here's the code for the main.dart:
import 'package:flutter/material.dart';
// change `flutter_database` to whatever your project name is
import 'package:flutter_database/database_helper.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQFlite Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
// reference to our single class that manages the database
final dbHelper = DatabaseHelper.instance;
// homepage layout
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqflite'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('insert', style: TextStyle(fontSize: 20),),
onPressed: () {_insert();},
),
RaisedButton(
child: Text('query', style: TextStyle(fontSize: 20),),
onPressed: () {_query();},
),
],
),
),
);
}
// Button onPressed methods
void _insert() async {
// row to insert
Map<String, dynamic> row = {
DatabaseHelper.columnName : 'Bob',
DatabaseHelper.columnAge : 23,
DatabaseHelper.columnColour : 'Red',
DatabaseHelper.columnDateTime : DateTime.now().toIso8601String(),
};
final id = await dbHelper.insert(row);
print('inserted row id: $id');
}
void _query() async {
final allRows = await dbHelper.queryAllRows();
print('query all rows:');
allRows.forEach((row) => print(row));
}
}
The goal is to simply to put today's date into the record - I don't need to time part of it, just the date. If you run the above code, the datetime gets displayed in the console if you click the 'query' button, but I want it to only store today's date, not the time aswell.
I've had a look at the docs for the intl package, as I've read that is a good way to format date, but I can't figure out how to implement it into my code in this instance.
Any ideas would be greatly appreciated!
Thank you very much,
Jason
Yep, you can utilize intl's DateFormatter for this:
import 'package:intl/intl.dart';
import 'package:test/test.dart';
void main() {
final DateFormat dateOnlyFormat = DateFormat('yyyy.MM.dd');
test(
'assert that dateOnlyFormat returns only date part of a DateTime '
'instance', () {
final DateTime givenDateTime = DateTime(1995, 1, 1, 5, 5, 5, 5, 5);
expect(dateOnlyFormat.format(givenDateTime), "1995.01.01");
});
test('assert that dateOnlyFormat parses the date as expected', () {
const String givenDateString = "1995.01.01";
expect(dateOnlyFormat.parse(givenDateString), DateTime(1995, 1, 1));
});
}
Refer to the DateFormat's class documentation if you need to customize the date format in some other way.

Using SQLite in main.dart from seperate file?

I've followed this easy example from the flutter documentation. However, this is wrote in a separate file (db_test.db). I'm aiming to convert data into a ListView at some point. So, how would I use CRUD operations like retrieving data in my main.dart? I could add this to my main.dart file but I'd like to keep it clean and separate.
Official Flutter tutorial
My db.dart file
void main () async {
final database = openDatabase(
join(await getDatabasesPath(), 'to_do.db'),
onCreate: (db, version) {
return db.execute("CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT, created TEXT, INTEGER is_complete)");
},
version: 1,
);
Future<void> insertTask (Task task) async {
final Database db = await database;
await db.insert(
'tasks',
task.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace
);
}
Future<List<Task>> tasks () async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query('tasks');
return List.generate(maps.length, (i) {
return Task(
id: maps[i]['id'],
title: maps[i]['title'],
created: maps[i]['created'],
isComplete: maps[i]['is_complete']
);
});
}
Future<void> updateTask(Task task) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'tasks',
task.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [task.id],
);
}
Future<void> deleteTask(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'tasks',
// Use a `where` clause to delete a specific dog.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
}
You can create a new file containing Class with static members to help. Static members ensure that only one instance of database is created in your whole app.
class DatabaseHelper {
static Database _database;
///Returns db instance if already opened
///else call the initDatabase
static Future<Database> getDBConnector() async {
if (_database != null) {
return _database;
}
return await _initDatabase();
}
///Open DB Connection, returns a Database instance.
///
static Future<Database> _initDatabase() async {
_database = await openDatabase(
join(await getDatabasesPath(), "my_path.db"),
onCreate: (db, version) async {
//on create
},
version: 1,
);
return _database;
}
//put your CRUD in static function
static Future<void> insertTask (Task task) async {
final Database db = await getDBConnector();
await db.insert(
'tasks',
task.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace
);
}
//the same with edit, delete
}
Then in your other file (like main.dart) you can just call it like this:
import "./databaseHelper.dart";
void caller() async{
//create task
//insert
await DatabaseHelper.insertTask(task);
}
Make sure the caller is asynchronous.

Flutter Firestore - Find `Document Snapshot` id

I hava simple app with list of products. Products are stored on Firebase Firestore. I want to download list of products and give user possibility to update some data.
So I prepare list with products:
Widget _buildProductsList() {
return new StreamBuilder(
stream: Firestore.instance
.collection('products')
.snapshots,
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Loading...");
return new ListView(
children: snapshot.data.documents.map((document) {
Product product = new Product(document.data);
_ProductCell cell = new _ProductCell();
cell.product = product;
return cell;
}).toList(),
);
},
);
}
I serialize Document Snapshot to my Product object:
class Product {
static const NAME_KEY = 'name';
static const DESC_KEY = 'desc';
static const PHOTO_URL_KEY = 'photoUrl';
static const AUTHOR_ID_KEY = 'authorId';
static const CREATED_AT_KEY = 'createdAt';
String name;
String description;
String photoUrl;
String authorId;
int createdAt;
Product(Map<String, dynamic> json) {
name = json[NAME_KEY];
description = json[DESC_KEY];
photoUrl = json[PHOTO_URL_KEY];
authorId = json[AUTHOR_ID_KEY];
createdAt = json[createdAt];
}
}
Now, when user make some interact with ProductCell I want to update Product Document in Firestore which is linked with but I don't have an ID so it is impossible to create proper Firestore reference.
How to achieve this?
Since the issue #12471 is resolved, you can get the document Id from the document object.
print(document.documentID)
the syntax for this has since been updated you can now get it from
print(document.id);
https://pub.dev/documentation/firebase/latest/firebase_firestore/DocumentSnapshot/id.html
I have made an issue about this yesterday as I encountered the same bug. You can track it here: https://github.com/flutter/flutter/issues/12471
Currently the FireStore plugin has some more issues.
You can see all issues with Firebase plugins by filtering on the plugin:
https://github.com/flutter/flutter/labels/plugin%3A%20firebase

Resources