How to store a List in Hive (Flutter)? - flutter

I am trying to store and fetch a list using Hive in Flutter, however I get a range error.
int steps = 0;
List<int> stepCountList = List(7);
var time = DateTime.now();
// This is my method for a listener that updates when it detects a change,
void _onData(int newValue) async {
fetchSteps();
steps = stepCountList[time.weekday] ?? 0;
stepDivider += newValue;
stepCountList.insert(time.weekday - 1, steps);
moneyLottoBox.put('stepCountList', stepCountList);
}
void fetchSteps() {
stepCountList = moneyLottoBox.get('stepCountList');
if (stepCountList == null) {
moneyLottoBox.put('stepCountList', <int>[7]);
stepCountList = moneyLottoBox.get('stepCountList');
}
}
// I create my MoneyLotto box here,
var moneyLottoBox = Hive.box('moneyLottoBox');
Future<void> main async {
moneyLottoBox = await Hive.openBox('box');
}
Today being Saturday for me, the value of time.weekday for me is 6, however it shows me the error when I try to print(stepCountList[6])
RangeError (index): Invalid value: Only valid value is 0: 6

You can't use insert() method on fixed length List, by fixed length I mean when you declare it this way List<int> stepCountList = List(7);
Edited the code, now it should work
void _onData(int newValue) async {
fetchSteps();
steps = stepCountList[time.weekday] ?? 0;
stepDivider += newValue;
//stepCountList.insert(time.weekday - 1, steps);this is not correct
stepCountList[time.weekday -1] = steps; // this should be the approach
moneyLottoBox.put('stepCountList', stepCountList);
}
void fetchSteps() {
stepCountList = moneyLottoBox.get('stepCountList');
if (stepCountList == null) {
/* moneyLottoBox.put('stepCountList', <int>[7]); this statement is the
reason for your error because your are storing a growable list instead of fixed list in the hive */
moneyLottoBox.put('stepCountList', List<int>(7));// try this instead
// or u can use this moneyLottoBox.put('stepCountList', stepCountList);
stepCountList = moneyLottoBox.get('stepCountList');
}
}

Hive box has methods like: put(dynamic key, E value), putAll(Map<dynamic, E> entries) and putAt(int index, E value). For example, I used putAt() to update entry at the specific key.
For adding with auto-increment key, you want to use add(E value) or to add a list - addAll(Iterable<E> values).
And when I wanted to delete all data in the box, I used clear(), like so:
await _booksBox.clear();
And this method is for getting all the entries from the database:
List<Book> getAllBooks() {
return _booksBox.values.map(_bookFromDb).toList();
}
A complete Hive db class is from the tutorial.

Related

Not able to change the postion of items in hive database

Not able to swap the position of the record in hive database
getting error like Unhandled Exception: HiveError: The same instance of an HiveObject cannot be stored with two different keys ("5" and "10").
code:
Box<CounterDetails> box = await Hive.openBox<CounterDetails>(kHiveBoxName);
if (oldIndex < newIndex) {
newIndex -= 1;
}
// this is required, before you modified your box;
final oldItem = box.getAt(oldIndex);
final newItem = box.getAt(newIndex);
// here you just swap this box item, oldIndex <> newIndex
box.putAt(oldIndex, newItem!);
box.putAt(newIndex, oldItem!);
As for the error, you can create copyWith method on CounterDetails that will return new instance. Or create new instance duplicating fields. Then put this new object.
The copyWith method will be like
class CounterDetails {
final String a;
CounterDetails({
required this.a,
});
CounterDetails copyWith({
String? a,
}) {
return CounterDetails(
a: a ?? this.a,
);
}
}
And putting those will be like
box.putAt(oldIndex, newItem!.copyWith()); //better do a null check 1st , same for next one

Existing state editing in Riverpod / Flutter

