Access method to a SQL model class through the parent table - flutter

I have a DB with two tables, each table has his own class with toMap and fromMap method. Each table has a provider and SQLhelper class with the CRUD. My question is how can I access to the table 2 when I only have the context of the table 1 (parent table). The idea is to access to the model class secondTable().
The secondtable.id is the foreign key which references firsttable.id
In my second table sql operations I'm trying this way:
Future<List<Map<String, dynamic>>> getData(int firstTableId) async {
final db = SQLite.db;
List<Map<String, dynamic>> results = await db.rawQuery('''
SELECT *
FROM secondtable
INNER JOIN firsttable ON secondtable.id = firsttable.id
WHERE id = ?
''', [firstTableId]);
return results;
After that I have a provider:
Future<SecondTable?> getData(int firstTableId) async {
final results = await _databaseHelper.getData(firstTableId);
if (results.isNotEmpty) {
Map<String, dynamic> row = results.first;
SecondTable secondTable = SecondTable.fromMap(row);
return secondTable;
} else {
return null;
}
}
And in the page that I need the information I call the provider like that:
#override
void initState() {
super.initState();
_seconTableDataProvider =
Provider.of<SecondTableDataProvider>(context, listen: false);
loadSecondTable();
}
FirstTable _firstTable = FirstTable();
SecondTable? _secondTable = SecondTable();
Future<void> loadSecondTable() async {
_secondTable = await _seconTableDataProvider.getData(_firstTable.id!);
}
I have always a cast error, because return me null. If I hardcode an id like:
_seconTableDataProvider.getData(1);
The problem is DatabaseException(ambiguous column name: id
But if I create a new variable as a foreign key. It doesn't take the value ok the firstTable column referenced and its null again.
I don't know how to act now.

There is a problem in the SQL syntax. The correct way to do it is:
List<Map<String, dynamic>> results = await db.rawQuery('''
SELECT secondtable.*
FROM secondtable
INNER JOIN firsttable ON secondtable.firstTableId = firsttable.id
WHERE firsttable.id = ?
''', [firstTableId]);
After that you have to make sure to initialize the _firstTable.
#override
void initState() {
super.initState();
_secondTableDataProvider =
Provider.of<SecondTableDataProvider>(context, listen: false);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_firstTable = ModalRoute.of(context)!.settings.arguments as FirstTable;
loadSecondTable();
}
And finally you need to take care with the async call to the loadSecondTable function, due the time it needs to acces the data. So use Fututrebuilder or other options.

Related

Future<dynamic> is not a subtype of List<dynamic>

So I am trying to pass a list of String values from firestore table, but I am getting an exception type 'Future<dynamic>' is not a subtype of type 'List<dynamic>'
This is the function
getLectureList(String userId) async {
var collection = FirebaseFirestore.instance.collection('students');
var docSnapshot = await collection.doc(userId).get();
Map<String, dynamic>? data = docSnapshot.data();
List<String> _lectureList =
await data!['attendance']; //This line is kinda giving me trouble
userInfo = FirestoreWrapper()
.getStudentFromData(docId: currentUser(), rawData: data);
return _lectureList;
}
And this is the function where I am getting the exception thrown
#override
void initState() {
lectureList = getLectureList(currentUser()); // Getting an exception here
NearbyConn(context).searchDevices(devices: deviceList);
super.initState();
}
tried using await in the getLectureList() method but still getting the same problem
Why do you await your data? You already got it.
List<String> _lectureList = data!['attendance'];
Please note that I don't know what your data structure looks like, so I cannot tell you if this is correct, I can only tell you that it is more correct than before, because the await did not belong there.
You are getting an exception here lectureList = getLectureList(currentUser()); because the the parameter required by the getLectureList() method is the userId which is a string. I do not know what currentUser() return but I'm assuming it's the userId that you need when calling the getLectureList() method. Based on the error, it looks like currentUser() is an async method that returns a future after some time.
You're not awaiting that future. You shouldn't make the initState() method async so move the code block out of it into a separate method and then call it from initState().
Something like this,
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
lectureList =
getLectureList(await currentUser());
NearbyConn(context).searchDevices(devices: deviceList);
}
or
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
String _userID = await currentUser();
lectureList = getLectureList(_userID);
NearbyConn(context).searchDevices(devices: deviceList);
}
Which I recommend so you can see all the parts.
Making your method parameters required named parameters also help you to easily see what is needed to pass to a function/class/.
Eg.
getLectureList({required String userId}){
...
}
Your IDE will alert you on the type of object the function requires and it makes things clearer.
Ultimately, I think typing your classes makes it so much more easier to fetch data from fireStore Typing CollectionReference and DocumentReference
This way you can easily do this,
final moviesRef = FirebaseFirestore.instance.collection('movies').withConverter<Movie>(
fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
toFirestore: (movie, _) => movie.toJson(),
);
and get your data this way,
Future<void> main() async {
// Obtain science-fiction movies
List<QueryDocumentSnapshot<Movie>> movies = await moviesRef
.where('genre', isEqualTo: 'Sci-fi')
.get()
.then((snapshot) => snapshot.docs);
// Add a movie
await moviesRef.add(
Movie(
title: 'Star Wars: A New Hope (Episode IV)',
genre: 'Sci-fi'
),
);
// Get a movie with the id 42
Movie movie42 = await moviesRef.doc('42').get().then((snapshot) => snapshot.data()!);
}
Keeps everything dry and tidy.
< The data comes to list format thats why showing the exception of datatype >
List<String> lectureList = await getLectureList(currentUser()); // use
Future<List<String>> getLectureList(String userId) async {
- your code -
}
Instead of
List _lectureList =
await data!['attendance'];
Try this
_lectureList = await data![] As List

