I want to set up an ObjectBox database with Flutter.
I would like to pre-populate the database file with values. When installing the application, the database file will be copied and is used by the application. I want to be able to continue to provide schema migrations. Is it possible?
How to set up this type of architecture?
Have you any example?
SOLUTION :
With #vaind response, I have implemented a database Manager like :
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import '../objectbox.g.dart';
/// Database manager manage database initialisation and configuration.
class DatabaseManager {
static DatabaseManager? _instance;
DatabaseManager._internal();
/// Singleton Factory constructor
factory DatabaseManager() {
if (_instance == null) {
_instance = DatabaseManager._internal();
}
return _instance!;
}
/// Initialize configuration of database:
/// - Init database file by copying.
/// - Open instance of database and close it.
Future<void> init() async {
await copyDatabaseFileFromAssets();
await testInstanceAndCloseIt();
}
/// Copy packaged database from the assets folder to the app directory.
Future<void> copyDatabaseFileFromAssets() async {
// Get a pre-populated DB file.
ByteData data = await rootBundle.load("assets/databases/data.mdb");
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Search and create DB file destination folder if not exist
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = documentsDirectory.path + "/objectbox/data.mdb";
if (!await File(path).exists()) {
var objectBoxDirectory = Directory(documentsDirectory.path + "/objectbox/");
if ((!await objectBoxDirectory.exists())) {
objectBoxDirectory.create();
}
// Copying source data into destination file.
await new File(path).writeAsBytes(bytes);
}
}
/// Open an instance of the database and close it.
Future<void> testInstanceAndCloseIt() async {
await openStore().then((Store store) {
store.close();
});
}
}
Yes, even if you want to avoid populating the database in-app (e.g. on the first run), you can bundle an existing database. Just create the database file (data.mdb) locally, even on your machine (they're compatible across platforms) and than add it to the app as a resource.
On the first app run you can then just move the data.mdb file to the application documents directory (into subdirectory objectbox) - that's where ObjectBox stores the by default if you've used the generated openStore() method. So the path to the database file should be (using package path_provider):
(await getApplicationDocumentsDirectory()).path + '/objectbox/data.mdb'. Make sure you open the store after you write the file, of course.
Update after the question has been updated. I think your copyDatabaseFileFromAssets should look more like the following. Create the database file only if it doesn't exist.
Future<void> copyDatabaseFileFromAssets() async {
// Search and create db file destination folder if not exist
final documentsDirectory = await getApplicationDocumentsDirectory();
final objectBoxDirectory = Directory(documentsDirectory.path + '/objectbox/');
if (!objectBoxDirectory.existsSync()) {
await objectBoxDirectory.create(recursive: true);
}
final dbFile = File(objectBoxDirectory.path + '/data.mdb');
if (!dbFile.existsSync()) {
// Get pre-populated db file.
ByteData data = await rootBundle.load("assets/databases/data.mdb");
// Copying source data into destination file.
await dbFile.writeAsBytes(data.buffer.asUint8List());
}
}
Related
I am generating images via an API call. The API call works successfully and I have been able to successfully download the image to the device. The next step I need is to upload this file into firebase via its file path.
Code:
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
import 'package:image_downloader_web/image_downloader_web.dart';
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
Future portraitsave(
String? imageURL,
String uid,
) async {
try {
// Saved with this method.
var imageId = await ImageDownloader.downloadImage(imageURL!);
if (imageId == null) {
return;
}
// Below is a method of obtaining saved image information.
var fileName = await ImageDownloader.findName(imageId);
String path = await ImageDownloader.findPath(imageId).toString();
var size = await ImageDownloader.findByteSize(imageId);
var mimeType = await ImageDownloader.findMimeType(imageId);
} on PlatformException catch (error) {
print(error);
}
}
What I'm trying to do is use the "path" variable to upload this image into firebase. Any ideas?
Since you mention an image that you have in a local file, I'm gonna assume you want to upload them to Cloud Storage through Firebase and not to Firestore (which is a document database that is also part of Firestore).
To upload the local image to Firestore:
Create a File reference to the local file.
Create a Reference to where you want the file to end up in Cloud Storage.
Call putFile on the reference passing in the file.
Here's an example from the Firebase documentation on uploading a file:
Directory appDocDir = await getApplicationDocumentsDirectory();
String filePath = '${appDocDir.absolute}/file-to-upload.png';
File file = File(filePath);
try {
await mountainsRef.putFile(file);
} on firebase_core.FirebaseException catch (e) {
// ...
}
I want to add export and import database to my application. My database is hive flutter. I tried several methods but it didn't work
ElevatedButton(onPressed: () async{
final String? pathHive=Hive.box<Cart>(cartBoxName).path;
Directory dir=Directory('/storage/emulated/0/Download');
await File(pathHive!).copy('$dir/backup.hive');
}
it didn't work
you can use those methods in order to import/export a backup file for your Hive box in your flutter app:
import 'dart:io';
import 'package:hive/hive.dart';
Future<void> backupHiveBox<T>(String boxName, String backupPath) async {
final box = await Hive.openBox<T>(boxName);
final boxPath = box.path;
await box.close();
try {
File(boxPath).copy(backupPath);
} finally {
await Hive.openBox<T>(boxName);
}
}
Future<void> restoreHiveBox<T>(String boxName, String backupPath) async {
final box = await Hive.openBox<T>(boxName);
final boxPath = box.path;
await box.close();
try {
File(backupPath).copy(boxPath);
} finally {
await Hive.openBox<T>(boxName);
}
}
The concept over it, is that open the Hive box first (if it's already open then you can just get its instance ), Then, using dart:io we can export/import a File with the path of the Hive Box that we got with box.path.
And make sure that the box should be closed when you make the copying operation into/from a file, after that you can open it again.
I followed the guide in the flutter doc and didnt understand how it works.
I have a existing file containing users info, and I want to be able to write/update the file.
Here's the relevant parts of my code:
Future<bool> sendMessage(String sender, String target, String body) async {
String tempAccountData = await loadMessage();
List<Map<String, dynamic>> accountData =
List<Map<String, dynamic>>.from(jsonDecode(tempAccountData));
//find valid target
print(accountData);
print(target);
for (Map<String, dynamic> accountInfo in accountData) {
if (accountInfo["username"] == target) {
//write message
accountInfo["messages"]
.add({"time": DateTime.now(), "sender": sender, "message": body});
writeMessage(accountData);
return true;
}
}
return false;
}
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
print(path);
return File('$path/accountInfo.json');
}
Future<File> writeMessage(List<Map<String, dynamic>> accountData) async {
File file = await _localFile;
// Write the file
return file.writeAsString('$accountData');
}
I didnt know what the path is, and I put the file into assets/data as seen in the screenshot below: (I'm trying to read/write accountInfo.json from accountInfo.dart)
It's not possible to modify a file in the assets during runtime.
What you can do is write all the data in the Json to a file in one of the device's directories and then modify that file when needed or use a database to store all the data.
There are a few steps that need to happen and things you'll need to know. First of all, assets are immutable. This means you can read from them, but you're not able to write to them. What you'll need is a local file. A file that lives on the file system of the device where the app is installed. This file can be modified, and will allow writing to it.
The process would go as follows:
Check if there's already a local file
If not, create it and put the contents of an initial asset file onto it.
Now that the mutable local file exists, read and write from it.
Here's a code sample of a class that takes care of that:
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
const initialAssetFile = 'assets/initial.json';
const localFilename = 'data.json';
class Repository {
/// Initially check if there is already a local file.
/// If not, create one with the contents of the initial json in assets
Future<File> _initializeFile() async {
final localDirectory = await getApplicationDocumentsDirectory();
final file = File('$localDirectory/$localFilename');
if (!await file.exists()) {
// read the file from assets first and create the local file with its contents
final initialContent = await rootBundle.loadString(initialAssetFile);
await file.create();
await file.writeAsString(initialContent);
}
return file;
}
Future<String> readFile() async {
final file = await _initializeFile();
return await file.readAsString();
}
Future<void> writeToFile(String data) async {
final file = await _initializeFile();
await file.writeAsString(data);
}
}
It will basically check before every read or write if the local file has already been created, and will take care of that if not.
Note that there's no JSON specific things happening here, this is just default file IO. JSON encoding/decoding should happen before/after this.
You'll just need to add an initial file into the assets/ folder and specify that in your pubspec.yaml as follows:
flutter:
assets:
- assets/initial.json # the file containing the initial data
# - assets/ # you can also just add the whole directory
Once that's done, you should be able to use readFile() and writeFile(String data) from this Repository class.
(It might be important to make sure WidgetsFlutterBinding.ensureInitialized(); has been called in order to interact with the local file system)
What's the best way to save an activity page state with Flutter provider package? Currently, using the provider package and doing a restart or closing the app the page gets rebuilt and all the new widgets are removed. The state is saved on hot reload not hot restart. For example, the post created by a user on the news feed page is removed when I close the app or restart it.
So following the comment and reading the flutter persistence cookbook, I see three common possibilites:
Persist data with SQLite
Read and write files
Store key-value data on disk
Each has different usecases, but I think the common case is to store a few objects as JSON. SQLite seems to me in simple cases an overkill and key-values do not offer enough flexibility. So I will focus on reading and writing files.
Here comes an example. I use JSON code generation libraries as described here.
The data object I read and write to disk is looks the following way:
import 'package:json_annotation/json_annotation.dart';
// json methods generated using code geneartion
// see https://flutter.dev/docs/development/data-and-backend/json#serializing-json-using-code-generation-libraries
part 'easy_data.g.dart';
#JsonSerializable()
class EasyData {
int counter;
String data;
EasyData(this.counter, this.data);
// run the following command to generate json:
// flutter pub run build_runner build
factory EasyData.fromJson(Map<String, dynamic> json) => _$EasyDataFromJson(json);
Map<String, dynamic> toJson() => _$EasyDataToJson(this);
}
The Service to load the json from disk and save looks the following way:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import '../model/easy_data.dart';
class FileStorage with ChangeNotifier {
EasyData loadedData;
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/app_data.json');
}
loadData() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
if (contents != null) {
// parse json if file was saved before
loadedData = EasyData.fromJson(json.decode(contents));
notifyListeners();
}
} on FileSystemException catch (_) {
// the file did not exist before
} catch (e) {
// error handling
log(e);
}
}
Future<File> writeData(EasyData data) async {
final file = await _localFile;
// Write the file
return file.writeAsString(jsonEncode(data.toJson()));
}
}
I used a provider for the service, because I want other services to be notified when the data is loaded from disk. The other Providers need to be declared with ChangeNotifierProxyProvider in main dart to use the information from the FileStorage Provider.
I hope it this was helpful.
I am using a flutter plugin named path_provider. I have to store image file at path_provider.getTemporaryDirectory(). Is the image stored here is deleted automatically or I have to do it explicitly.
from the documentation of path_provider
Files in this directory may be cleared at any time. This does not return
a new temporary directory. Instead, the caller is responsible for creating
(and cleaning up) files or directories within this directory. This
directory is scoped to the calling application.
So you are responsible for cleaning up, which means it is not automatically cleared, but it may be cleared any time
Edit
You can clear the temporary the directory as follows:
import 'dart:io';
....
Directory dir = await getTemporaryDirectory();
dir.deleteSync(recursive: true);
dir.create(); // This will create the temporary directory again. So temporary files will only be deleted
To keep things safe, I store all the file paths in list with flutter_secure_storage, then when I launch the app I browse all the file paths, check if file still exists (can be previously deleted by system) and delete it. Finally, I clear the list from flutter_secure_storage for next time.
AppSecureStorage.dart
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AppSecureStorage {
static const _storage = FlutterSecureStorage();
static const TEMP_FILES_PATH = 'temp-files-path';
static Future<String?> get(String key) async {
return await _storage.read(key: key);
}
static Future<void> set(String key, String? value) async {
if (value == null) {
await _storage.delete(key: key);
} else {
await _storage.write(
key: key,
value: value,
);
}
}
static Future<List<String>> getTempFilesPath() async {
final tempFilesPathValue = await get(
AppSecureStorage.TEMP_FILES_PATH,
);
List<String> tempFilesPathStrList = [];
if (tempFilesPathValue != null) {
List<dynamic> tempFilesPathDynamicList = jsonDecode(tempFilesPathValue);
for (var element in tempFilesPathDynamicList) {
tempFilesPathStrList.add(element.toString());
}
}
return tempFilesPathStrList;
}
static Future<void> addTempFile(String filePath) async {
final tempFilesPath = await AppSecureStorage.getTempFilesPath();
tempFilesPath.add(filePath);
AppSecureStorage.set(
AppSecureStorage.TEMP_FILES_PATH,
jsonEncode(tempFilesPath),
);
}
}
main.dart
void _handleTempFiles() async {
List<String> tempFilesPath = await AppSecureStorage.getTempFilesPath();
for (var element in tempFilesPath) {
final file = File(element);
// check if still exist : tmp file can be deleted by system
if (await file.exists()) {
file.delete();
}
}
AppSecureStorage.set(AppSecureStorage.TEMP_FILES_PATH, jsonEncode([]));
}