How I can manipulate the existing state in Riverpod. 'm a beginner about Flutter and Riverpod. When I try add one to Order error pops up and says:
Error: A value of type 'int' can't be assigned to a variable of type
'List'.
final OrderPaperProvider = StateNotifierProvider<OrderPaper, List<Order>>((ref) {
return OrderPaper();
});
#immutable
class Order {
final String product_id;
final String product_name;
final int product_price;
final int product_count;
Order({required this.product_id, required this.product_name, required this.product_price, required this.product_count});
Order copyWith({product_id, product_name, product_price, product_count}) {
return Order(
product_id: product_id,
product_name: product_name,
product_price: product_price,
product_count: product_count,
);
}
}
class OrderPaper extends StateNotifier<List<Order>> {
OrderPaper() : super([]);
void addOrder(Order order) {
for (var x = 0; x < state.length; x++) {
if (state[x].product_id == order.product_id) {
addOneToExistingOrder(x, order);
return;
}
}
state = [...state, order];
}
void removeOrder(String product_id) {
state = [
for (final order in state)
if (order.product_id != order) order,
];
}
void addOneToExistingOrder(index, Order order) {
state = state[index].product_count + 1; // <--------- Error in this line
}
void clearOrderPaper() {
state = [];
}
}
What is happening in the code you posted is basically this:
You are telling it to update the state which is a List<Order> with an integer.
This is because state[index].product_count + 1 actually equals a number, for example 1+2 = 3.
So you are telling it, state = 3, and this causes your error.
What you need to do, is create a new state with the list of items and the edited item, like this (you don't need to pass the index, you can get it in the function):
void addOrder(Order order) {
//find if the product is in the list. The index will not be -1.
final index = state.indexWhere((entry) => entry.product_count == order. product_count);
if (index != -1) {
//if the item is in the list, we just edit the count
state = [
...state.sublist(0, index),
order.copyWith(product_count: state[index].product_count + 1),
...state.sublist(index + 1),
];
}else {
//otherwise, just add it as a new entry.
state = [...state, order],
}
}

Is this flutter (riverpod with sqfite) code optimal/correct for manual reordering a list? (code attached)

Is this flutter (riverpod/StateNotifier) code optimal/correct for the use case of manually re-ordering a list, and for which order is persisted in SQLite (using sqfite)? Being new to riverpod aspects I'm seeking clarity on:
manually calculating database updates for "orderNum" field needed for the rows impacted
separately/manually then calculated updates to Riverpod state (?)
need to iterate through Riverpod state and create new records (using copyWith) when required as you can't "update" Riverpod state objects? (I guess this is the concept of riverpod)
overall am I on track for simplest way to reorder manually a list in flutter when using Riverpod/StateNotifer for internal state, and SQLite for persisting? (my code just seems complex)
ReOrder Function in StateNotifier
void reorderLists({required int oldIndex, required int newIndex}) async {
// Step 1 - Validation & prep (check state isn't loading or error)
if ((state is! AsyncData)) {
developer.log("WARRNING: Current state: ${state.toString()}");
return;
}
List<Todolist> _currTodolists = state.asData!.value.data;
// Index Ajustment for Flutter Reorderable List
bool moveDown = newIndex > oldIndex;
int startIndex = oldIndex;
int destIndex = moveDown ? (newIndex - 1) : newIndex;
// List to store database updates required
List<TodolistEntity> databaseUpdates = [];
// Determine Order Updates - First Item
databaseUpdates.add({
'id': _currTodolists[startIndex].id,
'orderNum': _currTodolists[destIndex].orderNum,
});
// Determine Order Updates - Remaining Items
int loopStart = moveDown ? startIndex + 1 : destIndex;
int loopEnd = moveDown ? destIndex : startIndex - 1;
int loopInc = moveDown ? -1 : 1;
int loopCurr = loopStart;
while (loopCurr <= loopEnd) {
databaseUpdates.add({
'id': _currTodolists[loopCurr].id,
'orderNum': _currTodolists[loopCurr].orderNum + loopInc,
});
loopCurr += 1;
}
// Update database
await _todoRepos.updateOrder(databaseUpdates);
// Update State (Riverpod) via changes to existing state Objects
_logStateData();
for (var dbUpdate in databaseUpdates) {
_currTodolists.asMap().forEach((index, value) {
if (value.id == dbUpdate['id']) {
_currTodolists[index] = _currTodolists[index].copyWith(orderNum: dbUpdate['orderNum']);
}
});
}
_currTodolists.sort( (a,b) => a.orderNum.compareTo(b.orderNum));
state = AsyncData(state.asData!.value.copyWith()); // TODO: This was required to trigger UI refresh - why?
}
UpdateOrder function for DB update (uses sqfite)
#override
Future<void> updateOrder(List<TodolistEntity> entityList) async {
Database _db = await DatabaseHelper.instance.database;
await _db.transaction((txn) async {
var batch = txn.batch();
for (final entity in entityList) {
int tableId = entity['id'];
batch.update(
"todo_list",
entity,
where: 'id = ?',
whereArgs: [tableId],
);
}
await batch.commit();
});
}
State riverpod / freezed classes
#freezed
abstract class Tododataset with _$Tododataset{
const factory Tododataset({
#Default([]) List<Todolist> data,
}) = _Tododataset;
}
#freezed
abstract class Todolist with _$Todolist{
const factory Todolist({
required int id,
required int orderNum,
required String listTitle,
#Default([]) List<Todo> items,
}) = _Todolist;
}

How to use a variable for method name

I want to use a variable to access a certain value in my hive database:
In the code below if I use myBox.getAt(i).attributeSelect I get an error because attributeSelect is not defined for the box.
If I use myBox.getAt(i).test it works. How can I make flutter recognise that attributeSelect is a variable and put the value there? I have a total of 181 different variables the user can choose from. Do I really need that many if clauses? The variables are booleans. So I want to check if that attribute is true for the document at index i.
Error: NoSuchMethodError: 'attributeSelect'
method not found
Receiver: Instance of 'HiveDocMod'
attributeSelect = 'test'; //value depends on user choice
Future<void> queryHiveDocs() async {
final myBox = await Hive.openBox('my');
for (var i = 0; i < myBox.length; i++) {
if (attributeSelect == 'All Documents') {
_hiveDocs.add(myBox.getAt(i)); // get all documents
//print(myBox.getAt(24).vesselId);
} else {
// Query for attribute
if (myBox.getAt(i).attributeSelect) {
_hiveDocs.add(myBox.getAt(i)); // get only docs where the attributeSelect is true
}
}
}
setState(() {
_hiveDocs = _hiveDocs;
_isLoading = false;
});
}
I solved it the annoyingly hard way:
if (attributeSelect == 'crsAirCompressor') {
if (myBox.getAt(i).crsAirCompressor) {
_hiveDocs.add(myBox.getAt(i));
}
} else if (attributeSelect == 'crsBatteries') {
if (myBox.getAt(i).crsBatteries) {
_hiveDocs.add(myBox.getAt(i));
}...

How to map each item from observable to another one that comes from async function?

I want to
1.map item from observable to another one if it has already saved in database.
2.otherwise, use it as it is.
and keep their order in result.
Saved item has some property like tag, and item from observable is 'raw', it doesn't have any property.
I wrote code like this and run testMethod.
class Item {
final String key;
String tag;
Item(this.key);
#override
String toString() {
return ('key:$key,tag:$tag');
}
}
class Sample {
///this will generate observable with 'raw' items.
static Observable<Item> getItems() {
return Observable.range(1, 5).map((index) => Item(index.toString()));
}
///this will find saved item from repository if it exists.
static Future<Item> findItemByKey(String key) async {
//simulate database search
await Future.delayed(Duration(seconds: 1));
if (key == '1' || key == '4') {
final item = Item(key)..tag = 'saved';
return item;
} else
return null;
}
static void testMethod() {
getItems().map((item) async {
final savedItem = await findItemByKey(item.key);
if (savedItem == null) {
print('not saved:$item');
return item;
} else {
print('saved:$savedItem');
return savedItem;
}
}).listen((item) {});
}
The result is not expected one.
expected:
saved:key:1,tag:saved
not saved:key:2,tag:null
not saved:key:3,tag:null
saved:key:4,tag:saved
not saved:key:5,tag:null
actual:
not saved:key:2,tag:null
not saved:key:3,tag:null
not saved:key:5,tag:null
saved:key:1,tag:saved
saved:key:4,tag:saved
How to keep their order in result?
I answer myself to close this question.
According to pskink's comment, use asyncMap or concatMap solve my problem. Thanks!!
below is new implementation of testMethod.
asyncMap version:
getItems().asyncMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return savedItem;
else
return Future.value(item);
}).listen(print);
concatMap version:
getItems().concatMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return Observable.fromFuture(savedItem);
else
return Observable.just(item);
}).listen(print);