dart asyncronous code - should i choose static or non static parameter? - flutter

i was building an app with Flutter and Firebase and I encountered the following problem:
I have a firebase helper class with within all the methods I need to use firebase realtime database and I will call these from the main function.
this is the firebase helper class:
class FBHelper {
FBHelper._internal();
static final DatabaseReference database = FirebaseDatabase.instance.ref('/');
factory FBHelper() {
return FBHelper._internal();
}
Future<void> initializeFirebase() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
print('firebase app initialized');
}
Future<void> callPost(dynamic o) async {
await database.set(o);
}
}
and this is my main
void main() async {
final _ = FBHelper();
WidgetsFlutterBinding.ensureInitialized();
await _.initializeFirebase();
await _.callPost({"master": "1"});
runApp(const MyApp());
}
if I mark as static the DatabaseReference database property there are no problems at all.
instead, if I avoid the static modifier I get this
FirebaseException ([core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp())
can someone explain me what I’m missing? do I have a wrong idea of how to use static?

Related

Flutter ObjectBox watch

I'm using the objectbox with watch feature.
But if I use watch feature I cant close the store, Can I have any problem about this? with battery low or slow app? is there a better way to do it?
My code
Stream<List<Room>>> listenRoomUnreadMessage(){
try {
return objectBox.store
.box<RoomUnread>()
.query()
.watch(triggerImmediately: true)
.transform(
StreamTransformer.fromHandlers(
handleData: (query, sink) =>
sink.add(query.find().map((e) => e.toDomain()).toList()),
),
);
} catch (ex) {
rethrow;
}
}
and create store class
class ObjectBox {
late final Store store;
ObjectBox._create(this.store);
static Future<ObjectBox> create() async {
final store = await openStore();
return ObjectBox._create(store);
}
}
and I call create in main function
late ObjectBox objectBox;
void main() async {
objectBox = await ObjectBox.create();
runApp(const MaterialAppMain());
}
Thanks

Flutter - How to add and retrieve data to/from hive?

I know it sounds simple and I went through the example given in the documentation. Yet somehow I am unable to get it right.
This is what I have:
void main() async {
await Hive.initFlutter();
//Hive.openBox('workoutBox');
runApp(const MyApp());
}
...
Next Screen:
var box;
...
Trying to add to the box
Future<void> _save() async{
// save doc id somewhere
final Id = doc.id;
//box = await Hive.openBox('workoutBox');
box.put("Id", Id);
}
Trying to retrieve in another function:
var someId = box.get("Id");
Current error: get was called on null
My confusion is, where/how do you declare, open and retrieve from the box in this situation?
It seems you are forgetting to initialize a Box param and assign the value returned by the openBox function to it.
After Hive initialization you should have something like this:
Box<myValue> boxValue = await Hive.openBox("myKey");
Important: the retrieval method will dependend based on what you need to do and, more importantly, how you saved your data in the first place.
Let's say you saved data like this:
await boxValue.add(value);
By adding data like this, the key assigned to the value will be an auto-incremented one, so that trying to retrieve it with a specific key that never was assigned in the first place will fail.
If you did add the data like this:
await boxValue.put("myKey", value);
then you will be able to successfully fetch it using the intended key.
You can do the following:
void main() async {
await Hive.initFlutter();
await Hive.openBox('workoutBox'); //<- make sure you await this
runApp(const MyApp());
}
...
_save() { // <- can be a synchronous function
final box = Hive.box('workoutBox'); //<- get an already opened box, no await necessary here
// save doc id somewhere
final Id = doc.id;
box.put("Id", Id);
}
I have written an example app and a Flutter Cubits + Hooks + Hive DB tutorial. I have the following AppDatabase class there:
const String _bookBox = 'book';
#Singleton()
class AppDatabase {
AppDatabase._constructor();
static final AppDatabase _instance = AppDatabase._constructor();
factory AppDatabase() => _instance;
late Box<BookDb> _booksBox;
Future<void> initialize() async {
await Hive.initFlutter();
Hive.registerAdapter<BookDb>(BookDbAdapter());
_booksBox = await Hive.openBox<BookDb>(_bookBox);
}
Future<void> saveBook(Book book) async {
await _booksBox.put(
book.id,
BookDb(
book.id,
book.title,
book.author,
book.publicationDate,
book.about,
book.readAlready,
));
}
Future<void> deleteBook(int id) async {
await _booksBox.delete(id);
}
...

how to save data in Hive database when receiving data in the background?

I have an issue saving data to Hive when receiving Firebase Cloud Messaging (FCM) push notification data when the app is in the background.
I have a static method to set up hive like this
static Future<void> setUpHive() async {
try {
await Hive.initFlutter();
if (!Hive.isBoxOpen("Box Name")) {
await Hive.openBox("Box Name");
}
} catch (error) {
print(error.toString());
}
}
I use that setUpHive static method in main function like this
Future<void> main() async {
await HiveHelper.setUpHive();
runApp(
MyApp(),
);
}
when the app is in the background, and then it receives FCM message, then this code below will be called. after that I try change the data stored in the Hive box
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// when receive FCM message when app is in the background, this block will be executed
// set up the hive first
await HiveHelper.setUpHive();
// then I try to change the data stored in the Hive box
final myBox = Hive.box("BOX NAME");
myBox.put("key", 12345);
}
it seems okay after receiving FCM background data, but when I fully close the app, and the main called again I have error when trying to open the box like this
static Future<void> setUpHive() async {
try {
await Hive.initFlutter();
if (!Hive.isBoxOpen("Box Name")) {
await Hive.openBox("Box Name"); // Error in this line
}
} catch (error) {
print(error.toString());
}
}
the error is:
HiveError: This should not happen. Please open an issue on GitHub.
E/flutter (13142): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)]
Unhandled Exception: HiveError: This should not happen. Please open an
issue on GitHub. E/flutter (13142): #0 BinaryReaderImpl.readFrame
(package:hive/src/binary/binary_reader_impl.dart:250:7)
E/flutter
I try to find the solution, and I find similar issue from here about Using Hive DB in a Background Process and it is said
leisim:
Unfortunately, Hive does not support opening boxes in multiple
isolates. That means you can either close the box in the main isolate,
update it in your background isolate and reopen it in the main isolate
or you pass the data from the background to the main isolate and
perform the update there...
I am new in Flutter, and I don't understand what he said. please help :(
You can try the following code. The basic idea is to send data from background isolate to main isolate.
Future<void> backgroundMessageHandler(RemoteMessage msg){
IsolateNameServer.lookupPortByName('main_port')?.send(msg);
}
#override
void initState(){
super.initState();
ReceivePort receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort,'main_port');
receivePort.listen((message) {
if(message is RemoteMessage){
//TODO: save your data in hive box
}
}
}
You need to close your hive box in the main isolate once app goes into background. When it does, you need to CRUD in the background isolate. If you want to sync data between two isolates (because they don't share the same hive data) then you need a two way communication between isolates.
Here is an example code of communicating between two isolates.
import 'dart:io'; // for exit();
import 'dart:async';
import 'dart:isolate';
Future<SendPort> initIsolate() async {
Completer completer = new Completer<SendPort>();
ReceivePort isolateToMainStream = ReceivePort();
isolateToMainStream.listen((data) {
if (data is SendPort) {
SendPort mainToIsolateStream = data;
completer.complete(mainToIsolateStream);
} else {
print('[isolateToMainStream] $data');
}
});
Isolate myIsolateInstance = await Isolate.spawn(myIsolate, isolateToMainStream.sendPort);
return completer.future;
}
void myIsolate(SendPort isolateToMainStream) {
ReceivePort mainToIsolateStream = ReceivePort();
isolateToMainStream.send(mainToIsolateStream.sendPort);
mainToIsolateStream.listen((data) {
print('[mainToIsolateStream] $data');
exit(0);
});
isolateToMainStream.send('This is from myIsolate()');
}
void main() async {
SendPort mainToIsolateStream = await initIsolate();
mainToIsolateStream.send('This is from main()');
}
for more go to https://medium.com/#lelandzach/dart-isolate-2-way-communication-89e75d973f34

Flutter Unhandled exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized

I am trying to create an Isolate un Flutter and then use this isolate to fetch some data from Firebase Realtime Database.
I am creating de Isolate in a file called home.dart (not main) and here is my code for that file. I have a class to create the Isolate and the function for the Isolate to execute. Inside this function I am trying to fetch the data.
void elIsolate(SendPort sPort) async {
print("Fetching data");
final databaseReference = FirebaseDatabase.instance.reference().child("categories");
DataSnapshot info;
/*databaseReference.once().then((DataSnapshot snapshot) {
info = snapshot;
print(info.value);
});*/
print("new isolate created");
IsolateChannel channel = IsolateChannel.connectSend(sPort);
channel.stream.listen((data) {
print('newIsolate received : $data');
});
channel.sink.add("hi");
}
class _MyHomePageState extends State<MyHomePage> {
List list = [];
void initState(){
WidgetsFlutterBinding.ensureInitialized();
super.initState();
print("Init state");
loadIsolate();
}
Future loadIsolate() async {
await Firebase.initializeApp();
print("Load isolate");
ReceivePort rPort = ReceivePort();
IsolateChannel channel = IsolateChannel.connectReceive(rPort);
channel.stream.listen((data) {
print('rootIsolate received : $data');
channel.sink.add('How are you');
});
await Isolate.spawn(elIsolate, rPort.sendPort);
/*await Isolate.spawn(getAllWorkers, receivePort.sendPort);
receivePort.listen((message) {
print(message);
});*/
}
}
Then I have my main.dart. I added this line inside the main function: WidgetsFlutterBinding.ensureInitialized();
Here is my code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
bool resp;
await SharedPreferences.getInstance().then((prefs) {
resp = prefs.getBool('isUser');
if (resp == null) {
FirebaseAuth _auth = FirebaseAuth.instance;
resp = (_auth.currentUser != null);
prefs.setBool('isUser', resp);
}
});
runApp(MyApp(user: resp));
}
flutter_isolate: ^2.0.2
onPressed: () {
FlutterIsolate.spawn(_isolateEntrypoint, "");
}
// A "top level" function (i.e. not inside a class or make it static)
_isolateEntrypoint(String foo) {
WidgetsFlutterBinding.ensureInitialized();
...
}
Make sure that authorization and initialization were made on the same main thread (top level or static).
Now this FlutterEngine will be able to communicate with Firebase Realtime Database but the main FlutterEngine won't. In practice, depending on the app, an app may want to communicate with Realtime Database from either engine (or both). In background apps, more likely from here rather than the main isolate, but again that depends on the app.

