Flutter SQflite: How to solve unhandled exception? - sqlite

In my app I do want to save some data with sqflite plugin, but it keeps throwing me the error:
Unhandled exception: type '_InternalLinkedHashMap' is not a subtype of type
Map<String, dynamic>' where _InternalLinkedHashMap is from dart:collection
- Map is from dart:core
- String is from dart:core
This is my code for regenerating my error:
Rezepte.dart The where I am handling the table named Rezepte from the database.
class Rezepte{
Rezepte();
int id;
String name;
int personen;
String beschreibung;
int favorit;
static final spalten = ["id", "name", "personen", "beschreibung", "favorit"];
Map toMap(){
Map map = {
"name": name,
"personen": personen,
"beschreibung": beschreibung,
"favorit":favorit
};
if(id != null){
map["id"] = id;
}
return map;
}
static fromMap(Map map){
Rezepte rezepte = new Rezepte();
rezepte.id = map["id"];
rezepte.name = map["name"];
rezepte.personen = map["personen"];
rezepte.beschreibung = map["beschreibung"];
rezepte.favorit = map["favorit"];
return rezepte;
}
}
Datenbank.dart The code for handling the whole database. My Database-H
import 'dart:async';
import 'dart:io';
import 'package:flutter_app/datenbank/Rezepte.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseClient{
Database database;
//Datenbank wird erstellt
Future erstellen() async{
Directory pfad = await getApplicationDocumentsDirectory();
String datenbankPfad = join(pfad.path, "rezept_buch.db");
database = await openDatabase(datenbankPfad, version: 1, onCreate: this.erstelleTabellen);
}
//Die Tabellen werden erstellt
Future erstelleTabellen(Database db, int version) async{
await db.execute("""
create table rezept (
id integer primary key,
name text not null,
personen_anzahl integer not null,
beschreibung text default null,
favorit integer default 0
)
""");
}
Future setRezept(Rezepte rezepte) async{
var count = Sqflite.firstIntValue(await database.rawQuery("SELECT * FROM rezept WHERE name =?", [rezepte.name]));
if(count == 0){
rezepte.id = await database.insert("rezept", rezepte.toMap());
} else {
await database.update("rezept", rezepte.toMap(), where: "id = ?", whereArgs: [rezepte.id]);
}
return rezepte;
}
//Daten aus Tabellen holen
Future getAllRezepte(int id) async{
List ergebnisse = await database.query("rezept", columns: Rezepte.spalten, where: "id=?", whereArgs: [id]);
Rezepte rezepte = Rezepte.fromMap(ergebnisse[0]);
return rezepte;
}
}
This two files are the only one, that are able to generate my error. Does anybody have an idea how I could possibly solve the error?

Hard to know where the issue happens without a longer stack trace. Make sure you are using the latest version (sqflite >=8.4) especially if you are using --preview-dart-2. I also recommend turning on implicit-casts: false in analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false
Casting a Map to Map<String, dynamic> could throw an error if the map is not of the proper type.
The crash reported by Flutter should also indicate the file and line where the issue happened.

Related

Flutter: SQLite database in external storage. database is null

