Creating a DatabaseService in Dart/Flutter - flutter

My team and I are currently in the process of trying to port an older Android project over to Flutter and are having issues with the Dart null checker in our DatabaseService class.
Using a number of resources we found on Google, the class currently is defined as:
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:agineek/models/player.dart';
class DatabaseService {
static const _dbName = 'agineek.db';
static final DatabaseService _instance = DatabaseService._internal();
static Database _db;
factory DatabaseService() => _instance;
final Logger log = Logger('Database');
DatabaseService._internal();
Future<Database?> get db async {
if(_db != null) {
return _db;
}
// ignore: join_return_with_assignment
_db = await _openDatabase();
return _db;
}
/// Open a connection to the [Database] and perform
/// database creation transactions.
Future<Database> _openDatabase() async {
final dbPath = await getDatabasesPath();
final database = await openDatabase(
join(dbPath, _dbName),
version: 1,
onCreate: (Database db, int version) async {
await db.execute('''
create table ${Player.tableName} (
${Player.columnPlayerId} integer primary key autoincrement,
${Player.columnPlayerHitColor} text not null
);
''');
}
);
return database;
}
/// Create a [Player] entry in the database.
/// Conflicts are resolved as a replace action.
Future<Player> createPlayer(Player player) async {
final database = _db;
player.id = await database.insert(
Player.tableName,
player.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace
);
return player;
}
... a bunch of other boilerplate transactions
}
However, we're stuck with trying to figure out why the static _db is being flagged as a dart(not_initialized_non_nullable_variable) error.
Any assistance would be appreciated.

Try adding the late keyword as such:
static late Database _db;
But that would require some extra changes I guess. So an alternative would be to make it nullable:
static Database? _db;

Related

how to initialize SQLite Database variable in my flutter app

I'm a beginner in flutter, i want to use SQlite database using sqflite package in my Flutter App, when I declare the _database variable with this syntax static Database _database; i got a compilation error saying that i must initialize _database, and when i use this syntax static Database? _database;, i have a compilation error under return _database saying A value of type 'Database?' can't be returned from the function 'database' because it has a return type of 'Future<Database>'. and when i put static late Database _database; i have an error in execution saying Error: LateInitializationError: Field '_database' has not been initialized.
my code is
class AnnonceDataBase {
AnnonceDataBase._();
static final AnnonceDataBase instance = AnnonceDataBase._();
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await initDB();
return _database;
}
}
I have modified it a bit but it works for you using a singleton to maintain the state of the database
class AnnonceDataBase {
AnnonceDataBase._();
static final AnnonceDataBase _instance = AnnonceDataBase._();
static Database? _database;
factory AnnonceDataBase() => _instance;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await initDB();
return _database!;
}
Future<Database> initDB() async {
String table1 = "CREATE TABLE IF NOT EXISTS table1(id INTEGER PRIMARY KEY,key TEXT NOT NULL UNIQUE,name TEXT,phone NUMERIC)";
String table2 = "CREATE TABLE IF NOT EXISTS table2(id INTEGER PRIMARY KEY,key TEXT NOT NULL UNIQUE,name TEXT,phone NUMERIC)";
String path = await getDatabasesPath();
// Open the database and save the reference.
final Future<Database> database = openDatabase(
// Set the path to the database. Note: Using the `join` function of the
// plugin `path` is best practice to ensure the path is correct
// built for each platform.
p.join(path, 'admin.db'),
// When the database is first created, create a table to store data
onCreate: (db, version) {
db.execute(table1);
return db.execute(table2);
},
// Set the version. This runs the onCreate function and provides a
// path to perform updates and downgrades on the database.
version: 1,
);
return database;
}
}
You can use it like this
#override
void initState() {
AnnonceDataBase._instance.database; //Initialize when loading the application or in the singleton
super.initState();
}
Create a record
Future<void> addData() async {
Database database = AnnonceDataBase._database!;
database.transaction((txn) async {
await txn.rawInsert('INSERT INTO table1(id,key,name,phone) VALUES(?,?,?,?)', ["1", DateTime.now().toString(), "name", "+5555555"]);
});
}
Read all records
Future<void> readData() async {
Database database = AnnonceDataBase._database!;
List<Map> data = await database.rawQuery("SELECT * FROM table1");
debugPrint("data: $data");
}

how to avoid safety null