Box not found. Did you forget to call Hive.openBox()?

When working with HIVE database in flutter. If you ever get error like this:
"Box not found. Did you forget to call Hive.openBox()?"
It means you haven't opened your box to
To resolve this issue call
await Hive.openBox("boxname");
before using the box
It means you haven't opened your box. To resolve this issue call
await Hive.openBox("boxname");
before using the box.
The box needs to be open either at the beginning, after database initialization or right before doing the operation on the box.
For example in my AppDatabase class I have only one box ('book') and I open it up in the initialize() method, like below:
The whole application and tutorial is here.
const String _bookBox = 'book';
#Singleton()
class AppDatabase {
AppDatabase._constructor();
static final AppDatabase _instance = AppDatabase._constructor();
factory AppDatabase() => _instance;
late Box<BookDb> _booksBox;
Future<void> initialize() async {
await Hive.initFlutter();
Hive.registerAdapter<BookDb>(BookDbAdapter());
_booksBox = await Hive.openBox<BookDb>(_bookBox);
}
Future<void> saveBook(Book book) async {
await _booksBox.put(
book.id,
BookDb(
book.id,
book.title,
book.author,
book.publicationDate,
book.about,
book.readAlready,
));
}
Future<void> deleteBook(int id) async {
await _booksBox.delete(id);
}
Future<void> deleteAllBooks() async {
await _booksBox.clear();
}
}
You have to open the box you want to use and make sure to use await while using the openBox() function.
await Hive.openBox("boxname");