Here I want to add bookmark functionality in my flutter news application. I get data from APIs. I display that data below.
This Image shows you how I get data from APIs
I am using this snippet for saving data with SQflite which I display below. I save this file with name bookmark_db_provider.dart.
import 'dart:io';
import 'home_screen_data.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class DBProvider {
static Database _database;
static final DBProvider db = DBProvider._();
DBProvider._();
Future<Database> get database async {
if (_database != null) _database = await initDB();
return _database;
}
initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'ProductData.db');
return await openDatabase(path, version: 1, onOpen: (db) {},
onCreate: (Database db, int version) async {
await db.execute('CREATE TABLE ProductData('
'id INTEGER PRIMARY KEY,' //id
'categoryName Text,' //headline
'publisherName Text,' //description
'isAvailable Text,' //content
'categoryImgUrl Text' //image
')');
});
}
createProductData(ProductData productData) async {
await deleteAllProductData();
final db = await database;
final res = await db.insert('ProductData', productData.toJson());
return res;
}
Future<int> deleteAllProductData() async {
final db = await database;
final res = await db.delete('DELETE FROM ProductData');
return res;
}
Future<List<ProductData>> getProductDataList() async {
final db = await database;
final res = await db.rawQuery("SELECT * FROM ProductData");
List<ProductData> list = res.isNotEmpty ? res.map((x) => ProductData.fromJson(x)).toList() : [];
return list;
}
}
So, I want to how to save data and get data this with SQflite database in flutter. How I accomplish this?
This is an old question and hopes you have gotten an answer to it. However, a lot of flutter dev using sqflite struggles with handling data in a nested array format of the type mentioned e.g
``` {
"id": 1,
"name": "xyz",
"images": [
{
"imageId": 1,
"image": "image1"
},
{
"imageId": 2,
"image": "image2"
}
]
}
```
Since json handling is not part of sqflite at the moment, it is suggested to either;
a., save inner array as a string/text field in the 'image' column of table 'data' like
**
"[{"imageId": 1, "image": 'image1'}, {"imageId": 2, "image":
'image2'},}"
** , no guaranty.
or, b., flatten out the inner array so as to have only **
data[id, name, image1, image2, image3,...].
** this approach may be possible in a simple array as given but in a complex system. flattening out may be really cumbersome.
my suggestion, create two tables, one for data, and another for images. let each row of images table have reference or relationship with corresponding data table row. Your data class and images class will be something like,
```
class Data {
int dataId;
String name;
List<Image> images;
data({this.id, this.images, this.name});
...
```
and
```
class Image {
int imageId;
int dataId;
String image;
Image({this.imageId, this.dataId, this.image});
...
}
```
Your sqflite table data will have only two fields 'dataId' and 'name' and the image table must include 'dataId' as the relationship between the two tables.
to save data, you can use transactions like
```
void saveData(Data data, Map<String, Object> map) async {
await db.execute(""" INSERT INTO "data" (name) values (?) """, [data.name]);
// retrieve dataId of the new row inserted. last_inserted_rowid can also be used if the database does not contain several tables that may have been updated or saved before completing the transaction.
int dataId;
List<Map> x = await db.query('data', columns: ['dataId'], where: 'name = ?', whereArgs: [data.name]);
dataId = x[x.length - 1]['dataId'];
db.transaction((txn) async {
var batch = txn.batch();
data.images
.map((e) => {
batch.rawInsert(
""" INSERT INTO "images" (dataId,image,) values (?,?,?,? ) """, [dataId, e.image])
}).toList();
});
}
```
to retrieve data and images, you can try something like
```
Data _data = new Data();
Future<void> fetchData() async {
if (db != null) {
// do not execute if db is not instantiate
//retrieve all data from data table and store in instantiated, also instantiate images array as empty list
final dataList = await db.query(data);
_data = (dataList as List)
.map(
(data) => Data(
dataId: data['dataId'],
name: data['name'],
images: data['images'] != null ? data['images'] : []
)) .toList();
_data.forEach((data) async {if (data.images != null) {
List<Image> _images = [];
var imageResults = await db.query(images,where: 'dataId =?', whereArgs: [data.dataId]);
_images = (imageResults as List).map((e) => Image(
imageId: e['imageId'],
dataId: e['dataId'],
image: e['image']
)).toList();
_data.images.addAll(_images);
} });
}
}
```
with that approach, you should be able to handle nested array in flutter and sqflite
I might be unclear with your question, but according to what I understood,
You need to call the method of this provider with data that you want:
DBProvider.init() // Use this only one which when the application is instaled
After that, you can call these methods from anywhere to put and get data
DBProvider.createProductData(productData) //insert data
Get data
DBProvider.getProductDataList() //get data
Related
I'm the junior flutter developer, and I would like to seek your support.
Currently, I would need to update data in a local JSON file in Flutter as below.
{"title":"ករណី ពិនិត្យផ្ទៃពោះ",
"lists":[
{
"id":"127",
"faci_code":"20018",
"y_txt":"2022",
"m_txt":"1",
"ind_id":"1",
"qty":"100",
"rec_by":"od123456",
"rec_date":"2022-06-27 13:50:31",
"lock_txt":"0",
"sec_id":"1",
"ind_num":"1",
"ind_eng":"# of ANC 1",
"ind_kh":"ចំនួនស្រ្ដីបានពិនិត្យផ្ទៃពោះលើកទី១ទាំងអស់",
"HFAC_NAME":"Boeng Pram",
"HFAC_NAMEKh":"បឺងប្រាំ",
"OD_CODE":"201",
"OD_NAME":"Thma Koul",
"OD_NAME_KH":"ថ្មគោល",
"PRO_CODE":"2",
"PROVINCE":"Battambang",
"PROVINCE_KH":"បាត់ដំបង"
}]}
I have searched and tried multiple solutions, but I couldn't resolve this issue yet.
I hope someone can provide a solution in advance.
Regard, thanks.
You can't right files in project folders.
Try local storage.
I used path_provider package in this example for get application directory path to write json file on local storage.
1. Create data class
Note. this data class should match your stored json structure
class DataClass {
String name;
int age;
DataClass(this.name, this.age);
factory DataClass.fromJson(Map<String, dynamic> json) {
return DataClass(json['name'], json['age']);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'age': age,
};
}
}
2.create and update json file
DataClass? updateJson;
void createJson() async {
String data = await rootBundle.loadString('assets/data.json');
Directory appDocDir = await getApplicationDocumentsDirectory();
File jsonFile = File("${appDocDir.path}/data.json");
jsonFile.writeAsStringSync(data);
updateJson = DataClass.fromJson(jsonDecode(data));
setState(() {});
}
void updateJsonFile() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
File jsonFile = File("${appDocDir.path}/data.json");
//update
final jsonAsString = jsonFile.readAsStringSync();
updateJson = DataClass.fromJson(jsonDecode(jsonAsString));
updateJson?.name = "updated name";
updateJson?.age = _counter;
jsonFile.writeAsStringSync(jsonEncode(updateJson?.toJson()));
setState(() {});
}
could anyone help me with how to manage this problem? I am trying to code exactly likes the course's video, but I got this problem:
I'm getting (Non-nullable instance field 'id' must be initialized.
Non-nullable instance field 'miles' must be initialized.
Non-nullable instance field 'name' must be initialized. Non-nullable instance field '_database' must be initialized.)
. errors in my project.
My car class:
import './dbhelper.dart';
class Car {
int id;
String name;
int miles;
Car(this.id, this.name, this.miles);
**GET ERROR FROM Car.fromMap..**
Car.fromMap(Map<String, dynamic> map) {
** Non-nullable instance field 'id' must be initialized.
Non-nullable instance field 'miles' must be initialized.
Non-nullable instance field 'name' must be initialized.**
id = map['id'];
name = map['name'];
miles = map['miles'];
}
Map<String, dynamic> toMap() {
return {
DatabaseHelper.columnId: id,
DatabaseHelper.columnName: name,
DatabaseHelper.columnMiles: miles,
};
}
}
My dbHelper.dart:
import './car.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DatabaseHelper {
static final _databaseName = "cardb.db";
static final _databaseVersion = 1;
static final table = 'cars_table';
static final columnId = 'id';
static final columnName = 'name';
static final columnMiles = 'miles';
** GET ERROR FROM DatabaseHelper._provateConstructor(); AND _database;
Non-nullable instance field '_database' must be initialized.**
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
** GET ERROR Non-nullable instance field '_database' must be initialized.**
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 {
String path = join(await getDatabasesPath(), _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 AUTOINCREMENT,
$columnName TEXT NOT NULL,
$columnMiles INTEGER 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(Car car) async {
Database db = await instance.database;
return await db.insert(table, {'name': car.name, 'miles': car.miles});
}
// 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);
}
// Queries rows based on the argument received
Future<List<Map<String, dynamic>>> queryRows(name) async {
Database db = await instance.database;
return await db.query(table, where: "$columnName LIKE '%$name%'");
}
// 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(Car car) async {
Database db = await instance.database;
int id = car.toMap()['id'];
return await db.update(table, car.toMap(), 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]);
}
}
You have to put the null aware operator in the variables type for example:
class Car {
int? id; // <---- add a ? to int so it will be int?
String? name; // <-- same here
int? miles; // <--- and here
Car({this.id, this.name, this.miles});
Car.fromMap(Map<String, dynamic> map) {
id = map['id'];
name = map['name'];
miles = map['miles'];
}
Map<String, dynamic> toMap() {
return {
DatabaseHelper.columnId: id,
DatabaseHelper.columnName: name,
DatabaseHelper.columnMiles: miles,
};
}
}
Null safety as it is explain in the dart documentation:
When you opt into null safety, types in your code are non-nullable by
default, meaning that variables can’t contain null unless you say they
can. With null safety, your runtime null-dereference errors turn into
edit-time analysis errors.
More information in: https://dart.dev/null-safety
Maybe the tutorial you are taking was previous the null aware implementation and it does not apply the null aware operators, you can do the same at the beggining of the type declartion where the error is happening in other classes, you just have to add ? to the variable type at the beginning of the variable.
I have loaded json data in a list of objects. Whenever I try to insert data into a table my console says "Unhandled Exception: DatabaseException(no such table: Album (code 1 SQLITE_ERROR): , while compiling: INSERT OR REPLACE INTO Album...." . I know before inserting data I have to create database and table. I have done that in my code. But for some unknown reason the table doesn't get build. Here is my database helper class -
class DatabaseHelper {
static final _dbName = "myDatabase.db";
static final _dbVersion = 1;
static final _tableAlbum = "Album";
static final columnAlbum = '_fetched_album';
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database? _database;
Future<Database?> get database async {
if (_database == null) {
print("instance.db null");
_database ??= await _initiateDatabase();
return _database;
}else{
print("instance.db not null");
return _database; }
}
Future<Database> _initiateDatabase() async {
String directory = await getDatabasesPath();
String path = join(directory, _dbName);
return await openDatabase(
path,
version: _dbVersion,
onCreate:(Database db,int version) async {
await db.execute('''
CREATE TABLE $_tableAlbum (
$columnAlbum TEXT
)''');
},
);
}
Future<int?> insertAlbum(ModelAlbum list) async {
Database? db = await instance.database;
return await db?.insert(
_tableAlbum,list.toMap(),conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<int?> insertPhoto(ModelPhoto list) async {
Database? db = await instance.database;
return await db?.insert(
_tablePhoto,list.toMap(),conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<List<Map<String, dynamic>>> queryAlbum() async {
Database? db = await instance.database;
return await db!.query(_tableAlbum);
}
Future<List<Map<String, dynamic>>> queryPhoto() async {
Database? db = await instance.database;
return await db!.query(_tableAlbum);
}
Future<List<ModelAlbum>> retrieveAlbum() async {
final Database? db = await database;
final List<Map> maps = await db!.query(_tableAlbum);
return List.generate(maps.length, (i) {
return ModelAlbum(
id: maps[i]['id'],
title: maps[i]['title'],
userId: maps[i]['userId'],
);
});
}
Future<List<ModelPhoto>> retrievePhoto() async {
final Database? db = await database;
final List<Map> maps = await db!.query(_tablePhoto);
return List.generate(maps.length, (i) {
return ModelPhoto(
albumId: maps[i]['albumId'],
thumbnailUrl: maps[i]['thumbnailUrl'],
url: maps[i]['url'],
id: maps[i]['id'],
title: maps[i]['title'],
);
});
}
}
Whenever Database.instance.insert(object); gets called the database and table should be automatically created. But it doesn't.
You are referring to static fields from instance methods. this may a problem try removing static keyword from the fields as,
final _dbName = "myDatabase.db";
final _dbVersion = 1;
final _tableAlbum = "Album";
final columnAlbum = '_fetched_album';
This method is supposed to load a list of of items from my query. I copy my database (which is in my assets folder) to the flutter directory and when I try to read from the directory it gives a "No such table" error
`Future<<"List<<"Model>> model(String searchParam) async {
var dbDir = await getDatabasesPath();
var dbPath = join(dbDir, "app.db");
var exists = await databaseExists(dbPath);
if (!exists) {
// Should happen only the first time you launch your application
print("Creating new copy from asset");
// Create the writable database file from the bundled demo database file:
ByteData data = await rootBundle.load("assets/mydb.db");
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(dbPath).writeAsBytes(bytes);
}
print("Opening existing db");
var db = await openDatabase(dbPath);
print(db.isOpen);
// Get a reference to the database.
// final Database db = await database;
// Query the table for all The Hymns.
final List<Map<String, dynamic>> maps = await db.rawQuery("SELECT * FROM Test WHERE title LIKE '%$searchParam%' OR id LIKE '%$searchParam%'");
// final List<Map<String, dynamic>> maps = await db.query('hymns');
print(maps.length);
// Convert the List<Map<String, dynamic> into a List<Dog>.
return List.generate(maps.length, (i) {
return Model(
id: maps[i]['id'],
title: maps[i]['title'],
text: maps[i]['text'],
favorite: maps[i]['favorite'],
);
});
}
`
So Initially I created a table named "TABLE" in the database. I wanted to add another table. So I added another query to create a table. However I get an error saying "TABLE1 does not exist" when I run the app.
I do feel like there is a flaw in my code. I think the _onCreate() method will only be called the first time I run the app. So any code I add on _onCreate() method afterwards will not run. Any help will be appreciated.
class DBHelper {
static Database _db;
static const String DB_NAME = 'employeeDB';
static const String ID = 'id';
static const String NAME = 'name';
static const String TABLE = 'Employee';
static const String ID1 = 'id1';
static const String NAME1 = 'name1';
static const String TABLE1 = 'Employee1';
Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, DB_NAME);
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}
_onCreate(Database db, int version) async {
await db.execute("CREATE TABLE $TABLE ($ID INTEGER PRIMARY KEY,$NAME TEXT)");
await db.execute("CREATE TABLE $TABLE1 ($ID1 INTEGER PRIMARY KEY,$NAME1 TEXT)");
}
Future<Employee> save(Employee employee) async {
var dbClient = await db;
employee.id = await dbClient.insert(TABLE, employee.toMap());
return employee;
}
Future<Employee1> saveEmp1(Employee1 employee) async {
var dbClient = await db;
employee.id = await dbClient.insert(TABLE1, employee.toMap());
return employee;
}
Future<List<Employee>> getEmployees() async {
var dbClient = await db;
List<Map> maps = await dbClient.query(TABLE, columns: [ID, NAME]);
List<Employee> employees = [];
if (maps.length > 0) {
for (int i = 0; i < maps.length; i++) {
employees.add(Employee.fromMap(maps[i]));
}
}
return employees;
}
Future<List<Employee1>> getEmployees1() async {
var dbClient = await db;
List<Map> maps = await dbClient.query(TABLE1, columns: [ID1,
NAME1]);
List<Employee1> employees = [];
if (maps.length > 0) {
for (int i = 0; i < maps.length; i++) {
employees.add(Employee.fromMap(maps[i]));
}
}
return employees;
}
}
The first time run this app and initDb() in emulator, the db file employeeDB has created.
and it will not be created again
For only test app execution,
you can change
String DB_NAME = 'employeeDB'
to another name
String DB_NAME = 'employeeDB1'
or you can uninstall this app from Emulator first then run it again.
source code snippet of https://github.com/tekartik/sqflite/blob/master/sqflite/lib/sqlite_api.dart
/// Called when the database is created.
OnDatabaseCreateFn onCreate;
For schema migration, you can use OnUpgrade, detail reference https://efthymis.com/migrating-a-mobile-database-in-flutter-sqlite/
code snippet
await openDatabase(path,
version: 1,
onCreate: (Database db, int version) async {
await db.execute(initialSchema));
},
onUpgrade: (Database db, int oldVersion, int newVersion) async {
await db.execute(migrationScript));
});
You have to create migrations and run them against your existing database using the onUpgrade handler. Basically you need to check the existing database version and upgrade if the version is smaller than the migration number.
You can check out the detail steps/code tutorial here.
I needed to create more than just multiple tables; but multiple database. I have since developed a dart package; sqlite_at_runtime that stretches to as far as creating all these entities at run time.
Below is an example code that creates three tables with similar attributes.
await Sqlartime.tableCreate(['sample1','sample2','sample3'],['name TEXT','age INTEGER','temp REAL']);