Description
Hi, i'm facing an issue with yielding a state.
All what I want is when I click the button , it dispatch an event and then yield the state, but what I'm facing it is that the state yield for one time only.
Cart Event
import 'package:vending/bloc/models/catalog.dart';
import 'package:equatable/equatable.dart';
abstract class CartEvent extends Equatable {
const CartEvent();
@override
List<Object> get props => [];
}
class CartCatalogAddedEvent extends CartEvent{
final int quantity;
final Catalog catalog;
const CartCatalogAddedEvent({this.quantity, this.catalog});
@override
List<Object> get props => [quantity, catalog];
}
class ClearCartEvent extends CartEvent{
}
Cart State
import 'package:vending/bloc/models/cart_catalog.dart';
import 'package:equatable/equatable.dart';
abstract class CartState extends Equatable {
const CartState();
@override
List<Object> get props => [];
}
class CartEmpty extends CartState{}
class CartUpdated extends CartState {
final List<CartCatalog> cartCatalogs;
const CartUpdated({this.cartCatalogs});
@override
List<Object> get props => [cartCatalogs];
}
class CartSizeExceed extends CartState {}
Cart Bloc:
import 'package:vending/bloc/cart/cart_event.dart';
import 'package:vending/bloc/cart/cart_state.dart';
import 'package:vending/bloc/models/cart_catalog.dart';
import 'package:vending/repositories/cart_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CartBloc extends Bloc<CartEvent, CartState> {
final CartRepository cartRepository;
final List<CartCatalog> cartCatalogs;
CartBloc({this.cartRepository, this.cartCatalogs});
@override
CartState get initialState => CartEmpty();
@override
Stream<CartState> mapEventToState(CartEvent event) async* {
if(event is CartCatalogAddedEvent) {
if(!this.cartRepository.checkCartSize(this.cartCatalogs, event.quantity)) {
CartCatalog cartCatalog = CartCatalog(
catalog: event.catalog,
quantity: event.quantity
);
this.cartCatalogs.add(cartCatalog);
yield CartUpdated(cartCatalogs: this.cartCatalogs);
}else {
print("Cart exceed");
yield CartSizeExceed();
}
}
if(event is ClearCartEvent) {
this.cartCatalogs.clear();
yield CartEmpty();
}
}
}
Page:
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:vending/bloc/cart/index.dart';
import 'package:vending/bloc/models/catalog.dart';
import 'package:vending/styles/product_detail_style.dart';
import 'package:vending/styles/scanner_page_style.dart';
import 'package:vending/theme.dart';
import 'package:vending/views/home_page.dart';
import 'package:vending/views/products_page.dart';
import 'package:flutter/material.dart';
class ProductDetailPage extends StatefulWidget {
final int index;
final ThemeData themeData;
final SharedPreferences sharedPreferences;
ProductDetailPage({@required this.index, @required this.themeData, @required this.sharedPreferences});
@override
_ProductDetailPageState createState() => _ProductDetailPageState();
}
class _ProductDetailPageState extends State<ProductDetailPage> with WidgetsBindingObserver{
int quantity = 1;
double totalPrice = 17.49;
double initialPrice = 17.49;
bool canNavigate = true;
int counter = 0;
@override
Widget build(BuildContext context) {
return _renderPage(context);
}
Widget _renderPage(BuildContext context) {
final EdgeInsets appPadding = MediaQuery.of(context).padding;
final double productImageWidth = MediaQuery.of(context).size.width / 1.2;
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: this.widget.themeData,
home: Scaffold(
appBar: _customAppBar(appPadding, context),
body: Center(
child: Container(
padding: EdgeInsets.only(
left: (15 + appPadding.left),
right: (15 + appPadding.right)
),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Hero(
tag: this.widget.index.toString(),
child: Container(
child: Image.asset(
"assets/images/pepsi.png",
width: productImageWidth,
),
),
),
SizedBox(height: 20.0,),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Pepsi",
style: this.widget.themeData == AppTheme.lightTheme ? productDetailTitleLightTheme : productDetailTitleDarkTheme,
),
Text(
"\$$totalPrice",
style: TextStyle(
color: const Color(0xff29D370),
fontSize: 25.0,
fontWeight: FontWeight.bold
),
),
],
),
),
SizedBox(height: 30.0,),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Quantity",
style: this.widget.themeData == AppTheme.lightTheme ? productQuantityLightTheme : productQuantityDarkTheme,
),
_renderQuantityController(this.widget.themeData)
],
),
),
// Add if statement when the product has description
SizedBox(height: 30.0,),
Container(
child: Text(
"Lorem Ipsum is simply dummy text of the printing and typesetting industry",
style: this.widget.themeData == AppTheme.lightTheme ? productDetailDescriptionLightTheme : productDetailDescriptionDarkTheme,
),
),
SizedBox(height: 30.0,),
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 10
),
child: InkWell(
onTap: () {
BlocProvider.of<CartBloc>(context).add(CartCatalogAddedEvent(
quantity: quantity,
catalog: Catalog(
id: 1,
name: "Pepsi",
picture: "assets/images/pepsi.png",
price: 17.49
)
));
},
child: Container(
width: double.infinity,
height: 60.0,
decoration: BoxDecoration(
color: const Color(0xff29D370),
borderRadius: BorderRadius.circular(50),
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(0.3),
blurRadius: 4.0,
offset: Offset(0, 3)
)
]
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20.0
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Add to Cart",
style: buttonTextStyle,
),
Text(
"\$17.49",
style: buttonTextStyle,
),
],
),
)
),
),
),
],
),
),
),
)
),
);
}
}
Logs:
I/flutter (25060): ThemeBloc ThemeLoadEvent
I/flutter (25060): Transition { currentState: ThemeUninitialized, event: ThemeLoadEvent, nextState: ThemeChanged }
I/flutter (25060): CartBloc CartCatalogAddedEvent
I/flutter (25060): Transition { currentState: CartEmpty, event: CartCatalogAddedEvent, nextState: CartUpdated }
I/flutter (25060): CartBloc CartCatalogAddedEvent
I/flutter (25060): CartBloc CartCatalogAddedEvent
I/flutter (25060): CartBloc CartCatalogAddedEvent
I/flutter (25060): CartBloc CartCatalogAddedEvent
I will be happy if someone can help me and thank you.
@override
Stream<CartState> mapEventToState(CartEvent event) async* {
if(event is CartCatalogAddedEvent) {
if(!this.cartRepository.checkCartSize(this.cartCatalogs, event.quantity)) {
CartCatalog cartCatalog = CartCatalog(
catalog: event.catalog,
quantity: event.quantity
);
this.cartCatalogs.add(cartCatalog);
yield CartUpdated(cartCatalogs: this.cartCatalogs);
}else {
print("Cart exceed");
yield CartSizeExceed();
}
}
if(event is ClearCartEvent) {
this.cartCatalogs.clear();
yield CartEmpty();
}
}
First of all you change state first time it is normal because CartEmpty() => CartUpdated() and then you update list CartUpdated() => CartUpdated()
In order for the BlocBuilder to realize this, it must be an immutable object. So okay you use equatable for this BUT your List
May be it can work like this, creating new list
if(event is CartCatalogAddedEvent) {
if(!this.cartRepository.checkCartSize(this.cartCatalogs, event.quantity)) {
CartCatalog cartCatalog = CartCatalog(
catalog: event.catalog,
quantity: event.quantity
);
List newList = this.cartCatalogs.toList();
newList.add(cartCatalog);
yield CartUpdated(cartCatalogs: newList);
}
@furkanvatandas Well thank you for replying.
I tested your code, and it doesn't work, same issue
Hi @amine0909 馃憢
You're not getting a new state because of
this.cartCatalogs.add(cartCatalog);
yield CartUpdated(cartCatalogs: this.cartCatalogs);
which mutates the existing list so the value equality won't work.
You're also keeping an extra state directly on your bloc final List<CartCatalog> cartCatalogs;. I'd recommend moving it to your state unless you need to use that field to create your initial state, in which case is fine but you shouldn't be using it beyond that point.
Hope that gets you going on the right path 馃憤
Closing this for now but let us know if you have any additional questions 馃憤
@felangel I'm trying to yield a new state from a Map
Hi @manhDat0301 馃憢
One way of doing it is yield Map.from(existingMap)..update(...).
Hi @manhDat0301 馃憢
One way of doing it is
yield Map.from(existingMap)..update(...).
@RollyPeres
What if i'm at CurrentState(map: map). I add an event and modify the map and yield CurrentState(map: map"modified"). It does not update the state? inside prop i put [map] and i try prop => [ ] but nothing happen.
if (event is AddEvent) {
map[key] = event.data;
yield CurrentState(map: map)
}
if (event is DeleteEvent) {
map.remove(event.key);
yield CurrentState(map:map)
}
@manhDat0301 as @RollyPeres mentioned, you should not be mutating an existing map instance and yielding it -- instead create a new map instance from the previous state like:
if (event is AddEvent) {
yield CurrentState(map: Map.from(state.map)..update(key, (_) => event.data));
}
@felangel @RollyPeres thanks very much, i will try
Most helpful comment
Hi @amine0909 馃憢
You're not getting a new state because of
which mutates the existing list so the value equality won't work.
You're also keeping an extra state directly on your bloc
final List<CartCatalog> cartCatalogs;. I'd recommend moving it to your state unless you need to use that field to create your initial state, in which case is fine but you shouldn't be using it beyond that point.Hope that gets you going on the right path 馃憤