I'm looking for the best approach to update an ObservableList with objects.
I've created a dummy example below with Car and a store with Cars.
In JavaScript I can simply call
const carData = {id: 1, name: "Chrysler"};
update(carData);
and in the update method:
#action
updateCar(carData) {
cars = cars.map(car => car.id === carData.id ? {...car, ...carData} : car);
}
How do one achieve the same in Flutter with MobX?
class Car {
int id;
String model;
DateTime year;
}
abstract class _CarCollectionStore with Store {
#observable
ObservableList<Car> cars = ObservableList<Car>();
#computed
get latestCar() => iterating and getting latest Car by year.
#action
updateCar(WHICH PARAMETERS?) {
...
}
}
How do I actually update the Name of the latestCar?
carCollectionStore = Provider.of<CarCollectionStore>(context, listen: false);
carNameController = TextEditingController(text: carCollectionStore.latestCar.name);
carNameController.addListener(() {
carCollectionStore.updateCar(carNameController.text);
});
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;
}
Looking for a clean way to parse out data from an API.
The API returns data for creating “Boat” and “Car” models wrapped up in metadata.
{
“timestamp” “0000”,
“data”: {
“count” “1”,
“results” [
{
“name”: “boatyMcBoatFace”
}
]
}
}
I want to create 2 classes for the metadata and also the boat/car data using custom fromJson() methods.
Current implementation is something like this
Class Boat {
String name;
Boat.fromJson(json) {
this.name = json[‘name’]
}
static List<Boat> listFromJson(List<dynamic> json) {
return json.map((c) => Boat.fromJson(c)).toList();
}
}
ResponseModel<T> {
String timestamp
DataModel data
ResponseModel.fromJson(json) {
this.timestamp = json[‘timestamp’]
this.data = DataModel<T>.fromJson(json[‘data’])
}
}
DataModel<T> {
String count
List<T> results
DataModel.fromJson(json) {
this.count = json[‘count’]
this.results = json[‘results’] // change this, see below
}
}
Currently I'm creating a ResponseModel, which in turn creates a DataModel.
But then I'm manually creating and setting Boats using:
// yes
final res = methodThatMakesHttpRequest();
final apiResponse = ResponseModel<Boat>.fromJson(res);
// no
final boats = Boat.listFromJson(apiResponse.data.results);
apiResponse.data.results = boats;
Ideally I would remove those last two lines, and instead have Boats get created dynamically within DataModel.fromJson with something like
DataModel.fromJson(json) {
this.count = json[‘count’]
T.listFromJson(json[‘results’])
}
But this of course does not work as T.listFromJson does not exist.
Let's say I have 2 simple tables Users and Orders:
Users has columns Id
Orders has columns Id and UserId
How do I get all orders of a user easily using moor_flutter and return it as a stream of the following model?
class UserModel {
final String id;
final List<OrderModel> orderModels;
UserModel(this.id, this.orders);
}
class OrderModel {
final String id;
OrderModel(this.id);
}
This is the official documentation but it is not covering this use case.
Equivalent call with EFCore and C# would be:
public async Task<IEnumerable<UserModel>> GetUserOrders(String userId)
{
return await _db.Users
.Include(user => user.Orders)
.Where(user => user.Id == userId)
.Select(user => new UserModel
{
Id = user.Id,
OrderModels = user.Orders.Select(order => new OrderModel
{
Id = null,
}).ToList()
})
.ToListAsync();
}
I faced the same problem recently. I was confused to use Joins as per the given example in documentation.
What I have done is:
The created class (in database file only. see below example) has two objects which you want to join(combine). I wrote the query in a class of database.
You will get better understanding with example:
#UseMoor(
tables: [OfflineProductMasters, OfflineProductWiseStocks],
)
class MyDatabase extends _$MyDatabase {
// we tell the database where to store the data with this constructor
MyDatabase() : super(_openConnection());
// you should bump this number whenever you change or add a table definition. Migrations
// are covered later in this readme.
#override
int get schemaVersion => 1;
Future<List<ProductWithStock>> getProductWiseStock(String searchString, int mappingOfflineSalesTypeId) async {
try {
final rows = await (select(offlineProductMasters).join([innerJoin(offlineProductWiseStocks, offlineProductMasters.itemId.equalsExp(offlineProductWiseStocks.itemId))])
..where(offlineProductWiseStocks.salesTypeId.equals(mappingOfflineSalesTypeId) & offlineProductMasters.productName.like('$searchString%'))
..limit(50)
).get();
return rows.map((row) {
return ProductWithStock(
row.readTable(offlineProductMasters),
row.readTableOrNull(offlineProductWiseStocks),
);
}).toList();
}catch(exception){
print(exception);
return Future.value([]);
}
}
}
class ProductWithStock {
final OfflineProductMaster offlineProductMaster;
final OfflineProductWiseStock? productWithStock;
ProductWithStock(this.offlineProductMaster, this.productWithStock);
}
Now you got the structure that how you can use this type of query. Hope you will write your query in this way.
I don't know whether you have solved it or not. If solved then please post the answer so others can get help.
Thank you.
This feels like a hacky workaround but what I ended up doing is that I created 2 classes called HabitWithLogs and HabitModel. I put my query result into HabitWithLogs instances and then group them into HabitModel instances.
Data classes:
class HabitWithLog {
final Habit habit;
final HabitLog? habitLog;
HabitWithLog({required this.habit, required this.habitLog}) : assert(habitLog == null || habitLog.habitId == habit.id);
}
class HabitModel {
final Habit habit;
final List<HabitLog> habitLogs;
HabitModel({required this.habit, required this.habitLogs});
}
Dao method:
Future<List<HabitModel>> getAllHabits() async {
// Get habits and order
final query0 = (_db.select(_db.habits)..orderBy([(t) => OrderingTerm(expression: t.order, mode: OrderingMode.asc)]));
// Join with habit logs
final query1 = query0.join([
leftOuterJoin(_db.habitLogs, _db.habitLogs.habitId.equalsExp(_db.habits.id)),
]);
// Naive way that return the same habit multiple times
final hwlList = query1.map((rows) => HabitWithLog(
habit: rows.readTable(_db.habits),
habitLog: rows.readTableOrNull(_db.habitLogs),
));
// Group hwlList by habits
final groups = (await hwlList.get()).groupListsBy((hwl) => hwl.habit);
// Map grouping
return groups.entries
.map((group) => HabitModel(
habit: group.key,
habitLogs: (group.value[0].habitLog == null) ? List<HabitLog>.empty() : group.value.map((hwl) => hwl.habitLog!).toList(),
))
.toList();
}
Mapping the stream feels terrible and should not be the only way to achieve this.
i have one List (growable) with an item (actually item 0:
items is of class Team
items[_id = 1, _team = "Team01", _note = "blabla"]
and I want to transfer it into another list with a different structure:
participants is of class User
participants[id = 1, name = "participant1"]
skipping the note and translating _id into id and so on.So at last the result would give me
participants[id = 1, name = "team01"]
(sorry for the writing, I describe it out of the debugger)
i tried something like this, but doesnt work with value:
List<TestTeam> participants;
for (var value in items) {
participants.add(new TestTeam(value.id, value.team));
}
my class Team is defined like this:
class Team {
int _id;
String _team;
String _note;
Team(this._team, this._note);
Team.map(dynamic obj) {
this._id = obj['id'];
this._team = obj['team'];
this._note = obj['note'];
}
int get id => _id;
String get team => _team;
String get note => _note;
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
if (_id != null) {
map['id'] = _id;
}
map['team'] = _team;
map['note'] = _note;
return map;
}
Team.fromMap(Map<String, dynamic> map) {
this._id = map['id'];
this._team = map['team'];
this._note = map['note'];
}
}
You should implement below way
void main() {
List<Team> teams=[];
List<User> participants=[];
for (var i = 0; i < 4; i++) {
teams.add(Team(i,'Team_$i','Note_$i'));
}
for (var value in teams){
participants.add(User(value.id,value.team));
}
for (var value in teams){
print(value.toString());
}
for (var value in participants){
print(value.toString());
}
}
class Team{
int id;
String team;
String note;
Team(this.id,this.team,this.note);
toString()=> 'Team Map :{id:$id,team:$team,note:$note}';
}
class User{
int id;
String team;
User(this.id,this.team);
toString()=> 'User Map :{id:$id,team:$team}';
}
Output
Team Map :{id:0,team:Team_0,note:Note_0}
Team Map :{id:1,team:Team_1,note:Note_1}
Team Map :{id:2,team:Team_2,note:Note_2}
Team Map :{id:3,team:Team_3,note:Note_3}
User Map :{id:0,team:Team_0}
User Map :{id:1,team:Team_1}
User Map :{id:2,team:Team_2}
User Map :{id:3,team:Team_3}
I am getting back information from a websocket that might contain one or more items on each response.
I want to convert these responses to a list of custom objects.
The issue I have is where I want to use the first index (key) and second index (value) of the returned data and use them to pass into the custom object initialization to instantiate them and add them to the list.
I also want to add to the list, but if the 'name' field in an object already exists then just update that objects amount value instead of adding another copy. I will just do this with a loop, unless there is a shorter way.
Below you can see some of the attempts I have tried.
EDIT:
var listAmounts = valueMap.values.toList();
var listNames = valueMap.keys.toList();
print(listAmounts[0]);
print(listNames[0]);
for (int i = 0; i < listAmounts.length; i++) {
Person newPerson =
new Person(name: listNames[i], amount: double.parse(listAmounts[i]));
coins.add(newPerson);
print('=========');
print(newPerson.name);
print(newPerson.amount.toString());
print('=========');
}
I am not sure this is a good way as would there be a possibility that the keys/values will not be in order? From testing they are but I am not 100% sure?
Here are two examples of the message received from the websocket:
example 1
{jacob: 123.456789, steven: 47.3476543}
example 2
{henry: 54.3768574}
I have a custom class:
class Person {
double amount;
String name;
Person({
this.name = '',
this.amount = 0.0,
});
}
Here is the main code:
IOWebSocketChannel channel;
List<Person> people = [];
void openChannel() async {
channel = IOWebSocketChannel.connect(
"wss://ws.example.com/example?example=exp1,exp2,exp3");
channel.stream.listen((message) {
//print(message);
Map valueMap = json.decode(message);
print(valueMap);
//people = List<Person>.from(valueMap.map((i) => Person.fromJson(i)));
//message.forEach((name, amount) => people.add(Person(name: name, amount: amount)));
});
}
var listAmounts = valueMap.values.toList();
var listNames = valueMap.keys.toList();
print(listAmounts[0]);
print(listNames[0]);
for (int i = 0; i < listAmounts.length; i++) {
Person newPerson =
new Person(name: listNames[i], amount: double.parse(listAmounts[i]));
coins.add(newPerson);
print('=========');
print(newPerson.name);
print(newPerson.amount.toString());
print('=========');
}