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

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

Related

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

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?

How to provide Future to inside Providers using Riverpod?

I'm trying to learn Riverpod with clean architecture.
I have following set/chain of providers:
final databaseFutureProvider = FutureProvider<Database>((ref) async {
final db = await DatabaseHelper().createDatabase(); // this is async because 'openDatabase' of sqflite is async
return db;
});
final toDoDatasourceProvider = Provider<ToDoDatasource>((ref) {
final db = ref.watch(databaseFutureProvider); // problem is here!!
return ToDoDatasourceImpl(db: db);
});
final toDoRepositoryProvider = Provider<ToDoRepository>((ref) {
final ds = ref.watch(toDoDatasourceProvider);
return ToDoRepositoryImpl(ds);
});
I am probably missing some small things or doing it completely wrong. How to properly provide DB (that is async in its nature)?
You don't need multiple providers, in your case since you would need ToDoRepository always you can just Initialized before running the app and use it later without worrying about the database connection state
Future<void> main(List<String> args) async {
// Initialization the db
final db = await DatabaseHelper().createDatabase();
ProviderScope(
overrides: [
// pass the db
toDoRepositoryProvider.overrideWithValue(db),
],
child: RootApp(),
);
}
final toDoRepositoryProvider = Provider<ToDoRepository>((ref) {
throw UnimplementedError();
});
I totally agree with Mohammed Alfateh's decision. In addition, you can use ProviderContainer()..read(toDoDatasourceProvider) and UncontrolledProviderScope, to asynchronously assign values in main method. And in ToDoDatasourceImpl call the async method init() to assign a value in the field late final db.

Future<dynamic> is not a subtype of List<dynamic>

So I am trying to pass a list of String values from firestore table, but I am getting an exception type 'Future<dynamic>' is not a subtype of type 'List<dynamic>'
This is the function
getLectureList(String userId) async {
var collection = FirebaseFirestore.instance.collection('students');
var docSnapshot = await collection.doc(userId).get();
Map<String, dynamic>? data = docSnapshot.data();
List<String> _lectureList =
await data!['attendance']; //This line is kinda giving me trouble
userInfo = FirestoreWrapper()
.getStudentFromData(docId: currentUser(), rawData: data);
return _lectureList;
}
And this is the function where I am getting the exception thrown
#override
void initState() {
lectureList = getLectureList(currentUser()); // Getting an exception here
NearbyConn(context).searchDevices(devices: deviceList);
super.initState();
}
tried using await in the getLectureList() method but still getting the same problem
Why do you await your data? You already got it.
List<String> _lectureList = data!['attendance'];
Please note that I don't know what your data structure looks like, so I cannot tell you if this is correct, I can only tell you that it is more correct than before, because the await did not belong there.
You are getting an exception here lectureList = getLectureList(currentUser()); because the the parameter required by the getLectureList() method is the userId which is a string. I do not know what currentUser() return but I'm assuming it's the userId that you need when calling the getLectureList() method. Based on the error, it looks like currentUser() is an async method that returns a future after some time.
You're not awaiting that future. You shouldn't make the initState() method async so move the code block out of it into a separate method and then call it from initState().
Something like this,
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
lectureList =
getLectureList(await currentUser());
NearbyConn(context).searchDevices(devices: deviceList);
}
or
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
String _userID = await currentUser();
lectureList = getLectureList(_userID);
NearbyConn(context).searchDevices(devices: deviceList);
}
Which I recommend so you can see all the parts.
Making your method parameters required named parameters also help you to easily see what is needed to pass to a function/class/.
Eg.
getLectureList({required String userId}){
...
}
Your IDE will alert you on the type of object the function requires and it makes things clearer.
Ultimately, I think typing your classes makes it so much more easier to fetch data from fireStore Typing CollectionReference and DocumentReference
This way you can easily do this,
final moviesRef = FirebaseFirestore.instance.collection('movies').withConverter<Movie>(
fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
toFirestore: (movie, _) => movie.toJson(),
);
and get your data this way,
Future<void> main() async {
// Obtain science-fiction movies
List<QueryDocumentSnapshot<Movie>> movies = await moviesRef
.where('genre', isEqualTo: 'Sci-fi')
.get()
.then((snapshot) => snapshot.docs);
// Add a movie
await moviesRef.add(
Movie(
title: 'Star Wars: A New Hope (Episode IV)',
genre: 'Sci-fi'
),
);
// Get a movie with the id 42
Movie movie42 = await moviesRef.doc('42').get().then((snapshot) => snapshot.data()!);
}
Keeps everything dry and tidy.
< The data comes to list format thats why showing the exception of datatype >
List<String> lectureList = await getLectureList(currentUser()); // use
Future<List<String>> getLectureList(String userId) async {
- your code -
}
Instead of
List _lectureList =
await data!['attendance'];
Try this
_lectureList = await data![] As List

Flutter SharedPreferences getInstance return null

Although I set the _sharedPreferences in the constructor, it gets null in getUsername. I don't know missing what:
class PreferencesProvider {
SharedPreferences _sharedPreferences;
PreferencesProvider() {
SharedPreferences.getInstance().then((prefs) => _sharedPreferences = prefs);
}
String getUsername() {
return _sharedPreferences.getString("Username");
}
String getX() {
return _sharedPreferences.getString("X");
}
String getY() {
return _sharedPreferences.getString("Y");
}
String getZ() {
return _sharedPreferences.getString("Z");
}
}
alternatively it didn't work either:
class LoginProvider {
SharedPreferences _sharedPreferences;
LoginProvider._internal();
static final LoginProvider _instance = LoginProvider._internal();
factory LoginProvider() {
_instance.initPreferences();
return _instance;
}
initPreferences() async {
_sharedPreferences = await SharedPreferences.getInstance();
}
I want to use this in MaterialApp:
initialRoute: PreferencesProvider().isLoggedIn() ? "MainPage" : "LoginPage"
Edit: I know I should use await. But then keyword isn't same? I don't want to wait the instance again for all returns. In the other hand, I can't use await in initialRoute.
The way i manage to login the user for my application for the similar scenario is,
String startPage="LoginPage";
void main() {
SharedPreferences prefs = await SharedPreferences.getInstance();
String user=prefs.getString("Username");
if(user!=null && user.length>0){
startPage="MainPage";
}
runApp(MyApp());
}
Now, set your initialRoute as follow,
initialRoute: startPage,
This solution works in every scenario because i am fetching the data before the runApp() function in my application. Your application renders your initialPage after calling the runApp() function.
This is the best way to manage your login page based on data retrieval from the sharedpreferences as SharedPreferences takes time to fetch the data. Till the data is retrieved from sharedpreferences your build method gets completed its UI rendering.
While using preferences you should use Future, await and async
Future<String> getUsername() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String storeUserDetails = prefs.getString("Username");
return (storeUserDetails != null);
}
Hope this helps!
You need to wait a little bit for get username from shared preferences. getInstance is an async process.
Below code will work, because getString will work after getInstance
Future<String> getUsername() async {
_sharedPreferences = await SharedPreferences.getInstance();
return _sharedPreferences.getString("Username");
}
You need to modify your PreferencesProvider class

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