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

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

Related

How to save app data after restart of an app - flutter?

I was wondering how to make my app save data after restart? (The user can delete task and add new task to list, as well as check the box that the task is done. I want the app to save this data so when the user exists the app it will display all the tasks that he left the app with)
I was reading on google for few hours now, I got to
[1]: https://flutter.dev/docs/cookbook/persistence/reading-writing-files
This link as someone recommended on a similar post. But after reading it through I am a bit confused about where to start with my app.
Including some of my code and if you could help me I would really appreciate it as after hours of reading and watching tutorials I am still quite unsure where to start or which way of doing this is best.
My main.dart is this
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) =>
TaskData(), //changing builder: to create: fixed the errors i been having
child: MaterialApp(
home: TasksScreen(),
),
);
}
}
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: "Sample task 1"),
Task(name: "Sample task 2"),
Task(name: "Sample task 3"),
];
UnmodifiableListView<Task> get tasks {
return UnmodifiableListView(_tasks);
}
int get taskCount {
return _tasks.length;
}
void addTask(String newTaskTitle) {
final task = Task(name: newTaskTitle);
_tasks.add(task);
notifyListeners();
}
void updateTask(Task task) {
task.toggleDone();
notifyListeners();
}
void deleteTask(Task task) {
_tasks.remove(task);
notifyListeners();
}
Thank you so much!
The basic method is using the device local storage.
1 - Add the shared_preferences in your pubspec.yaml
2 - Create a class to write and read data :
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class StoreData {
StoreData._privateConstructor();
static final StoreData instance = StoreData._privateConstructor();
Future<void> saveString(String key, String value) async {
try{
SharedPreferences pref = await SharedPreferences.getInstance();
final encodedValue = base64.encode(utf8.encode(value));
pref.setString(key, encodedValue);
} catch (e){
print('saveString ${e.toString()}');
}
}
Future<String> getString(String key) async {
SharedPreferences pref = await SharedPreferences.getInstance();
final value = pref.getString(key) == null ? '' : pref.getString(key);
if (value.length > 0) {
final decodedValue = utf8.decode(base64.decode(value));
return decodedValue.toString();
}
return '';
}
Future<bool> remove(String key) async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.remove(key);
}
}
3 Use :
Save Data:
StoreData.instance.saveString('name', 'sergio');
Retrieve Data:
final String storedName = await StoreData.instance.getString('name');
print('The name is $storedName');
We have many other methods, like use a SQlite, NoSql or a Database in back-end, but the local storage is the most basic case
What you need is a database or a document based data storage. You can store data in a local sqlite db, using sqflite plugin. Or you can store in a JSON asset file.
You can also use a server or cloud service. Firebase is pretty well integrated with flutter, but AWS and Azure are also great.
You can write the data in a text file in the asset, but that would very complicated.

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

How to Delete A Specific File From AWS S3Bucket

I am integrating CKEDITOR in react project and I am using AWS S3 bucket to upload the image that i add in text-editor.. Upload is working fine... The problem is if I delete the image in text-editor.It does not delete it from the AWS bucket.
Causing a lot of unwanted images in bucket. Hence , I need to delete the image from AWS server if it's not present in the text-editor..
How Can I do it??
I have the link to the image in the React Part as the repsonse of the upload.
You need to have a bucket name and key of that file in order to delete that file form AWS s3
const deleteS3Object = async (key, BUCKET_NAME) => {
return new Promise((resolve, reject) => {
try {
let s3bucket = new AWS.S3({
accessKeyId: IAM_USER_KEY,
secretAccessKey: IAM_USER_SECRET,
Bucket: BUCKET_NAME,
});
var params = { Bucket: BUCKET_NAME, Key: key };
s3bucket.deleteObject(params, function(err, data) {
if (err) reject(err);
// an error occurred
else resolve(data); // successful response
});
} catch (e) {
reject(e);
}
});
};

How to add multiple docs to a collection in firebase?

Im working with React native and react-native-firebase
My objective is to add multiple docs(objects) to a collection at once.
Currently, I have this:
const array = [
{
name: 'a'
},{
name: 'b'
}
]
array.forEach((doc) => {
firebase.firestore().collection('col').add(doc);
}
This triggers an update on other devices for each update made to the collection.
How can I batch these docs together for ONE update?
You can create batch write like
var db = firebase.firestore();
var batch = db.batch()
in you array add updates
array.forEach((doc) => {
var docRef = db.collection("col").doc(); //automatically generate unique id
batch.set(docRef, doc);
});
finally you have to commit that
batch.commit()
You can execute multiple write operations as a single batch that contains any combination of set(), update(), or delete() operations. A batch of writes completes atomically and can write to multiple documents.
var db = firebase.firestore();
var batch = db.batch();
array.forEach((doc) => {
batch.set(db.collection('col').doc(), doc);
}
// Commit the batch
batch.commit().then(function () {
// ...
});
Version 9 of Web API is slightly different, the docs include this example:
import { writeBatch, doc } from "firebase/firestore";
// Get a new write batch
const batch = writeBatch(db);
// Set the value of 'NYC'
const nycRef = doc(db, "cities", "NYC");
batch.set(nycRef, {name: "New York City"});
// Update the population of 'SF'
const sfRef = doc(db, "cities", "SF");
batch.update(sfRef, {"population": 1000000});
// Delete the city 'LA'
const laRef = doc(db, "cities", "LA");
batch.delete(laRef);
// Commit the batch
await batch.commit();
The batch from database also has a create function that adds a new document in a collection and throws an error if there is already a document. we just need the reference to the document. Please note that this function exists in admin sdk of firebase.
const batch = db.batch();
await users.map(async (item)=> {
const collectionRef = await db.collection(COLLECTION_NAME).doc();
batch.create(collectionRef, item);
});
const result = await batch.commit();
A batched write can contain up to 500 operations. Each operation in the batch counts separately towards your Cloud Firestore usage.
Note: For bulk data entry, use a server client library with parallelized individual writes. Batched writes perform better than serialized writes but not better than parallel writes. You should use a server client library for bulk data operations and not a mobile/web SDK.

Resources