_CastError (Null check operator used on a null value) from SQFLITE Flutter

I am trying to call this fetchall() method from totalprice() method, but every time it throws this error.
Here is my fetchall() method -
Future<List<CartModel>>? fetchall() async {
List<Map<String, Object?>>? map = await database?.query(tablename);
List<CartModel> cartproducts = [];
CartModel singleproduct = CartModel();
for (Map<String, Object?> i in map!) {
cartproducts.add(CartModel.fromJson(i));
singleproduct = CartModel.fromJson(i);
a = a + singleproduct.price!.toInt();
}
return cartproducts;
and this is the totalprice() method -
Future<int> totalprice() async {
int totalprice = 0;
List<CartModel>? products = await fetchall();
if (products != null) {
for (var i in products) {
totalprice = (totalprice + i.price!.toInt() * i.quantity!.toInt());
}
}
return totalprice;
Thanks for any little help.
Your line
List<Map<String, Object?>>? map = await database?.query(tablename);
Is probably assigning a null value to your map variable which your are later using with a null check operator ! which means that you are sure that it's not null, but the type List<Map<String, Object?>>? in the mentioned line gives it the ability to be null.
You can solve this by adding a null check before using the map variable like so
Future<List<CartModel>>? fetchall() async {
List<Map<String, Object?>>? map = await database?.query(tablename);
List<CartModel> cartproducts = [];
CartModel singleproduct = CartModel();
if(map != null)
for (Map<String, Object?> i in map!) {
cartproducts.add(CartModel.fromJson(i));
singleproduct = CartModel.fromJson(i);
a = a + singleproduct.price!.toInt();
}
}
return cartproducts;
}
Additional advice
For better code quality and readability, please follow variable naming conventions. For dart, make sure are your variables and methods are camelCase
so instead of fetchall => fetchAll
instead of cartproducts => cartProducts
instead of singleproduct => singleProduct
And so on...

How to store only new value to sqflite data table in (Flutter)

I have stored my phones call list into a data table. I want to store only new call list data into this data table. It means, only new data will be saved and existing data will be skipped.
Please tell me with example.
Here is my code:
this is the Database Helper
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 columnNumber = 'number';
static final columnType = 'type';
static final columnDate = 'date';
static final columnDuration = 'duration';
// 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 getExternalStorageDirectory();
String path = join(documentsDirectory.path, _databaseName);
await deleteDatabase(path);
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,
$columnNumber INTEGER,
$columnType TEXT,
$columnDate DATETIME,
$columnDuration INTEGER
)
''');
}
// 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, {ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace}) async {
Database db = await instance.database;
return await db.insert(table, row, conflictAlgorithm: conflictAlgorithm);
}
// 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]);
}
}
This is the main file. I have added here only the Database insertion method. home.dart
...
Future callLogDB() async {
Iterable<CallLogEntry> cLog = await CallLog.get();
final dbHelper = DatabaseHelper.instance;
cLog.forEach((log) async {
// row to insert
Map<String, dynamic> row = {
DatabaseHelper.columnName: '${log.name}',
DatabaseHelper.columnNumber: '${log.number}',
DatabaseHelper.columnType: '${log.callType}',
DatabaseHelper.columnDate:
'${DateTime.fromMillisecondsSinceEpoch(log.timestamp)}',
DatabaseHelper.columnDuration: '${log.duration}'
};
await dbHelper.insert(row, conflictAlgorithm: ConflictAlgorithm.replace);
print('CallLog:: $row');
});
return cLog;
}
...
What's the problem with my code?
there are several ways to do this, and the one I will offer are not the best or the nicest ones, but hope that they will help
1) Simply write all your data to table
You can just insert all your data to the table setting the ConflictAlgorithm to replace or ignore
db.insert(table, data, conflictAlgorithm: ConflictAlgorithm.replace);
This will replace/ignore same entries
2) Query, compare, replace
This is a less 'elegant' solution, you can first query all your data from table
db.query(table, columns: availableColumns, where: 'columnToQueryBy = ?', whereArgs: [neededValue]);
Then compare to the data you have
Then write using db.insert() as above
I think that in your case the first option suits better, this example pretty much covers most things that might help you
Hope it helps!
WHAT ABOUT Read Data from Sqflite and Show in datatable?

How do I get data from an sqflite table and display it as a % inside text widget

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';
// 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
)
''');
}
// 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<double> queryRowCount() async {
Database db = await instance.database;
List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT(*) FROM $table');
int rowCount = Sqflite.firstIntValue(x);
return rowCount.toDouble();
}
// 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]);
}
Future<List<Map<String, dynamic>>> queryOmnivore() async {
Database db = await instance.database;
return await db.query(table, where: '$columnName = ?', whereArgs: ['omnivore']);
}
Future<List<Map<String, dynamic>>> queryPescatarian() async {
Database db = await instance.database;
return await db.query(table, where: '$columnName = ?', whereArgs: ['pescatarian']);
}
Future<List<Map<String, dynamic>>> queryVegetarian() async {
Database db = await instance.database;
return await db.query(table, where: '$columnName = ?', whereArgs: ['vegetarian']);
}
Future<int> queryVegetarianCount() async {
var vegList = await queryVegetarian();
int count = vegList.length;
return count;
}
Future<double> queryOmnivoreCount() async {
var omniList = await queryOmnivore();
int omniCount = omniList.length;
return omniCount.toDouble();
}
Future<double> calcOmnivorePercentage() async {
var x = await queryOmnivoreCount();
var y = await queryRowCount();
double omniPercentage = (x / y) * 100;
return omniPercentage;
}
}
Hey Folks!
I was hoping someone may be able to help me please?!
I'm trying to figure out how to take data out of a a sqflite table I've created, perform a calculation that expresses it as a percentage of the other values, and display it inside a text widget in the app.
I've actually managed to get the result to print in the console using this code:
void omnivorePercentageQ() async {
final omni = await dbHelper.calcOmnivorePercentage();
print('omnivore percentage: ${omni.toStringAsFixed(1)}');
}
But I have no idea how to get it to show up in a text widget in the app itself.
Any ideas would be greatly appreciated!
Thank you,
Jason
you are not far off the answer, and already catch the value of the calculation needed. As i can see you dont need to pass any parameters to the function so i would recomend using a futurebuilder:
return FutureBuilder(
future: dbHelper.calcOmnivorePercentage(),
builder: (context, AsyncSnapshot<double> snapshot) {
if (snapshot.hasData) {
return Center( child: Text('${snapshot.data.toStringAsFixed(1)}'),);
}else
return Center(
child: CupertinoActivityIndicator(),
);
});
The Future Builder class https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Serves to manage widgets that depend on futures, since your calculation and database querys are async you can check its state (As inside the widget in snapshot.hasData). That conditional checks if the future has finished and else shows an indicator. Hope it helps

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]);
});
}
}