Transaction/batch in a newly created SQFlite database - flutter

The following only happens with the database just created as in the code. A previously existing database works all right.
I have the usual singleton setup for the database helper, the relevant part being:
Future<Database> get database async {
// ...
db ??= await openDatabase(
path.join(await getDatabasesPath(), 'database.db'),
onCreate: (db, version) async {
final batch = db.batch();
batch.execute('CREATE TABLE table1 ...');
batch.execute('CREATE TABLE table2 ...');
await batch.commit(noResult: true);
},
// ...
return db;
}
Let's suppose the database doesn't exist yet. I call the following routine:
final db = await database;
await db.transaction((txn) async {
await txn.delete('table1');
final batch = txn.batch();
for (data in newData1)
batch.insert('table1', data.toJson()));
await batch.commit(noResult: true);
await txn.delete('table2');
final batch = txn.batch();
for (data in newData2)
batch.insert('table2', data.toJson()));
await batch.commit(noResult: true);
});
The transaction and batch calls execute without error. When the whole operation is actually executed at the end, it stops on the first DELETE FROM table1 SQL operation with a DatabaseException(attempt to write a readonly database(Sqlite code 1032) (running on Android).
I checked that the singleton is a singleton, openDatabase is not called twice. I also tried the transaction with exclusive: false, no difference.

The likely cause of the issue is that the table can't be accessed. A known workaround for this case is to open the database and close it before deleting, as mentioned in this GitHub issue thread.

Related

Database path in main isolate and other isolate

I am writing database from background thread for this I have used isolate.But after writing data unable to get updated data from main thread to update UI.
I have checked it like right after inserting data I am getting count of inserted entries its give me right count. and write after this whole operation I am getting count from main thread but here the count is 0. Even I am building app again means not using hot reload.
I have registered my database like this in main thread:
LazyDatabase openConnection({bool logStatements = false}) {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'myDatabase.sqlite'));
return NativeDatabase(file,logStatements: logStatements);
});
}
and in isolate accessing it like
Future<DatabaseConnection> _backgroundConnection() async {
final dbFolder = await getApplicationDocumentsDirectory();// here getting error
final file = File(p.join(dbFolder.path, 'myDatabase.sqlite'));
final database =NativeDatabase(file, logStatements: true);
return DatabaseConnection.fromExecutor(database);
}

Flutter / SQFLite adding rows rawInsert