in this class while declaring the constructor Repository i get this error 'Non-nullable instance field '_database' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'' i added late but it doesn't work the same error while declaring the static Database _database .
this is the class Repository
import 'package:sqflite/sqflite.dart';
import 'package:todo/repositories/database_connection.dart';
class Repository {
DatabaseConnection _databaseConnection;
Repository() {
_databaseConnection = DatabaseConnection();
}
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _databaseConnection.setDatabase();
return _database;
}
insertData(table, data) async {
var connection = await database;
return await connection.insert(table,data)
}
and this is the DatabaseConnection
class DatabaseConnection {
setDatabase() async {
var directory = await getApplicationDocumentsDirectory();
var path = join(directory.path, 'db_todo_sqflite');
var database =
await openDatabase(path, version: 1, onCreate: _onCreateDatabase);
return database;
}
_onCreateDatabase(Database database, int version) async {
await database.execute(
"CREATE TABLE categories(id INTEGER KEY , name TEXT , descrption TEXT");
}
}
Your database itself is nullable, so you add a ?
static Database? _database;
That makes the _database nullable, but in your getter you are returning a non nullable instance of Database.
The nullable instance would be Database?
Future<Database?> get database async {
...
}
If you are sure that the _database is initialized, and can't be null at that point you return the value, and tell dart that this value is never null.
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _databaseConnection.setDatabase();
return _database!;
}
Note the "!" at the return _database!
See also https://dart.dev/null-safety/understanding-null-safety
And https://dart.dev/tools/diagnostic-messages#unchecked_use_of_nullable_value
You are getting an error because
static Database _database is no nullable
Change the code to
static Database? _database
thank all for the response
this how i correct the code
import 'package:sqflite/sqflite.dart';
import 'package:todo/repositories/database_connection.dart';
class Repository {
DatabaseConnection? _databaseConnection;
Repository() {
_databaseConnection = DatabaseConnection();
}
static Database? _database;
Future<Database?> get database async {
if (_database != null) return _database;
_database = await _databaseConnection!.setDatabase();
return _database! ;
}
insertData(table, data) async {
var connection = await database;
return await connection!.insert(table, data);
}
}
You also can use the late modifier:
late DatabaseConnection _databaseConnection;
Just make sure you set this variable before you try to use it.

Sqlflite - Null check operator used on a null value

I received - Null check operator used on a null value this error while trying to test my sqflite application. Even thought the code is working in emulator. but it didnt passed the test.
Kindly help.
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class DbHelper {
static final DbHelper instance = DbHelper._instance();
DbHelper._instance();
static Database? _db;
Future<Database> get db async {
if (_db != null) return db;
_db = await _initDb();
return _db!;
}
Future<Database> _initDb() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + '/account_keeper.db';
final accountManagerDb = await openDatabase(
path,
version: 1,
onCreate: _createDb,
);
return accountManagerDb;
}
void _createDb(Database db, int version) async {
// Table 1- BusinessProfile Profile
print('creating db');
print('--create database--');
const String businessProfileTable = 'businessProfile_table';
String businessId = 'id';
String businessName = 'businessName';
await db.execute(
'''
CREATE TABLE
$businessProfileTable(
$businessId INTEGER PRIMARY KEY AUTOINCREMENT,
$businessName TEXTT )''',
);
}
}

Flutter does not run Async function

