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],
}
}
Related
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 i can update my state in multidimensional list like screen. I can update state in first list level, but if i go on next level, state won't update
[0]:MegaFileModel (FileM(id: 714a55f4-d620-4fcb-a723-16af2c6115e3, fileName: Альбомы, extension: folder, preview: null, type: DIRECTORY, uploadUrl: null, serverfileName: null, addDate: 14.12.2021 09:04:14, checked: false))
addDate:"14.12.2021 09:04:14"
checked:false
content:List (3 items)
dir:Map (2 items)
extension:"folder"
fileName:"Альбомы"
id:"714a55f4-d620-4fcb-a723-16af2c6115e3"
preview:null
serverfileName:null
type:"DIRECTORY"
uploadUrl:null
hashCode:92220431
isDirectory:true
runtimeType:Type (MegaFileModel)
This is my function which i call every time when i want update state
List<MegaFileModel> getNewContentListState(List<MegaFileModel> state,
List<MegaFileModel> newState, bool value, String id) {
for (var i = 0; i < state.length; i++) {
if (state[i].id == id) {
newState.add(state[i].copyWith(checked: value, content: []));
} else {
newState.add(state[i].copyWith(content: []));
}
if (state[i].type == 'DIRECTORY') {
getNewContentListState(
state[i].content, newState[i].content, value, id);
}
}
return newState;
}
void doPick(bool value, String id) {
List<MegaFileModel> newState = [];
state = getNewContentListState(state, newState, value, id);
}
I am new to riverpod and trying to figure out a state management issue.
I have one list, and two independent states which need access to the list. The states also need to select an element of the list and know what its index is. The list can totally change (add or remove elements) and the states need to determine if their selected element is still in the list and update the index accordingly (to 0 if it is not found)
Here is an example with Riverpod in pure dart:
import 'package:riverpod/riverpod.dart';
void main(List<String> arguments) {
final container = ProviderContainer();
List<String> names = ["Jack", "Adam", "Sally"];
container.read(nameListProvider.notifier).setNames(names);
//selectedName1 = "Adam"
//selectedIndex1 = 1
//selectedName2 = "Sally"
//selectedIndex2 = 2
names.remove('Adam');
container.read(nameListProvider.notifier).setNames(names);
// print(selectedName1) = "Jack" // set to 0 because selection was removed
// print(selectedIndex1) = 0
// print(selectedName2) = "Sally"
// print(selectedIndex2) = 1 // decrement to match Sally's new list index
}
final nameListProvider =
StateNotifierProvider<NameListNotifier, List<String>>((ref) {
return NameListNotifier();
});
class NameListNotifier extends StateNotifier<List<String>> {
NameListNotifier() : super([]);
setNames(List<String> names) {
state = names;
}
}
But I need the selected Name and Index to be Providers
Update: Here is my more elegant solution:
import 'package:riverpod/riverpod.dart';
void main(List<String> arguments) {
final container = ProviderContainer();
List<String> names = ["Jack", "Adam", "Sally"];
print(container.read(nameListProvider));
container.read(nameListProvider.notifier).setNames(names);
var first = container.read(selectionProvider(1).notifier);
first.setName(1);
print(container.read(selectionProvider(1)).name);
var second = container.read(selectionProvider(2).notifier);
second.setName(2);
print(container.read(selectionProvider(2)).name);
names.remove('Adam');
List<String> newNames = List.from(names);
container.read(nameListProvider.notifier).setNames(newNames);
print(container.read(selectionProvider(1)).name);
print(container.read(selectionProvider(1)).index);
print(container.read(selectionProvider(2)).name);
print(container.read(selectionProvider(2)).index);
print(container.read(nameListProvider));
}
final selectionProvider =
StateNotifierProvider.family<SelectionNotifier, Selection, int>(
(ref, page) {
return SelectionNotifier(ref.read);
});
class SelectionNotifier extends StateNotifier<Selection> {
Reader _read;
SelectionNotifier(this._read) : super(Selection());
update() {
final String? selectedName = state.name;
final List<String> names = _read(nameListProvider);
if (names == []) {
state = Selection();
return null;
}
if (selectedName == null) {
state = Selection(name: names[0], index: 0);
return;
}
int i = names.indexOf(selectedName);
if (i == -1) {
state = Selection(name: names[0], index: 0);
return;
}
state = Selection(name: selectedName, index: i);
return;
}
setName(int index) {
final List<String> names = _read(nameListProvider);
state = Selection(name: names[index], index: index);
}
}
final nameListProvider =
StateNotifierProvider<NameListNotifier, List<String>>((ref) {
return NameListNotifier(ref.read);
});
class NameListNotifier extends StateNotifier<List<String>> {
Reader _read;
NameListNotifier(this._read) : super([]);
setNames(List<String> names) {
state = names;
_read(selectionProvider(0).notifier).update();
_read(selectionProvider(1).notifier).update();
}
}
class Selection {
final String? name;
final int? index;
Selection({this.name, this.index});
}
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.
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);