Flutter : insert data to tables in sqflite at initialaztion database - flutter

I have created helper class for sqlite database and tried to insert data to tables in intialzing database as this :
class DbHelper {
static final DbHelper _instance = DbHelper.internal();
factory DbHelper() => _instance;
DbHelper.internal();
static Database _db;
//---------------- Create database method-------------------
Future<Database> createDatabase() async {
var teamsTable = SqliteData().teams;
var clubsTable = SqliteData().clubs;
if (_db != null) {
return _db;
}
String path = join(await getDatabasesPath(), 'clubsFantasy.db');
_db = await openDatabase(path, version: 1,
onCreate: (Database db, int v) async {
await db.execute('''
create table teams (club varchar(50) ,
price integer ,
surename varchar(3),
id integer,
league varchar(20))
''');
await db.execute(
'create table clubs (id integer ,club varchar(50) ,league_id integer,price integer ,surename varchar(3), league_name varchar(20),counter integer , selected varchar(5))');
teamsTable.map((e) => {db.insert('teams', e.toMap())}).toList();
clubsTable.map((e) => {db.insert('clubs', e.toMap())}).toList();
});
return _db;
}
//---------------- load data method---------------------
Future getTeamsData() async {
Database db = await createDatabase();
return db.query('teams');
}
Future getClubsData() async {
Database db = await createDatabase();
return db.query('clubs');
}
}
I call database at splash screen to create database and insert data to tables:
DbHelper helper;
#override
void initState() {
helper = DbHelper();
checkLogin();
helper.createDatabase();
super.initState();
}
now I want to display data and used it in provider ,I did this :
class TeamsProvider with ChangeNotifier {
DbHelper helper;
get clubs => helper.getClubsData();
get teams => helper.getTeamsData();
}
I called teams in future builder in screen :
Widget build(BuildContext context) {
return Consumer<TeamsProvider>(
builder: (context, teamsProv, child) {
return FutureBuilder(
future: teamsProv.teams,
builder: (context, snapshot) {
if (snapshot.hasData) {.....etc
I get error :
The method 'getTeamsData' was called on null. Receiver: null Tried calling: getTeamsData()

I am not sure but try declaring your DbHelper like this:
class DbHelper {
factory DbHelper() => DbHelper._internal()
DbHelper._internal();
DbHelper methods like:
static Future<Database> createDatabase() async {...
static Future getTeamsData() async {...
static Future getClubsData() async {...
And then:
class TeamsProvider with ChangeNotifier {
get clubs => DbHelper.getClubsData();
get teams => DbHelper.getTeamsData();
}

Related

Unhandled Exception: Null check operator used on a null value E/flutter (28057): #0 DBHelper._createDB

I created a class for database operations, when i trigger the function of insert to data base to print occurs with this error: Unhandled Exception: Null check operator used on a null value E/flutter (28057): #0 DBHelper._createDB, also there is no print occures once initialize an object of DBHelper. i think null-able _db object is the reason. what is the solution?
database class:
class DBHelper {
DBHelper._();
factory DBHelper() => instance;
static final DBHelper instance = DBHelper._();
Database? _db;
Future<Database> _createDB() async {
// If the app is opened just now
if (_db == null) return _db!;
// Open db too create a db
// or to take an instance of it
// Get the default databases location.
// and join the name of app db
String path = join(await getDatabasesPath(), 'todo.db');
return _db = await openDatabase(path, version: 1,
onCreate: (Database database, int version) {
database.execute(
"CREATE TABLE tasks(id integer autoincrement PRIMARY KEY,content varchar(150),status varchar(9),isFinished BOOLEAN) ");
print("\n TABLE executed \n");
}, onOpen: (_) {
print("\n opened \n ");
});
}
Future<int> insertNoteToDB(NoteDatabaseModel noteDatabaseModel) async {
Database _database = await _createDB();
return await _database.rawInsert(
'INSERT INTO tasks(content,status) values("${noteDatabaseModel.content}", "today")');
// return await _database!.insert('tasks', noteDatabaseModel.toJason());
}
}
the implementation of sql:
class TodayPage extends StatelessWidget {
const TodayPage();
#override
Widget build(BuildContext context) {
DBHelper dbHelper=DBHelper();
return MaterialButton(
onPressed: () async{
// DBHelper _db = DBHelper.instance;
NoteDatabaseModel note=NoteDatabaseModel(content: "hey",status: "today");
int x= await dbHelper.
insertNodeToDB(note);
print("\n print all Data ${ await dbHelper.
insertNodeToDB(note)}\n");
},
child: const Text("Click Me"),
),
),
],
);
}
}
What do you think this line does:
if (_db == null) return _db!;
If I would have to guess, I'd say it should be:
if (_db != null) return _db;

Singleton doesn't have unique instance Flutter

I'm trying to implement the singleton pattern with null safety in Flutter, to do a unique instance for my sqflite database, but after initialization in the splashScreen, when I try to access it in another Widget, it seems that it's not the same instance since the database is not initialized. I tried to add a random() int to check if it has the same value in the different widget, and the value change each times I request it.
My code is like this :
class DatabaseHandler {
final int random = new Random().nextInt(200);
static final DatabaseHandler instance = new DatabaseHandler._internal();
factory DatabaseHandler() {
return instance;
}
DatabaseHandler._internal();
late Database _database;
Database getDb() {
print(random);
return _database;
}
Future<void> initDB() async {
var path = await getDatabasesPath();
var dbPath = join(path, 'test.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
return db.execute(
"CREATE TABLE favorite_page(id TEXT PRIMARY KEY, isFavorite BOOL)",
);
});
this._database = dbConnection;
}
}
I got a "Not initialize exception" when I do getDb() even if I used initDB() in the splashScreen.
I call the singleton in two places :
The db is initialized here :
class _SplashPageState extends State<SplashPage> {
static const String route = "/splash";
void initializeFlutterFire() async {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
PushNotificationService pushNotif = PushNotificationService();
pushNotif.initialise();
}
#override
initState() {
super.initState();
WidgetsFlutterBinding.ensureInitialized();
initializeFlutterFire();
DatabaseHandler().initDB();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Lottie.asset('assets/lotties/loading-screen.json',
animate: true, repeat: true)));
}
}
and used here :
class PagesDb {
Future<FavoritePage> insert(FavoritePage page) async {
var db = DatabaseHandler().getDb();
await db.insert(tablePage, page.toMap());
return page;
}
}
But I tried to just use DatabaseHandler().getDb() in different widgets, they got the same random value, but not the same as in the splash screen

Flutter's sqflite queries

I am working with an already existing database dictionary.db that has a table words and has 4 columns id, englishWord, germanWord, isFavorite.
Using Sqflite I am trying to return a list of German words based on an input in English pretty much the userflow works like this: user input an English word (example: "Ability") and I return a list of synonyms to that word in German.
My problem is I don't know how to do it the method I am trying returns the full list instead of only the intended search results
This is my Entity:
class Word {
final String id;
final String eng;
final String ger;
final String isFav;
Word({this.id, this.eng, this.ger, this.isFav});
Map<String, dynamic> toMap() {
return {
'wordId': id,
'englishWord': eng,
'germanWord': ger,
'isFavorite': isFav,
};
}
factory Word.fromMap(Map<String, dynamic> json) => new Word(
id: json['wordId'],
eng: json['englishWord'],
ger: json['germanWord'],
isFav: json['isFavorite']
);
}
And this is my database helper class:
class DatabaseHelper{
DatabaseHelper._();
static final DatabaseHelper databaseHelper = DatabaseHelper._();
Database _database;
static const String DB_NAME = "dict.db";
static const String TABLE = "words";
static const String ID = "wordId";
static const String ENGLISH_WORD = "englishWord";
static const String GERMAN_WORD = "germanWord";
static const String IS_FAV = "isFavorite";
Future<Database> get database async {
if (_database != null) return _database;
_database = await getDatabaseInstance();
return _database;
}
Future<Database> getDatabaseInstance() async {
io.Directory directory = await getApplicationDocumentsDirectory();
String path = join(directory.path, DB_NAME);
return await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE IF NOT EXISTS $TABLE ("
"$ID TEXT,"
"$ENGLISH_WORD TEXT,"
"$GERMAN_WORD TEXT,"
"$IS_FAV TEXT"
")");
});
}
//This is where I get the search result list
//I am stuck here I don't know where to go from here, I only get the full list
Future<List<Word>> searchEnglishResults(String userSearch) async{
final db = await database;
var response = await db.query("Word");
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
I assumed the database is like below:
|id | englidhWord | germanWord | isFavorite |
|-------|-------------|-------------------|------------|
| 'id0' | 'bye' | 'Tschüss' | 'false' |
| 'id1' | 'bye' | 'Auf Wiedersehen' | 'true' |
so if the user searches for "bye", she/he should receive [ 'Tschüss', 'Auf Wiedersehen'](the Word model not only the German words).
For that, query has some options like where and whereArgs that you can use to search for a specific row on the database.
Here we want to search the database for rows that their englidhWord field has a value of bye.
Here is the edited version of your DB:
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sudoku/src/word.dart';
class DatabaseHelper {
DatabaseHelper._();
static final DatabaseHelper databaseHelper = DatabaseHelper._();
Database _database;
String DB_NAME = "dict.db";
static const String TABLE = "words";
static const String ID = "wordId";
static const String ENGLISH_WORD = "englishWord";
static const String GERMAN_WORD = "germanWord";
static const String IS_FAV = "isFavorite";
Future<Database> get database async {
if (_database != null) return _database;
_database = await getDatabaseInstance();
return _database;
}
Future<Database> getDatabaseInstance() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = join(directory.path, DB_NAME);
return await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE IF NOT EXISTS $TABLE ("
"$ID TEXT,"
"$ENGLISH_WORD TEXT,"
"$GERMAN_WORD TEXT,"
"$IS_FAV TEXT"
")");
});
}
//add new words
Future<int> add(Word word) async {
final db = await database;
var response = await db.insert(TABLE, word.toMap());
return response;
}
//This is where I get the search result list
//I am stuck here I don't know where to go from here, I only get the full list
Future<List<Word>> searchEnglishResults(String userSearch) async {
final db = await database;
var response = await db
.query(TABLE, where: '$ENGLISH_WORD = ?', whereArgs: [userSearch]);
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
}
And I show the result with listview on a page named MyApp
import 'package:flutter/material.dart';
import 'package:sudoku/src/db.dart';
import 'package:sudoku/src/word.dart';
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
DatabaseHelper db;
#override
void initState() {
db = DatabaseHelper.databaseHelper;
add();
super.initState();
}
add() async {
// await db.add(Word(eng: 'hi', ger: 'Hallo', id: 'id2', isFav: 'true'));
// await db.add(Word(eng: 'bye', ger: 'Tschüss', id: 'id0', isFav: 'fasle'));
// await db.add(
// Word(eng: 'bye', ger: 'Auf Wiedersehen', id: 'id0', isFav: 'true'));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder<List<Word>>(
future: db.searchEnglishResults('bye'),
builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot) {
if (snapshot.hasData)
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].ger),
subtitle: Text(snapshot.data[index].eng),
trailing: Text(snapshot.data[index].isFav),
);
},
itemCount: snapshot.data.length,
);
return Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
I included a function named "add" in database and MyApp's initState to add new words to database.
If you refresh app for multiple time, it will add repetitive rows, the reason is that you should make one of the DB field unique, here can be id to do so you should create the table like below:
return await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE IF NOT EXISTS $TABLE ("
"$ID TEXT NOT NULL PRIMARY KEY,"
"$ENGLISH_WORD TEXT,"
"$GERMAN_WORD TEXT,"
"$IS_FAV TEXT"
")");
});
Also in add function in DB, you should choose what should be done when it faces to a repetitive row(Word here), there are some options, one can be to replace it, it should change like this:
//add new words
Future<int> add(Word word) async {
final db = await database;
var response = await db.insert(TABLE, word.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
return response;
}
I hope it was what You were looking for.

Initialized database still returns as null [Flutter, sqflite]

I'm trying to perform CRUD operations on Flutter, using the sqflite library. Online resources point towards a bunch of ways to go about this. Here is my implementation:
class SqlManager {
static const String tname = 'table1';
static const String dname = 'database.db';
Future<Database> db;
initDB() async {
Directory path = await getApplicationDocumentsDirectory();
db = openDatabase(join(path.path, dname), version: 1, onCreate: (db, version) {
return db.execute(
'CREATE TABLE $tname (id INTEGER PRIMARY KEY, name TEXT, type TEXT, time1 INTEGER, time2 INTEGER, imp INTEGER, urg INTEGER)');
});
}
Future<void> writing(Task task) async {
print("called");
final Database DB = await db;
await DB.insert(
tname,
task.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
print("Execution completed");
}
Future<List<Task>> reading() async {
Database DB = await db;
List<Map<String, dynamic>> maps = await DB.query(tname);
return List.generate(maps.length, (i) {
return Task.fromMap(maps[i]);
});
}
}
Whenever I attempt to call any of these functions, I hit upon a NoSuchMethodError, thrown by the variable 'DB' inside one of these functions. Any help is appreciated, thanks!
Whenever I attempt to call any of these functions, I hit upon a NoSuchMethodError, thrown by the variable 'DB' inside one of these functions. Any help is appreciated, thanks!
It's because you haven't initialized your database by calling the initDB(). So, call it before you call the method using the database. But you'll end with recreating each instance for each call.
The better way is by creating a singleton for your database. Modify your SqlManager to something like this:
class SqlManager {
static const String tname = 'table1';
static const String dname = 'database.db';
// Future<Database> db;
// Make a singleton class
SqlManager._privateConstructor();
static final SqlManager instance = SqlManager._privateConstructor();
// Use a single reference to the db.
static Database _db;
// Use this getter to use the database.
Future<Database> get database async {
if (_db != null) return _database;
// Instantiate db the first time it is accessed
_db = await _initDB();
return _db;
}
// Init the database for the first time.
_initDB() async {
Directory path = await getApplicationDocumentsDirectory();
return await openDatabase(join(path.path, dname), version: 1, onCreate: (db, version) {
return db.execute(
'CREATE TABLE $tname (id INTEGER PRIMARY KEY, name TEXT, type TEXT, time1 INTEGER, time2 INTEGER, imp INTEGER, urg INTEGER)');
});
}
// Then you can use the database getter in another method
Future<List<Task>> reading() async {
Database DB = await instance.database;
List<Map<String, dynamic>> maps = await DB.query(tname);
return List.generate(maps.length, (i) {
return Task.fromMap(maps[i]);
});
}
}

Execute if database exists flutter

So basically what i want to do is:
When the user turns on the app for the first time,
The SQLite database is created and the data is fetched from the internet.
Until this is done, SetupPage() widget is displayed in scaffolds body or else Home() is displayed.
Now the code i wrote works perfectly for the first time, but when i open it the second time,
The SetupPage() only shows, it never goes back to the Home(). What am i doing wrong here ?
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart';
import 'package:path/path.dart';
import 'pages/home.dart';
import 'pages/SetUpPage.dart';
import 'package:sqflite/sqflite.dart';
class App extends StatefulWidget {
createState() {
return AppState();
}
}
class AppState extends State<App> {
final bgColor = const Color(0xFF1abc9c);
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool status = false;
Database database;
#override
void initState() {
initializeData();
super.initState();
}
void initializeData() async
{
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'demo.db');
status = false;
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE news (id INTEGER PRIMARY KEY, topic TEXT, img TEXT, newstitle TEXT, news TEXT, newslink TEXT)');
fetchData();
}
);
database.close();
}
#override
Widget build(context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("UnFound News"),
backgroundColor: bgColor,
),
body: status ? Home() : SetUpPage(),
);
}
/*Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);*/
void fetchData() async {
var result = await get("https://api.myjson.com/bins/a0bvu");
var arr = json.decode(result.body)['post'];
for(int i = 0; i < arr.length; i++)
{
//TODO: Implement addition to database.
}
setState(() {
status = true;
});
}
}
You probably want to put the fetchData in a onOpen parameter instead of the onCreate parameter like this:
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE news (id INTEGER PRIMARY KEY, topic TEXT, img TEXT, newstitle TEXT, news TEXT, newslink TEXT)');
await fetchData();
}, onOpen: (Database db) async {
setState(() {
status = true;
});
});
static Database _db;
Future<Database> get db async {
if(_db != null)
return _db;
_db = await initDb();
return _db;
}
//Creating a database with name test.dn in your directory
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "test.db");
var theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
return theDb;
}
checkDb(){
//do operation
var dbClient = await db;
}