how to open and query .sqlite database read-only in flutter - database

void openDb() async {
Database _db;
_db ??
await openReadOnlyDatabase(
'package:myAppName/databasesFolder/db_drill_pipe.sqlite');
var list = await _db.query('table_name_1', columns: ['column_title_1]);
print('$list');
}
I get this error:
Tried calling: query("table_name_1", columns: Instance(length:1) of '_GrowableList')
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 openDb (package:kbook/databases/db_helper.dart:50:24)
<asynchronous suspension>
I have one database file with .sqlite extension, it has three tables. All I need is to open and query data from that file when the user triggers during runtime, I will never write or update the database, read only. I thought this would be more responsive than json but I'm stuck.

I hope this helps in some way, it's how I use it in my code:
Future<Database> get database async {
if (_database != null) return _database;
_database = await _databaseConnection.setDatabase();
return database;
}
readData(table) async {
var connection = await database;
return await connection.query(table);
}
read() async {
return await _repository.readData('Table');
}

I don't think this error has anything to do with sqflite. Indeed _db is null as it is not assigned to anything. I think instead of
_db ?? await openReadOnlyDatabase...
you meant to do
_db = await openReadOnlyDatabase....

Related

Hive database not working offline while works fine when connected to internet

I am developing a flutter application in which I am implementing hive database for caching data.
I have added both hive and hive_flutter packages.
I am getting data from APIs and store that to hive to update data, It works fine when I used app connected to internet but didn't works when I try to read while being offline. Here is the code of my API method I am calling to get data:
static Future<List<UserPost>> getPosts() async {
//I call my API in try block, if its successful, I update the data in hive
List<UserPost> posts = [];
Hive.openBox(Constants.APIDATA_BOX);
try {
var response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'),);
if (response.statusCode == 200) {
//Clear hive box from old data
Hive.box(Constants.APIDATA_BOX).clear();
Hive.box(Constants.APIDATA_BOX).put(Constants.API_DATA,jsonDecode(response.body));
}
} catch (e) {
print('You are not connected to internet');
}
//I am getting data here from hive database and it works fine while connected to internet
var listMaps =await Hive.box(Constants.APIDATA_BOX).get(Constants.API_DATA, defaultValue: []);
posts = listMaps.map<UserPost>((map) {
//Here flow stucked whenever working offline,
//Data is also available but here conversion cause error, I have tried many way but fails.
return UserPost.fromMap(map);
}).toList();
return posts;
}
I don't why I am getting error, I have tried many conversion ways here but all works while being online. Any help will be highly apprerciated.
I think I've understood the error but you should explain better which type of error you're having.
Anyway pay attention to the operations on Hive, which are often async, for example Hive.openBox(Constants.APIDATA_BOX);.
So when you have internet connection, you have to await for the response and Hive has time to open the box, otherwise it will throw an error so, considering the futures, you should do this:
static Future<List<UserPost>> getPosts() async {
List<UserPost> posts = [];
await Hive.openBox(Constants.APIDATA_BOX);
try {
var response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'),);
if (response.statusCode == 200) {
//Clear hive box from old data
await Hive.box(Constants.APIDATA_BOX).clear();
await Hive.box(Constants.APIDATA_BOX).put(Constants.API_DATA,jsonDecode(response.body));
}
} catch (e) {
print('You are not connected to internet');
}
var listMaps = await Hive.box(Constants.APIDATA_BOX).get(Constants.API_DATA, defaultValue: []);
posts = listMaps.map<UserPost>((map) {
return UserPost.fromMap(map);
}).toList();
return posts;
}
Note that await Hive.put() in a normal box is not strictly necessary, as explained in the docs

How to use async functions to connect to database in Flutter?

I am trying to connect to a static database as it is explained in this answer. I therefore created an asynchronous function that looks like this:
Future<void> loadDataBase() async {
// Construct a file path to copy database to
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "asset_worldcities.db");
// Only copy if the database doesn't exist
if (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound) {
// Load database from asset and copy
ByteData data = await rootBundle.load(join('assets', 'worldcities.db'));
List<int> bytes = data.buffer.asUint8List(
data.offsetInBytes, data.lengthInBytes);
// Save copied asset to documents
await new File(path).writeAsBytes(bytes);
}
}
Now I thought I could access my database inside my main widget by using this function and then call
Directory appDocDir = await getApplicationDocumentsDirectory();
String databasePath = join(appDocDir.path, 'asset_database.db');
this.db = await openDatabase(databasePath);
initialized = true;
Future<List<Page>> search(String word, int parentId) async {
if (!initialized) await this._initialize();
String query = '''
SELECT * FROM users
LIMIT 25''';
return await this.db.rawQuery(query);
}
but this way I am not allowed to use this.db and also not await as I am not inside an async function. Where do I need to put this database request so that it works?
Depending whether you need to do this every time and the database could grow, or whether it's a one-time operation (which it seems like it might be?) and the database is small enough that it's not going to take long to query it, there are different approaches I'd take.
If it's a one-time per install sort of thing and the database will always be small, making the user wait while it copies the file across probably isn't a huge deal. In that case I'd do something like this:
main() async {
WidgetsFlutterBinding.ensureInitialized();
if (needToLoadDatabase()) {
await loadDatabase();
}
let users = await queryUsers();
runApp(MainWidget(users: users));
}
However, if you're reading from the database and it's something that could take any significant amount of time, I'd recommend initiating the load and then passing the future into your main widget, where it could use a FutureBuilder to build an intermediate UI.
That'd look something like this:
main() async {
WidgetsFlutterBinding.ensureInitialized();
let loadUsers = () async {
if (needToLoadDatabase()) {
await loadDatabase();
}
return await queryUsers();
}();
runApp(MainWidget(loadUsers: loadUsers));
}
class MainApp extends StatelessWidget {
final Future<Users> loadUsers;
MainApp({#required this.loadUsers, Key key}): super(key: key);
Widget build(BuildContext context) {
return FutureBuilder(
builder: (ctx, snapshot) {
if (snapshot.hasData) {
// build your UI with data
} else {
// build your UI without data
}
}
);
}
}
Also note that there's no reason you have to do the loading in the main function - you could make your widget stateful and kick that off in the initState, or any number of places like directly where you use the list. You could also look at the FutureProvider from the Provider package.

Initialize a store the first time is created in sembast

I'm developing a mobile application in Flutter, and I would like to manage the settings of my application (light theme or dark theme, ...).
I'm using sembast to store the settings of my application. I would like to initialize the store with some initial values the first time is created. How can I do that?
This is the my database helper class:
class AppDatabase {
// Name of the database.
static final String _dbName = 'mydb.db';
// Singleton instance.
static final AppDatabase _singleton = AppDatabase._();
// Singleton getter.
static AppDatabase get instance => _singleton;
// Transforms synchronous code into asynchronous code.
Completer<Database> _dbOpenCompleter;
// Private constructor.
AppDatabase._();
// Database object getter.
Future<Database> get database async {
// If completer is null, AppDatabaseClass is newly instantiated, so database is not yet opened.
if (_dbOpenCompleter == null) {
_dbOpenCompleter = Completer();
_openDatabase();
}
return _dbOpenCompleter.future;
}
Future<void> _openDatabase() async {
// Get a platform-specific directory where persistent app data can be stored.
final appDocumentDir = await getApplicationDocumentsDirectory();
// Path with the form: /platform-specific-directory/demo.db
final dbPath = join(appDocumentDir.path, _dbName);
final database = await databaseFactoryIo.openDatabase(dbPath);
// Any code awaiting the Completer's future will now start executing.
_dbOpenCompleter.complete(database);
}
}
Then I use the following repository to perform CRUD operations on my Settings:
class SettingsDatabaseRepository implements SettingsRepository {
// The name of the store.
static const String SETTINGS_STORE_NAME = 'settings';
// This store acts like a persistent map, values of which are Settings objects
// converted to Map.
final _settingsStore = intMapStoreFactory.store(SETTINGS_STORE_NAME);
// Private getter to shorten the amount of code needed to get the singleton
// instance of an opened database.
Future<Database> get _db async => await AppDatabase.instance.database;
#override
Future<void> insert(Settings settings) async {
await _settingsStore.add(await _db, settings.toMap());
}
#override
Future<void> update(Settings settings) async {
final finder = Finder(filter: Filter.byKey(settings.settingsId));
await _settingsStore.update(
await _db,
settings.toMap(),
finder: finder,
);
}
#override
Future<Settings> getSettings() async {
final recordSnapshots = await _settingsStore.find(await _db);
final settingsList = recordSnapshots.map((snapshot) {
final settings = Settings.fromMap(snapshot.value);
settings.copyWith(settingsId: snapshot.key);
return settings;
}).toList();
if (settingsList.isEmpty)
return null;
else
return settingsList.first;
}
}
I would like to initialize the store with some initial values the first time is created.
You cannot perform action when a store is created (since a store is not really created, it just holds records), however you can perform action when the database is created.
Sembast supports a database versioning system similar to sqlite, although here there is not much schema to modify. You can use this system to perform action when the database is created (or when you decide later in a new version to update it).
// Our shop store sample data
var store = intMapStoreFactory.store('shop');
var db = await factory.openDatabase(path, version: 1,
onVersionChanged: (db, oldVersion, newVersion) async {
// If the db does not exist, create some data
if (oldVersion == 0) {
await store.add(db, {'name': 'Lamp', 'price': 10});
await store.add(db, {'name': 'Chair', 'price': 15});
}
});
See more info

Is there any example of pre-populated database usage in Flutter?

Is there any example of pre-populated database usage in Flutter?
I don't need CRUD example. At this point I just need to read data from database.
I am new to Flutter so step by step tutorial would be nice.
You can bundle your app with your pre-populated sqlite database in your assets folder. And then on the first run copy the database from assets to your app's working directory. The following code sample shows one way to do it (print statements are just to show what is happening where):
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> initDatabase() async {
var databasesPath = await getDatabasesPath();
var path = join(databasesPath, "app.v1.db");
// Check if the database exists
var exists = await databaseExists(path);
if (!exists) {
// Should happen only the first time you launch your application
print("Creating new copy from asset");
// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (e) {
print(e.toString());
}
// Copy from asset
ByteData data = await rootBundle.load(join("assets", "prepopulated.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
print("Database created successfully");
} else {
print("Opening existing database");
}
// open the database
return await openDatabase(path, version: "1", readOnly: false);
}

WPF , howto to know that a task has completed

I am developping a MVVM WPF app, and I have some task to do.
first load files csv and parse it
In background don´t block the ui Thread and save the values in the database.To save the rows to the database I need to be with Async Await Task.
My problem is I don´t know how to notice the user with a popup notification or something else that values are already saved in database.
in My ViewModel
private void SaveDatasInDatabase()
{
ShowLoadingPanel = true;
_service.SaveValuesInDatabase(rows);
}
private async void startActions()
{
await LoadandParseCsv();
await SaveDatasInDatabase();
}
in my Service.cs
public string SaveDatasInDatabase(List<Object> rows)
{
Task.Run(async () =>
{
await SaveEntity(rows);
return "Done";
});
}
Thanks in advance.
Jolynce
You know that the task has completed once the remainder of the startActions() method is executed:
private async void startActions()
{
await LoadandParseCsv();
await SaveDatasInDatabase();
MessageBox.Show("done");
}
...provided that actually await the SaveEntity method in the SaveDatasInDatabase() method:
public async Task<string> SaveDatasInDatabase(List<Object> rows)
{
await SaveEntity(rows);
return "Done";
}
If you just call Task.Run without awaiting the returned Task, you don't know when it has finished.
The return type of the SaveDatasInDatabase method should be Task or Task<T> for you to be able to await it. The same thing applies to the SaveEntity method.

Resources