I am trying to get an async function _read() to run and the function does not pass the line:
Reading reading = await helper.queryReading(rowId); in this function:
_read() async {
DatabaseHelper helper = DatabaseHelper.instance;
int rowId = 1;
//lines above here executes
Reading reading = await helper.queryReading(rowId); //this is the line it stops on
// nothing below here is executed
if (reading == null) {
print('read row $rowId: empty');
} else {
print('read row $rowId: ${reading.reading}');
}
}
It is being called from the following function
class Profile {
Widget getScreen(){
print("Attempting to read db");
_read();
return Scaffold( ...)
Here is my helper class:
import 'dart:ffi';
import 'dart:io';
import 'package:ema/Readings.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
//table structure
final String tableName = 'Readings';
final String databasecolumnId = '_id';
final String databaseReading = 'Reading';
final String databaseDate = 'Time';
class DatabaseHelper {
//This is the name of the database file on disk
static final _databaseName = "readings.db";
//handles versioning for databases
static final _databaseVersion = 1;
//makes a singleton classs
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
//allows only one item to access the database
static Database _database;
Future<Database> get database async {
_database = await _initDatabase();
return database;
}
//this opens and creates the database
_initDatabase() async {
// The path_provider plugin gets the right directory for Android or iOS.
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
// Open the database. Can also add an onUpdate callback parameter.
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
//Creates the database
Future _onCreate(Database db, int version) async {
await db.execute(
'''
CREATE TABLE $tableName (
$databasecolumnId INTEGER PRIMARY KEY,
$databaseReading REAL NOT NULL,
$databaseDate INTERGER NOT NULL
)
'''
);
}
Future<int> insertReading(Reading reading) async {
Database db = await database;
int id = await db.insert(tableName, reading.toMap());
return id;
}
//gets reading
Future<Reading> queryReading(int id) async {
print("queryReading"); //gets here
Database db = await database;
print("Getting Db"); // not actually getting here
List<Map> maps = await db.query(tableName,
columns: [databasecolumnId, databaseReading, databaseDate],
where: '$databasecolumnId = ?',
whereArgs: [id]);
if (maps.length > 0) {
return Reading.fromMap(maps.first);
}
print('maps length : ${maps.length}');
return null;
}
}
Here is my Readings class:
class Reading {
int id;
double reading;
DateTime date;
//constructor
Reading({this.id, this.reading, this.date});
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
databaseReading: reading,
databaseDate: date.millisecondsSinceEpoch,
};
if (id != null) {
map[databasecolumnId] = id;
}
return map;
}
//extracts a node object from the map obect
Reading.fromMap(Map<String, dynamic> map) {
id = map[databasecolumnId];
reading = map[databaseReading];
date = new DateTime.fromMillisecondsSinceEpoch(map [databaseDate]);
}
}
Turns out there was a deadlock in getting the database. By putting a lock on it it worked.
Here is the code to resolve it:
///declreation of the database
Database _database;
///Gets the database ensuring that there are no locks currently on the database
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}

Flutter: How to best instantiate SQFlite db object when class is created

I'm learning Flutter and am on to my second program. This program uses SQFlite, and I copied most of the db handling from another program which appears to use a common pattern. I have made some modifications to it.
What I don't like, is having to evaluate the db every time to determine if it needs to be created as in "Database db = await this.db;". I would prefer it if it was simply created when the class is created, and then just used as instantiated. However, I'm unfamiliar with "._internal", and unfamiliar with "factory", so I'm unsure of the best way to achieve that aim.
Would appreciate if someone could show me how to best achieve that. IE. Remove the need for "Database db = await this.db;", and just reference the db.
Extract of relevant code for DbHelper is as follows:
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'noteRec.dart';
class DbHelper {
static final DbHelper _dbHelper = DbHelper._internal();
static const String sTblNotes = "Notes";
static const String sColId = "Id";
static const String sColTitle = "Title";
static const String sColDetail = "Detail";
DbHelper._internal();
factory DbHelper() {
return _dbHelper;
}
static Database _db;
Future<Database> get db async {
return _db != null ? _db : await initializeDb();
}
Future<Database> initializeDb() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + "/Notes.db";
_db = await openDatabase(path, version: 1, onCreate: _createDb);
return _db;
}
void _createDb(Database db, int newVersion) async {
await db.execute(
"CREATE TABLE $sTblNotes($sColId INTEGER PRIMARY KEY, $sColTitle TEXT, " +
"$sColDetail TEXT)");
}
Future<List> getNoteRecs() async {
Database db = await this.db;
var result =
await db.rawQuery("SELECT * FROM $sTblNotes ORDER BY $sColTitle ASC");
return result;
}
The following appears to do what I want to achieve.
dbHelper.dart
import 'dart:async';
import 'dart:io';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'noteRec.dart';
class DbHelper {
static final DbHelper _dbHelper = DbHelper._internal();
static const String sTblNotes = "Notes";
static const String sColId = "id";
static const String sColTitle = "title";
static const String sColDetail = "detail";
static Database _db;
DbHelper._internal();
factory DbHelper() {
return _dbHelper;
}
Future<bool> openDb() async {
if (_db == null) {
Directory dir = await getApplicationDocumentsDirectory();
_db = await openDatabase("${dir.path}/Notes.db",
version: 1, onCreate: _createDb);
}
return (_db != null);
}
void _createDb(Database db, int newVersion) async {
await db.execute(
"CREATE TABLE $sTblNotes($sColId INTEGER PRIMARY KEY, $sColTitle TEXT, " +
"$sColDetail TEXT)");
}
Future<List> getNoteRecs() async {
return await _db
.rawQuery("SELECT * FROM $sTblNotes ORDER BY $sColTitle ASC");
}
main.dart
import 'package:flutter/material.dart';
import 'dbHelper.dart';
import 'NotesList.dart';
DbHelper _dbHelper = DbHelper();
void main() async {
await _dbHelper.openDb();
runApp(MaterialApp(home: NotesList()));
}