I'm building a shopping app in Flutter using MVC pattern and mobx for app state management.
At the moment, I have a mobx store for cart items and one store for the cart controller.
The cart controller has a ObservableList of cart items and problem is that I don't know if there's a way of observing changes on cart items.
For instance, I'd like to observe cartItem.title or cartItem.total.
Is there a way to track this with ObservableList?
And whats is .observe() method the observable list has? (Think the documentation wasn't clear for me)
UPDATE: SAMPLE CODE
As I said I have to mobx sotres, one for the cart item and for the cart itself.
In the cart item store:
import 'package:mobx/mobx.dart';
part 'cart-item.model.g.dart';
class CartItemModel = _CartItemModel with _$CartItemModel;
abstract class _CartItemModel with Store {
int id;
String title;
String price;
String description;
#observable
int _quantity = 0;
#observable
double _total = 0;
_CartItemModel({
this.id,
this.title,
this.price,
this.description,
}) {
reaction(
(_) => _quantity,
(quantity) {
getTotal();
},
);
}
getItemQuantity() => _quantity.toString(); // Return item quantity
#action
increase() {
// Increase item quantity
if (_quantity <= 99) {
_quantity++;
}
}
#action
decrease() {
// Decrease item quantity
if (_quantity > 0) {
_quantity--;
}
}
#action
getTotal() {
// Return total price by item quantity
_total = double.parse(price) * _quantity;
return _total.toString();
}
}
And then in the cart controller:
import 'package:faccioo_user_app/models/cart-item.model.dart';
import 'package:mobx/mobx.dart';
part 'cart.controller.g.dart';
class CartController = _CartController with _$CartController;
abstract class _CartController with Store {
#observable
ObservableList<CartItemModel> cartItems = ObservableList<CartItemModel>();
#action
addItem(CartItemModel item) {
cartItems.insert(0, (item));
item.increase();
}
#action
removeItem(CartItemModel item) {
cartItems.removeWhere((cartItem) => cartItem.id == item.id);
getTotal();
}
#action
getSubtotal() {
cartItems.forEach((item) {
subtotal = subtotal + double.parse(item.getTotal());
});
return subtotal.toString();
}
#action
getTotal() {
total = (subtotal + shippingFee + serviceFee + change) - discount;
return total.toString();
}
}
The view is not being notified by the changes in cartItem.total, for example?. How do I observe changes in cartItemModel.total from ObservableLis?
To be more clear I got this print in which we can see that cart item quantity and total increase, therefore CartItemModel reactivity is working fine, but the cart controller can't track those changes from ObservableList, then the controller is not updating the view.
I'd really appreciate links and references from where I can learn more about mobx with Flutter and observable lists.
Cart view with cart item
this is an old question but I will share the hints for the solution and how to improve it.
You don't need to create a store for the model, just wrap the properties with #observable, #computed, etc.
Actions are meant to write to the store and update the store properties. If you want to read properties, just write a simple get property without the #action annotation, or if you want to read a property based on other #observable properties, use the get with #computed.
The store should be only the CartController in which you should have a #computed property that will change based on other properties changes, e.g. for the CartController store:
The CartItemModel should be transformed into a simple data class with getters and setters and is the store that will manage its state.
I didn't check in terms of types .. etc.., the getters are missing the return types, only the setters that don't need return type.
#computed
double get total() => subtotal + shippingFee + serviceFee + change - discount;
#computed
double get subtotal() {
double subTotalAggregator = 0;
cartItems.forEach((item) {
subTotalAggregator += double.parse(item.getTotal());
});
return subTotalAggregator;
}
Related
I have a shopping cart project using provider. When changing variant(array of object) the already pushed value inside the cart also changes. How do I prevent the value from changing when it is already inserted.
Adding items to cart
addProduct() async{
final productDetailsProvider =Provider.of<ProductDetailsProvider>(context,listen: false);
List<VariantDetails>? variants = productDetailsProvider.variants;
List<AddOnDetails>? extras = productDetailsProvider.extras;
double total = productDetailsProvider.total;
CartData cartItem = CartData(
addOnList: extras,
variantList: variants,
total:total);
final cart =Provider.of<CartProvider>(context,listen: false);
cart.addToCart(context, cartItem);
}
Provider class - The cart array variantList still changes when clicking button again. Should not change for the already pushed/added values. Total does not change only variantList and extras
class CartProvider extends ChangeNotifier {
List<CartData>? cart = [];
addToCart(context,CartData cartItem) {
if (cart!.isEmpty) {
cart?.add(cartItem);
}
print('${cart?[0]?.variantList}');//variantList changes value even when it's already pushed to array
}
Provider class changing variant for reference
class ProductDetailsProvider extends ChangeNotifier {
List<VariantDetails>? variants = [];
addVariant(VariantDetails? item) {
var exist =
variants?.indexWhere((element) => element.typeID == item?.typeID);
if (exist != -1) {
variants?[exist!] = item!;
} else {
variants?.insert(0, item!);
}
}
}
I am currently learning River Pod and also new to flutter.
When setting new state in StateNotifer , I need to create a new model and replace the state
But directly changing is not working
class CounterModel {
CounterModel(this.count, this.age);
int count;
int age;
}
class CounterNotifier extends StateNotifier<CounterModel> {
CounterNotifier() : super(_initialValue);
static CounterModel _initialValue = CounterModel(0,18);
void increment() {
// state.count = state.count + 1; // not working, but need like this !
state = CounterModel(state.count + 1, state.age); // working
}
}
In the above code , when I trying to change the count variable directly like, state.count = state.count + 1 , nothing changed
But when reinitialising the state by creating a new model like state = CounterModel(state.count + 1, state.age)
Its seems to be state model variables are immutable and needs to be recreated on every alteration !
My question is , what if the CounterModel have 50 variables , then I have to do something like
state = CounterModel (var1,var2,......,var49,var50) ;
So , is it possible to directly change the variables like
state.var1 = new_value1;
state.var2 = new_value2;
....
state.var50 = new_value50;
You have to always reassign the state in StateNotifier for Consumers to see the changes hence, state.var1 = new_value1; can't work with StateNotifier
If You're very keen about that syntax, use ChangeNotifier since it allows you change individual properties of the class but you must call notifyListeners
Like so:
class CounterNotifier extends StateNotifier {
static CounterModel value = CounterModel(0,18);
void increment() {
value.count = value.count + 1;
notifyListeners();
}
}
If You want to stick with StateNotifier and don't want to write boilerplate code, create a copyWith method on the model.
Like so:
class CounterModel {
CounterModel(this.count, this.age);
int count;
int age;
CounterModel copyWith({int? count, int? age}){
return CounterModel(
count ?? this.count,
age ?? this.age
);
}
}
Then you can keep reassigning with it like so:
class CounterNotifier extends StateNotifier<CounterModel> {
CounterNotifier() : super(_initialValue);
static CounterModel _initialValue = CounterModel(0,18);
void increment() {
state = state.copyWith(count: state.count + 1);
}
}
I've used Laravel, where we can define relationships inside the model itself. So is that possible in Dart/Flutter?
I've built this Cart Model where Grand-total should be dynamic.
So whenever I create an instance of this Cart Model, grand total will be auto-calculated once we enter the total & discount. until then the default value 0.
code from the image above:
class Cart {
Cart({
this.total = 0,
this.discount = 0,
this.grandTotal, // I want this field dynamic: (grandtotal = total - discount)
});
int total;
int discount;
int? grandTotal;
}
define a getter method to calculate grandTotal.
Sample code:
int? get grandTotal {
// TODO: check for null cases here
return total - discount;
}
I have a model called category , which has two fields, User has a list of categories and what to update the category name, for State Management getx is used
class Category {
String? categoryName;
bool status;
Category(this.categoryName,this.status);
}
I have a observable list called catList which is used in List widget
var catList = <Category>[].obs;
when I update the category field it doest not update
catList[index].categoryName = categoryChangedName.value;
but If I update the item in object and then assign the object to catList then It changed
catList[index] = Category(categoryChangedName.value, catList[index].status );
My question is how to make model fields observable, if we have more fields changes then this is not proper way.
As of GetX documentation you need to update values using method and call update(); method inside custom object !
Ex:
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // look here!
}
}
Your use case might be like....
class Category {
String? categoryName;
bool status;
Category(this.categoryName,this.status);
void updateCategoryName(name){
this.categoryName = name;
update();
}
}
//Use like..
catList[index].updateCaetgoryName = categoryChangedName.value;
I guess the best (only?) way is to create a separate box for the nested structure and then to create a relation from parent (ONE) to children (MANY).
However, my issue is how to implement a .toObjectBox() method to convert my domain data model into the ObjectBox data model. Is there anything alike Either from the dartz package (returns either a left or a right side object), which transports 2 objects simultaneously?
So with the ObjectBox Entities
#Entity()
class Customer {
int id;
#Backlink('customer')
final orders = ToMany<Order>();
}
#Entity()
class Order {
int id;
final customer = ToOne<Customer>();
}
I would store the parent (customer) with the nested data objected (order) in the 2 related boxes
Customer customer = domainData.toObjectBox; // but how to get the order out?
customer.orders.add(Order('Order 1')); // shouldn't this be Order(1)?
final customerId = store.box<Customer>().put(customer);
This is how I typically implement the toObjectBox method. By here, you see that this does not work out because I would have to split parent and nested child apart. While I guess I could achieve this with some spaghetti, I wonder if there is a smart way of doing so since I guess this should be a common pattern (though I haven't found any Q&A on it).
#Entity()
Customer {
int id;
List<Order> orders;
Customer({required this.id, required this.orders});
CustomerObox toObjectBox() {
return CustomerObox(
id: id,
// orders: orders.map((x) => x.toObjectBox()).toList()
);
}
=== UPDATE =====================================
I have meanwhile tried to create a return structure myself, and now I am in the process to get this working.
class Pair<T1, T2> {
final T1 parent;
final T2 child;
Pair({required this.parent, required this.child});
}
#Entity()
class Customer {
int id;
List<Order> orders;
Customer({required this.id, required this.orders});
static Pair<CustomerObox, List<OrderObox>> toObjectBox(Customer cust) {
Pair<CustomerObox, List<OrderObox>>(
parent: CustomerObox(
id: cust.id,
),
child: cust.orders.map((o) => o.toObjectBox()).toList()
);
}
}
So I implemented toObjectBox as shown in the update of my question above.
Inside my local datasource implementation (I use the layers: presentation > business logic (bloc) > domain (models, contracts etc) > repository > datasource) I implemented the following methods
#override
Future<void> storeCustomer(Customer customer) async {
// Pair(T1 parent, T2 child) with T2 being List<OrderObox>
final Pair customerObox = Customer.toObjectBox(customer);
final CustomerObox customerParent = customerObox.parent;
final List<OrderObox> customerChildren = customerObox.child;
for (final child in customerChildren)
customerParent.records.add(child);
// puts both, parent and children:
final id = _sBox.put(customerParent);
}
#override
Future<List<Customer>> getAllCustomers() async {
final List<Customer> parents = [];
final List<CustomerObox> parentsObox = await _sBox.getAll();
for (final p in parentsObox) {
final List<CustomerObox> childrenObox = p.records;
parents.add(SessionJournalDto.fromObjectBox(p));
for (final c in childrenObox)
parents.last.records.add(Order.fromObjectBox(c));
}
return parents;
}