I am working with an existant SQLite database that is copied from assets. Further now everything is alright but when trying to insert some data in the table an error appears:
DatabaseException(no such table: Parametre (code 1 SQLITE_ERROR[1])
and this is my :
Future<bool> insertUser (String user, String depot, String pwd) async {
int result = 0;
Database database = await openDatabase(dbPath);
final db = await database;
try {
result = await db.rawInsert(
'INSERT INTO Parametre (USER, DEPOT) VALUES ("$user", "$depot")');
}catch(e){
print(e);
}
if(result>0){
return true;
}else{
return false;
}
}
what have i missed?
Alright, the problem appeared to be in the dbPath. The dbPath was set to 'assets/myDatabase.db'. First step was to copy the database from the assets to the app.
Second after copying the database i don't have to use the full path to openDatabase. The right way was to use only the database name, means openDatabase('myDatabase.db').
From the looks of it, either you've mispelled the table name Parametre or you don't have a table with that name in your database. You should run a CREATE TABLE statement to create the table somewhere before (or, if you have it in your code, it has not been run or contains some other error).
It may help you knowing that sqflite provides two great arguments to its openDatabase method: onCreate and onUpdate. They allow you to call a custom function when the database is created (a simple way to create all the tables your app needs at the first run) and another different function to update your database by adding tables or altering columns if you just provide a coherent version argument to the same openDatabase function.

Flutter Desktop App (MacOS) - Unable to find SQLITE DB and creates empty db in MacOS /Users/<user>/Documents directory

There's some very odd problems when using flutter for MacOs apps.
I'm using the following Database initialisation:
_initDatabase() async {
Directory dbDirectory = await getApplicationDocumentsDirectory();
String path = join(dbDirectory.path, _databaseName);
//ByteData data = await rootBundle.load("assets/mydatabase.sqlite");
//List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
//await File(path).writeAsBytes(bytes);
return await openDatabase(path,
version: _databaseVersion,
//onCreate: _onCreate
);}
What is the issue?
I keep on getting error that it cannot find the table. The table absolutely exists.
But then I checked the logs and it appears it is using the /Users/myname/Documents to store the database file.
that's not what I want. The database file for this application is in the assets folder, and it should remain (when it is packaged) within the app bundle.
How do I specify the assets folder??
What am I doing wrong??
The create function, by the way, isn't working either.
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY,
$col1 TEXT,
$col2 TEXT,
$col3 TEXT
)
''');}
Im exhausted from trying...

Sqlite onCreate isn't invoked after dropping/deleting the table from the database/storage

Minimal reproducible code:
class Helper {
Database _db;
Future<Database> initDb() async {
if (_db == null) {
final directory = await getApplicationDocumentsDirectory();
_db = await openDatabase(join(directory.path, 'foo.db'), version: 1, onCreate: _onCreate);
}
return _db;
}
Future<void> _onCreate(Database db, _) async {
print('onCreate');
await db.transaction((txn) async {
await txn.execute('CREATE TABLE tableName(abc TEXT)');
});
}
}
Here's my main method:
void main() async {
final directory = await getApplicationDocumentsDirectory();
final file = File(join(directory.path, 'foo.db'));
if (await file.exists()) {
await file.delete();
}
// If there had been a file, it's deleted now.
final helper = Helper();
await helper.initDb(); // This must fire `onCreate` but it doesn't.
}
Every time you run the main method, it should execute the onCreate method in Helper class but it only does that once. What am I doing wrong here?
The issue description has changed since the beginning and it is not easy to make a proper explanation in a comment so here is yet another answer.
All existing responses remain valid but the issue now is moving to something like onCreate not called after deleting the database.
Every time you run the main method, it should execute the onCreate method in Helper class but it only does that once. What am I doing wrong here?
You don't really specify how you run (i.e. do you stop the application before), so I'm assuming you are just pressing the run button on your IDE which performs a hot-restart.
While you might be enclined to simply delete the file, you should however use
deleteDatabase to properly delete a database.
// Do not call File.delete, it will not work in a hot restart scenario
await File(path).delete();
// Instead do
await deleteDatabase(path);
it will properly close any existing database connection
it will properly handle the hot-restart scenario which put SQLite in a
weird state (basically the 'dart' side think the database is closed while
the database is in fact open on the native side)
If you call File.delete, while you might think it work (i.e. the file does not
exist anymore), since the database might still be opened in a hot restart scenario
the next open will re-use the open connection and at some point will get written
with the old data and onCreate will not be called the next time you open
the database.
onCreate will be executed only when there is no database file. Not when there is no table in the db. If you delete database file then there will be a print in console with message onCreate. Here is an example:
void main() async {
final directory = await getApplicationDocumentsDirectory();
final path = join(directory.path, 'foo.db');
await File(path).delete();
final helper = Helper();
await helper.initDb();
// await helper.dropTable();
}
This code prints a log message every run of a program.
This is not how Sqflite works.
The very first time when you create a database you give it a version number. In your case the version is 1.
Sqflite will check if database exists on the devices if it exists it checks the version number of db on device and version number of your code, if version is not the same it will call onUpgrade if new version is greater than old db version. and on downgrade if old version is greater than new version. OnCreate won't be called. onCreate is only called the very first time user install your app. Even if you drop your tables afterwards. If you need to update your database in future on a production app, you have to write onUpgrade method, in which you have to explicitly drop your tables and call onCreate yourself. and upgrade the database version.
See the following code. Use this code setup and change version to a higher version number whenever you need to dropAll tables and createAgain.
If you don't want to use the versions and explicitly drop tables and recreate them. You have to call _onCreate yourself after dropping a table.
class Helper {
Database _db;
Future<Database> initDb() async {
if (_db == null) {
final directory = await getDatabasesPath();
final path = join(directory, 'foo.db');
_db = await openDatabase(
path,
version: 2, //+1 to this number whenever you want to update
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
return _db;
}
Future<void> _onUpgrade(
Database db, int previousVersion, int newVersion) async {
_db = db;
await dropTable();
await _onCreate(db, newVersion);
}
Future<void> _onCreate(Database db, int version) async {
print('onCreate');
await db.transaction((txn) async {
await txn.execute('CREATE TABLE tableName(abc TEXT)');
});
}
Future<void> dropTable() async {
await _db.transaction((txn) async {
await txn.execute('DROP TABLE IF EXISTS tableName');
});
}
It is the default behavior because onCreate Method is executed when you create the database. Since the database is created before it is not executed again.
If you intent to clean the rows of the table then use the TRUNCATE command. For more information check the link comparing drop table and truncate table here

How to load data in sqflite database?

Database database = await openDatabase(
dbPathWithDatabase,
version: 1,
onCreate: (Database database, int version) async {
print("\n\nCalling database on create\n\n");
ByteData data = await rootBundle.load(join("assets", "database.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
print(bytes);
await new File(dbPathWithDatabase).writeAsBytes(bytes);
},
);
The above code is not copying the data to my database. But the database is created successfully.
database.db already exist in assets folder and i want to copy all the data from it.
Here i am calling it from openDatabase which cause the problem.
I can move the code above openDatabase. But i want to know the correct way to populate the data