I am writing an app to store some events and dates at which events happened (For history students as part of a project). I wish to create an SQLite database, stored in some preferred location in my device. Later I want this database to merge with main database using computer. Here is my database helper class;
class DatabaseHelper {
static final _dbName = 'mainDatabase.db';
static final _dbVersion = 1;
static final _mainTable = 'mainTable';
static final _storyTable = 'storyTable';
static final _topicTable = 'topicTable';
static final columnId = '_id';
static final title = 'title';
static final description = 'description';
static final parentId = 'topic_id';
static final priority = 'priority';
static final iconId = 'iconId';
static final futureImage = 'image';
static final year = 'year';
static final month = 'month';
static final day = 'day';
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;
}
_database = await _checkPermission();
return _database;
}
var status;
_checkPermission() async {
this.status = await Permission.storage.status;
debugPrint(' storage permission status : ${this.status}');
if (await Permission.storage.request().isGranted) {
_initiateDatabase();
} else if (await Permission.storage.request().isUndetermined) {
debugPrint('Undetermined permission');
} else if (await Permission.storage.request().isDenied) {
debugPrint('Permission denied');
_checkPermission();
} else if (await Permission.storage.request().isPermanentlyDenied) {
debugPrint(' it has been permenantly denied');
}
}
_initiateDatabase() async {
debugPrint(' database initialized');
Directory directory = await getExternalStorageDirectory();
String path = join(directory.path, _dbName);
debugPrint('Path for database: ${path}');
return await openDatabase(path, version: _dbVersion, onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $_mainTable (
$columnId INTEGER NOT NULL,
$year INTEGER,
$month INTEGER,
$day INTEGER,
$title TEXT NOT NULL,
$description TEXT,
$priority INTEGER,
$parentId INTEGER,
PRIMARY KEY($columnId AUTOINCREMENT)
);
'''); /
Future<int> insertEvent(Map<String, dynamic> row) async {
Database db = await instance.database;
debugPrint(' event : ${row}');
assert(db != null); //Issue!!!!
return await db.insert(_mainTable, row);
}
}
My code may seem messy, because I am not from this background. I apologize for that.
When I tried to add an event I get this error;
I/flutter ( 8090): event id is null
I/flutter ( 8090): storage permission status : PermissionStatus.granted
I/flutter ( 8090): database initialized
I/flutter ( 8090): event : {year: null, month: null, day: null, title: adsfasdf, description: null, priority: 0, topic_id: null}
E/flutter ( 8090): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: 'package:thisDay/config/databaseHelper.dart': Failed assertion: line 189 pos 12: 'db != null': is not true.
/////////////// some code
E/flutter ( 8090):
I/flutter ( 8090): Path for database: /storage/emulated/0/Android/data/com.example.thisDay/files/mainDatabase.db
What should I do? I am stuck at this point. I have my db file at /storage/emulated/0/Android/data/com.example.thisDay/files/mainDatabase.db . But can't add anything in it.
Any help would be greately appreciated.
Nb:- At first I used getApplicationDirectory instead of External directory. There were no issues. I have to switch because of portability issues
Finally, I fixed my issue by myself. _initializeDatabase() has a return type Future<Database>. So making respective changes on code fixed my issue.
Future<Database> _checkPermission() async {
this.status = await Permission.storage.status;
debugPrint(' storage permission status : ${this.status}');
if (await Permission.storage.request().isGranted) {
return await _initiateDatabase();
} else if (await Permission.storage.request().isUndetermined) {
debugPrint('Undetermined permission');
} else if (await Permission.storage.request().isDenied) {
debugPrint('Permission denied');
_checkPermission();
} else if (await Permission.storage.request().isPermanentlyDenied) {
debugPrint(' it has been permenantly denied');
}
}
Future<Database> _initiateDatabase() async {
debugPrint(' database initialized');
Directory directory = await getExternalStorageDirectory();
String path = join(directory.path, _dbName);
debugPrint('Path for database: ${path}');
return await openDatabase(path, version: _dbVersion, onCreate: _onCreate);
}
for creating a database and table you can refer this code
Database database;
opendatabase() async {
// Delete the database
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, "sample.db");
// await deleteDatabase(path);
database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
// When creating the db, create the table
await db.execute(
'CREATE TABLE tbl_test (id INTEGER PRIMARY KEY, sample TEXT, code TEXT, format TEXT)',
);
});
}

How to insert POJO Objects and Lists in SQFlite

I am trying to store data from an API to sqflite , but I have to insert the Objects and the Lists, however during the process, am getting this error :
ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: DatabaseException(java.lang.String cannot be cast to java.lang.Integer) sql 'INSERT INTO articleTable (id, created_on, title, summary, details, tags, featured_image, author, category) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)' args [21, 2020-04-14 04:04:57, Singer Jose ...}]
This is the Model Article class :
#JsonSerializable(explicitToJson: true)
class Article {
Article(this.id, this.created_on, this.title, this.details,
this.featured_image, this.author, this.category, this.summary, this.tags);
int id;
String created_on;
String title;
String summary;
String details;
List<String> tags;
String featured_image;
Author author;
Category category;
factory Article.fromJson(Map<String, dynamic> json) =>
_$ArticleFromJson(json);
Map<String, dynamic> toJson() => _$ArticleToJson(this);
}
The automated generated file for Article model class :
/ GENERATED CODE - DO NOT MODIFY BY HAND
part of 'article.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Article _$ArticleFromJson(Map<String, dynamic> json) {
return Article(
json['id'] as int,
json['created_on'] as String,
json['title'] as String,
json['details'] as String,
json['featured_image'] as String,
json['author'] == null
? null
: Author.fromJson(json['author'] as Map<String, dynamic>),
json['category'] == null
? null
: Category.fromJson(json['category'] as Map<String, dynamic>),
json['summary'] as String,
(json['tags'] as List)?.map((e) => e as String)?.toList(),
);
}
Map<String, dynamic> _$ArticleToJson(Article instance) => <String, dynamic>{
'id': instance.id,
'created_on': instance.created_on,
'title': instance.title,
'summary': instance.summary,
'details': instance.details,
'tags': instance.tags,
'featured_image': instance.featured_image,
'author': instance.author?.toJson(),
'category': instance.category?.toJson(),
};
This is where am doing my Sqflite insertion :
class ArticleApiProvider {
Future<List<Article>> fetchArticles() async {
final response =
await http.get('https://api.xyxyxyxy');
if (response.statusCode == 200) {
List jsonResponse = jsonDecode(response.body);
return jsonResponse.map((article) {
articleBloc.addArticles(Article.fromJson(article));
}).toList();
} else {
throw Exception('Failed to retrieve articles');
}
}
}
My Bloc Class :
class ArticleBloc {
final _repository = Repository();
final _articleFetcher = PublishSubject<List<Article>>();
Stream<List<Article>> get allArticles => _articleFetcher.stream;
fetchArticles() async {
List<Article> articles = await _repository.fetchArticles();
_articleFetcher.sink.add(articles);
}
getArticlesCached() async {
// sink is a way of adding data reactively to the stream
// by registering a new event
_articleFetcher.sink.add(await _repository.getAllArticles());
}
void addArticles(Article article) async {
await _repository.insertArticle(article);
}
dispose() {
_articleFetcher.close();
}
}
final articleBloc = ArticleBloc();
This is the DatabaseProvider class
final articlesTable = "articleTable";
class DatabaseProvider {
static final DatabaseProvider dbProvider = DatabaseProvider();
Database _database;
String id = "id";
String createdOn = "created_on";
String title = "title";
String summary = "summary";
String details = "details";
String featuredImage = "featured_image";
String author = "author";
String category = "category";
String tags = "tags";
Future<Database> get database async {
if (_database != null) return _database;
_database = await createDatabase();
return _database;
}
createDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
// NewVision.db is our database instance name
String path = join(documentsDirectory.path, "NewVision.db");
var database = await openDatabase(path,
version: 1, onCreate: initDB, onUpgrade: onUpgrade);
return database;
}
void initDB(Database db, int version) async {
await db.execute(
'CREATE TABLE $articlesTable($id INTEGER PRIMARY KEY AUTOINCREMENT, $createdOn TEXT, $title TEXT ,'
' $summary TEXT , $details TEXT , '
'$tags TEXT, $featuredImage TEXT , $author TEXT , $category TEXT )');
}
void onUpgrade(Database db, int oldVersion, int newVersion) {
if (newVersion > oldVersion) {}
}
}
String join(String path, String s) {
return path + s;
}
The rest of the files do follow the Bloc architecture, for the purposes of making the question precise, I think form what I have provided, I can get some help.
I will be grateful for your help.
Thanks for the detailed report. My assumption is that the error comes from your tags property. List of String is not a supported type in SQLite. sqflite supports List of int for compatibility reason (for blobs but Uint8List will likely be the only supported type in the future) so the casting error could come from this.
You should try to encode your tags (json or comma separated string) before doing another investigation.
This issue also applies to the author and category fields.
You have to flatten your model. See the supported types help section: https://github.com/tekartik/sqflite/blob/master/sqflite/doc/supported_types.md
Basically int, double, String and Uint8List(blob) are the only types supported. Unfortunately you have to convert your inner List and Map, json being one solution.
But i agree the error reported does not help in finding the issue, that could definitely be improved...

sqflite broke on Android after adding a new table Flutter

I just added a new "Translations" table to the database init and wrote all the crud methods. Commands db.query and db.insert work just fine, but as tried inside updateVersion() either db.delete or db.update throw the missing plugin error :
ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method update on channel com.tekartik.sqflite).
After a while I realised that also older two tables are now throwing the same error on delete and update command. Looks like adding a new table broke it all.. I tried flutter clean but nothing changed, so I uninstalled and reinstalled the app, but I still get same errors. I then commented out new table and reinstalled the app but still get the errors..I also tried invalidating the cache a restart but still the same..
On iPhone dough it does not throw any error..
In iOS CoreData kinda behaves the same when you change something.. but with a fresh app install it all resets.. here it seems that the db is still written on disk..
How do I make sure I erased it? I tried await deleteDatabase(path);.
Any Idea of what's going on?
As always thank you very much for your time and help.
This is the db:
class DBProvider {
//1.Create a private constructor that can be used only inside the class :
DBProvider._();
static final DBProvider db = DBProvider._();
//2.Setup the database
//Next we will create the database object and provide it with a getter
//where we will instantiate the database if it’s not (lazy initialization).
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// await Sqflite.devSetDebugModeOn(true);
// if _database is null we instantiate it
_database = await initDB();
return _database;
}
// If there is no object assigned to the database,
// we use the initDB function to create the database.
// In this function, we will get the path for storing the database
// and create the desired tables:
initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "Fixit.db");
//TODO: tried deleting
// await deleteDatabase(path); // still errors
return await openDatabase(path, version: 1, onOpen: (db) {},
onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE Route("
"routeId TEXT,"
"routeName TEXT,"
"routeDistance TEXT,"
"routeDuration TEXT,"
"coordinates TEXT"
")");
await db.execute("CREATE TABLE Alarm("
"alarmId TEXT,"
"alarmName TEXT,"
"enabled BOOL,"
"repeatWeekdays TEXT,"
"time TEXT,"
"sound TEXT,"
"routeName TEXT"
")");
// await db.execute("CREATE TABLE Translations("
// "version TEXT"
// ")");
});
}
}
and these are the CRUD methods:
class DefaultsDbRepository {
var database = DBProvider.db.database;
Future<int> checkVersion() async {
final db = await database;
try {
var res = await db.query('Translations');
assert(res != null);
int version = res != null ? int.parse(res.first['version']) : 0;
print('checkVersion() db version is: $version');
return version;
} catch (err) {
print(err);
}
}
saveVersion({int version}) async {
print('saveVersion() version to save is : $version');
assert(version != null);
final db = await database;
try {
Map<String, dynamic> map = {'version': version};
db.insert("Translations", map);
} catch (e) {
print(e);
}
}
updateVersion({int newVersion, int oldVersion}) async {
print('updateVersion() version to save is : $newVersion');
assert(newVersion != null);
final db = await database;
db.delete("Translations");
try {
// Map<String, dynamic> map = {'version': newVersion};
//
// db.insert("Translations", map);
// db.update("Translations", map); // [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method update on channel com.tekartik.sqflite)
// db.update("Translations", map,
// where: "version = ?", whereArgs: [oldVersion]);
} catch (e) {
print(e);
}
}
}
Finally made it work again. After quite a few, uninstall, flutter clean and reinstalls I saw the problem. I was using ' instead of " in my db methods.
So I changed'ed from:
saveVersion({int version}) async {
print('saveVersion() version to save is : $version');
assert(version != null);
final db = await database;
try {
Map<String, dynamic> map = {'version': version};
db.insert("Translations", map);
} catch (e) {
print(e);
}
}
to:
saveVersion({int version}) async {
print('saveVersion() version to save is : $version');
assert(version != null);
final db = await database;
try {
Map<String, dynamic> map = {"version": version};
db.insert("Translations", map);
} catch (e) {
print(e);
}
}
and all commands are working as expected.
Hope this will be of help to others.
Cheers.

How to query last 3 hours in sqflite?

I'm trying to query the last 3 hours of entries from my sqflite database. Here's my current query:
Future<List<Map<String, dynamic>>> queryLastThreeHours() async {
Database db = await instance.database;
return await db.query(table, where: '$columnDate = ?', whereArgs: ['now', '-3 hours']);
}
For more context, here's the complete 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 columnDate = 'date';
// 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,
$columnDate 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<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]);
}
Future<List<Map<String, dynamic>>> queryOmnivore() async {
Database db = await instance.database;
return await db.query(table, where: '$columnColour = ?', whereArgs: ['Omnivore']);
}
Future<List<Map<String, dynamic>>> queryLastThreeHours() async {
Database db = await instance.database;
return await db.query(table, where: '$columnDate = ?', whereArgs: ['now', '-3 hours']);
}
}
Any help would be greatly appreciated. I've tried to work it out using Sqlite SELECT * for Last 7 days and How to get Last 3 hours data from SQLite, but still haven't figured it out.
Thank you very much in advance!
Jason

Escalate DBProvider Flutter

I am new to working with databases in flutter. I have a data folder with a database.dart in the following form. This is fine for one table and with a few CRUD functions such as newNote or getNotes, but what happens when you have multiple tables and multiple CRUD Functions? Are you supposed to dump them all on this database.dart file making it chaotic? What are better alternatives for organization?
class DBProvider {
DBProvider._();
static final DBProvider db = DBProvider._();
Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;
}
_database = await initDB();
return _database;
}
initDB() async {
Directory documentsDir = await getApplicationDocumentsDirectory();
String path = join(documentsDir.path, 'app.db');
return await openDatabase(path, version: 1, onOpen: (db) async {
}, onCreate: (Database db, int version) async {
// Create the note table
await db.execute('''
CREATE TABLE note(
id INTEGER PRIMARY KEY,
contents TEXT DEFAULT ''
)
''');
});
}
newNote(Note note) async {
final db = await database;
var res = await db.insert('note', note.toJson());
return res;
}
}

Resources