update sibling state (data) flutter bloc - flutter

I am trying to update data in a sibling widget which already loaded data at first. The second widget adds to the same data and fetches it at the same time showing an updated data. The new data is not reflected in the first widget. Take a shopping cart and quantity of items as an example. When I add new items or change quantities in the cart it does not reflect in the cart.
All data is remotely called in a django rest api.
If I reload the app the new data shows in the cart.
this is the widget where i want to change the data in it
class MainScreen extends StatefulWidget {
final TokenResponse token;
const MainScreen({Key key, this.token});
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
BottomNavBarBloc _bottomNavBarBloc;
CartBloc cartBloc;
#override
void initState() {
_bottomNavBarBloc = BottomNavBarBloc();
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
cartBloc = BlocProvider.of<CartBloc>(context);
cartBloc.add(FetchOrderSummaryEvent());
return Scaffold(
backgroundColor: Colors.white,
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
sized: false,
child: StreamBuilder<NavBarItem>(
stream: _bottomNavBarBloc.itemStream,
initialData: _bottomNavBarBloc.defaultItem,
builder:
(BuildContext context, AsyncSnapshot<NavBarItem> snapshot) {
switch (snapshot.data) {
case NavBarItem.HOME:
return HomeScreen();
case NavBarItem.OFFERS:
return OffersScreen();
case NavBarItem.REWARDS:
return _testScreen();
case NavBarItem.FAVORITE:
return _testScreen();
case NavBarItem.BAG:
return CartScreen();
case NavBarItem.ACCOUNT:
return AccountMainScreen();
}
return Container();
}),
),
bottomNavigationBar: StreamBuilder(
stream: _bottomNavBarBloc.itemStream,
initialData: _bottomNavBarBloc.defaultItem,
builder: (BuildContext context, AsyncSnapshot<NavBarItem> snapshot) {
return BottomNavigationBar(
unselectedItemColor: Style.Colors.secondaryColor,
selectedItemColor: Style.Colors.primaryColor,
backgroundColor: Colors.white,
selectedFontSize: 10.0,
unselectedFontSize: 10.0,
type: BottomNavigationBarType.fixed,
currentIndex: snapshot.data.index,
onTap: _bottomNavBarBloc.pickItem,
items: [
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text("Home"),
),
icon: Icon(
FontAwesomeIcons.bars,
color: Style.Colors.secondaryColor,
),
activeIcon: Icon(
FontAwesomeIcons.bars,
color: Style.Colors.primaryColor,
)),
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text("Offers"),
),
icon: Icon(
FontAwesomeIcons.tag,
color: Style.Colors.secondaryColor,
),
activeIcon: Icon(
FontAwesomeIcons.tag,
color: Style.Colors.primaryColor,
)),
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text("Rewards"),
),
icon: Icon(
FontAwesomeIcons.gift,
color: Style.Colors.secondaryColor,
),
activeIcon: Icon(
FontAwesomeIcons.gift,
color: Style.Colors.primaryColor,
)),
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text("Favorite"),
),
icon: Icon(
FontAwesomeIcons.heart,
color: Style.Colors.secondaryColor,
),
activeIcon: Icon(
FontAwesomeIcons.heart,
color: Style.Colors.primaryColor,
)),
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text("Bag"),
),
icon: Container(
width: 30,
height: 25,
child: Stack(children: <Widget>[
Icon(
FontAwesomeIcons.shoppingBag,
color: Style.Colors.secondaryColor,
),
BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
return BlocBuilder<CartBloc, CartState>(
builder: (context, state) {
if (state is CartLoaded) {
print("loaded ${state.order.totalItemQuantity}"); // <=== change this data
return Positioned(
right: 0,
top: 0,
child: Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
border: Border.all(
width: 1.0,
color: Colors.white,
),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
left: 1.0,
bottom: 1.0,
),
child: Text(
state.order.totalItemQuantity !=null? state.order.totalItemQuantity.toString():"",
style: TextStyle(
color: Colors.white,
fontSize: 10.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
} else {
return Container(
);
}
},
);
} else {
return Text(" ");
}
},
)
]),
),
activeIcon: Container(
width: 30,
height: 25,
child: Stack(children: <Widget>[
Icon(
FontAwesomeIcons.shoppingBag,
color: Style.Colors.primaryColor,
),
BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
return BlocBuilder<CartBloc, CartState>(
builder: (context, state) {
if (state is CartLoaded) {
print("loaded ${state.order.totalItemQuantity}");
return Positioned(
right: 0,
top: 0,
child: Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
border: Border.all(
width: 1.0,
color: Colors.white,
),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
left: 1.0,
bottom: 1.0,
),
child: Text(
state.order.totalItemQuantity !=null? state.order.totalItemQuantity.toString():"",
style: TextStyle(
color: Colors.white,
fontSize: 10.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
} else {
return Container(
);
}
},
);
} else {
return Text(" ");
}
},
)
]),
),),
BottomNavigationBarItem(
title: Padding(
padding: EdgeInsets.only(top: 5.0),
child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
return Text("Account");
} else {
return Text("Login");
}
},
),
),
icon: Icon(
FontAwesomeIcons.user,
color: Style.Colors.secondaryColor,
),
activeIcon: Icon(
FontAwesomeIcons.user,
color: Style.Colors.primaryColor,
)),
],
);
},
),
);
}
Widget _testScreen() {
return Center(
child: Text(
'Test Screen',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 25.0,
),
),
);
}
}
i call the API in a sibling widget here i am posting the handling method that was called after pressing a button from a sibling widget:
handleAddItemFromCart(
String slug, List<ItemVariations> itemVariations, BuildContext context) {
final cartBloc = BlocProvider.of<CartBloc>(context);
final currentState = cartBloc.state;
if( currentState is CartLoaded){
print("order quantity: ${currentState.order.totalItemQuantity.toString()}");
}
print("current state " + currentState.toString());
cartBloc.add(AddItemToCartEvent(
slug: slug, variations: formatVariations(itemVariations)));
cartBloc.add(FetchOrderSummaryEvent());
}
cart_event.dart
#immutable
abstract class CartEvent extends Equatable {
CartEvent([List props = const []]):super();
}
class FetchOrderSummaryEvent extends CartEvent {
FetchOrderSummaryEvent([List props = const[]]) : super(props);
Order order;
#override
List<Object> get props => [this.order];
}
class RemoveItemFromCartEvent extends CartEvent {
String slug;
List<int> variations;
RemoveItemFromCartEvent({#required this.slug, this.variations});
#override
List<Object> get props => [];
}
class AddItemToCartEvent extends CartEvent {
String slug;
List<int> variations;
AddItemToCartEvent({#required this.slug, #required this.variations});
#override
List<Object> get props => [];
}
class RemoveItemEvent extends CartEvent {
#override
List<Object> get props => [];
}
class CheckoutEvent extends CartEvent {
String stripeToken;
int selectedAddress;
CheckoutEvent({#required this.stripeToken, #required this.selectedAddress});
#override
List<Object> get props => [];
}
cart_state.dart
#immutable
abstract class CartState extends Equatable {
CartState([List props = const[]]):super();
#override
List<Object> get props => [];
}
class CartInitial extends CartState {
#override
// TODO: implement props
List<Object> get props => [];
}
class CartLoading extends CartState {
#override
// TODO: implement props
List<Object> get props => [];
}
class CartLoaded extends CartState {
Order order;
CartLoaded({#required this.order});
CartLoaded copyWith({Order order}) {
return CartLoaded(order: order ?? this.order);
}
#override
// TODO: implement props
List<Object> get props => [order];
}
class CartFailure extends CartState {
String message;
CartFailure({#required this.message});
#override
// TODO: implement props
List<Object> get props => [message];
}
cart_bloc.dart:
class CartBloc extends Bloc<CartEvent, CartState> {
MenuItemsRepository menuItemsRepository;
CartBloc({#required this.menuItemsRepository});
#override
// TODO: implement initialState
CartState get initialState => CartInitial();
#override
Stream<CartState> mapEventToState(CartEvent event) async* {
final currentState = state;
if (event is FetchOrderSummaryEvent) {
yield CartLoading();
try {
Order order = await menuItemsRepository.getOrder();
yield CartLoaded(order: order);
} catch (err) {
yield CartFailure(message: err.message);
}
}
if (event is AddItemToCartEvent) {
//yield CartLoading();
try {
await menuItemsRepository.addItemToCart(event.slug, event.variations);
} catch (err) {
yield CartFailure(message: err.toString());
}
}
if (event is RemoveItemFromCartEvent) {
yield CartLoading();
try {
await menuItemsRepository.removeItemFromCart(
event.slug, event.variations);
} catch (err) {
yield CartFailure(message: err.toString());
}
}
if (event is CheckoutEvent) {
yield CartLoading();
try {
print("token: ${event.stripeToken}");
await menuItemsRepository.makePayment(
event.stripeToken, event.selectedAddress);
} catch (err) {
yield CartFailure(message: err.toString());
}
}
}
}
repository:
Future<bool> addItemToCart(String slug, List<int> variations) async {
final token = "Token " + await storage.read(key: utils.TOKEN);
var params = {
"slug": slug,
"variations": variations,
};
try {
Response response = await _dio.post(getAddToCartURL,
data: jsonEncode(params),
options: Options(headers: {
HttpHeaders.authorizationHeader: token,
HttpHeaders.contentTypeHeader: "application/json",
}));
return response.statusCode == 200;
} catch (error, stackTrace) {
print("Exception occurred: $error stackTrace: $stackTrace");
return false;
}
}
Future<Order> getOrder() async {
final token = "Token " + await storage.read(key: utils.TOKEN);
var params = {
"api_key": apiKey,
};
try {
Response response = await _dio.get(getOrderSummaryURL,
queryParameters: params,
options: Options(headers: {
HttpHeaders.authorizationHeader: token,
HttpHeaders.contentTypeHeader: "application/json",
}));
print(response.data);
return Order.fromJson(response.data);
} catch (error, stackTrace) {
print("Exception occurred: $error stackTrace: $stackTrace");
return Order.withError(error.toString());
}
}

UPDATE:
i figured it out!
the bloc CartBloc was instantiating 2 times! one at the beginning of the app creation or start in the main.dart file and then another one in a child widget which was the order_summary file. This was was causing to not be able to force the parent widget fetch the data from api. so all the updates was done in the child level not the parent.
I hope this would help anyone who might face the same issue. Make sure you instantiate the bloc once only so you can update or receive the updates from the same bloc.

Related

Add new items in list without rerendering old items

I 'am writing a message application whenever I insert a new message at the 0th index in the list all the messages within the offset are rerendered even though their properties have not been changed. How can I stop this?
Below in the code, you can see The ChatBook class which renders MessageList which is a a ScrollablePositionedListView.
I opted this in order to reach tagged messages instantly. As soon as I press the send button which is in InputBar The onPressSend Method is called which adds new message in the _messages array also I have provided _messages array inside InheritedWidget so that i can inherit it within the child since it required at multiple places. Whenever i add a new message simple a textmessage it re renders all the previous messages even though there properties have not been changed. Below are the required code snippets.
ChatBook class
/// This is the entry point of the [ChatBook] package.
class ChatBook extends StatefulWidget {
const ChatBook({
Key? key,
required this.author,
required this.onSendMessage,
this.theme = const DefaultChatTheme(),
this.giphyApiKey,
}) : super(key: key);
/// List of messages from which messagefor [Ayesavi ChatBook] will be retrieved by default this field is provided [DefaultChatTheme]
/// Theme assigned to ChatBook for info see [ChatTheme]
final ChatTheme theme;
/// callback for onPress event on sendMessageButton in inputbar [InputBar]
final void Function(Message currentMessage) onSendMessage;
/// GiphyApiKey required to retrieve giphy from servers.
final String? giphyApiKey;
final User author;
#override
State<ChatBook> createState() => _ChatBookState();
}
class _ChatBookState extends State<ChatBook> {
final List<Message> _messages = [];
/// Initialising ItemScrollController
/// It is needed for jumpTo and scrollTo methods to reach any particular item
final ItemScrollController itemScrollController = ItemScrollController();
/// Initialising ItemPositionListner
/// Listens for the position of any item.
final ItemPositionsListener itemPositionsListener =
ItemPositionsListener.create();
final TagMessageHelper _tagMessageHelper = TagMessageHelper();
/// It is for holding the selectedGif after onPressedEvent on Gif
/// GiphyCient is later initilising the [GiphyClient] in the [initState] method.
late GiphyClient client;
/// System used to generate random id for the user at the runtime by default initialised with aan empty String
String randomId = "";
/// For holding giphy Api Key
//! Store API keys in the env mode separate it is advised so to keep your API keys private to you only
#override
void initState() {
super.initState();
if (widget.giphyApiKey != null) {
client = GiphyClient(apiKey: widget.giphyApiKey!, randomId: '');
WidgetsBinding.instance.addPostFrameCallback((_) {
client.getRandomId().then((value) {
setState(() {
randomId = value;
});
});
});
}
}
void onPressSend(
Message sendingMessage,
) {
setState(() {
_messages.insert(0, sendingMessage);
});
widget.onSendMessage.call(sendingMessage.copyWith(
self: false,
));
}
#override
Widget build(BuildContext context) {
return GiphyGetWrapper(
/// GiphyGetWrapper is used to get giphys where ever called in the application.
giphy_api_key: widget.giphyApiKey!,
builder: (stream, giphyGetWrapper) {
stream.listen((gif) {
// widget.onGiphyPressed?.call(gif);
onPressSend(GifMessage(
author: const User(id: ""),
id: '',
gif: gif,
createdAt: DateTime.now().millisecondsSinceEpoch,
self: true,
status: Status.seen));
});
return InheritedProperties(
tagHelper: _tagMessageHelper,
theme: widget.theme,
giphyGetWrapper: giphyGetWrapper,
author: widget.author,
//* Needed for inheriting acess of messages to all its child widgets.
child: InheritedMessagesWidget(
messages: _messages,
child: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.00,
),
child: MessageList(
controller: itemScrollController,
positionsListener: itemPositionsListener,
),
),
),
const SizedBox(
height: 10,
),
// RepaintBoundary(
// child:
ValueListenableBuilder(
valueListenable: _tagMessageHelper.tagNotifier,
builder: (_, value, __) {
if (value == null) return const SizedBox();
return TaggedMessageIndicator(
message: value as Message);
}),
// ),
ConstrainedBox(
constraints:
const BoxConstraints(maxHeight: 150, minHeight: 60),
child: InputBar(
giphyGetWrapper: giphyGetWrapper,
onSendMessage: onPressSend,
))
],
),
),
);
});
}
}
MessageList
class MessageList extends StatefulWidget {
const MessageList(
{Key? key, required this.controller, required this.positionsListener})
: super(key: key);
final ItemScrollController controller;
final ItemPositionsListener positionsListener;
#override
State<MessageList> createState() => _MessageListState();
}
class _MessageListState extends State<MessageList> {
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return ScrollablePositionedList.builder(
reverse: true,
itemScrollController: widget.controller,
itemPositionsListener: widget.positionsListener,
initialScrollIndex: 0,
itemCount: InheritedMessagesWidget.of(context).messages.length,
itemBuilder: (_, index) {
return MessageBuilder(
message: InheritedMessagesWidget.of(context).messages[index],
prevMessage:
index != InheritedMessagesWidget.of(context).messages.length - 1
? InheritedMessagesWidget.of(context).messages[index + 1]
: null,
);
},
scrollDirection: Axis.vertical,
);
}
}
MessageBuilder
class MessageBuilder extends StatefulWidget {
const MessageBuilder({Key? key, required this.message, this.prevMessage})
: super(key: key);
final Message message;
final Message? prevMessage;
#override
State<MessageBuilder> createState() => _MessageBuilderState();
}
class _MessageBuilderState extends State<MessageBuilder> {
#override
Widget build(BuildContext context) {
return Column(
children: [
_dateProvider(widget.message, widget.prevMessage),
Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Container(
width: double.infinity,
alignment:
widget.message.self != null && widget.message.self == true
? Alignment.centerRight
: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
LimitedBox(
maxWidth: 6.5 / 10 * (MediaQuery.of(context).size.width),
child: IntrinsicWidth(
child: RepaintBoundary(
child: Swipeable(
maxOffset: .7,
movementDuration: const Duration(milliseconds: 500),
background: const Align(
alignment: Alignment.centerLeft,
child: Icon(
Icons.share,
size: 20,
color: Colors.white,
)),
direction: SwipeDirection.startToEnd,
onSwipe: (dir) {
if (SwipeDirection.startToEnd == dir) {
logger.log("swiped");
InheritedProperties.of(context)
.tagHelper
.tagNotifier
.value = widget.message;
}
},
confirmSwipe: (direction) async {
logger.log((SwipeDirection.startToEnd == direction)
.toString());
return SwipeDirection.startToEnd == direction;
},
allowedPointerKinds: const {
PointerDeviceKind.mouse,
PointerDeviceKind.stylus,
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
PointerDeviceKind.unknown,
},
key: const ValueKey(1),
child: Bubble(
padding: const BubbleEdges.all(0),
showNip: _nipGiver(widget.message, widget.prevMessage),
nip: widget.message.self == true
? BubbleNip.rightTop
: BubbleNip.leftTop,
color: _bubbleColorGiver(widget.message),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_messageProviderWidget(widget.message)!,
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
DateFormat.jm().format(
DateTime.fromMillisecondsSinceEpoch(
widget.message.createdAt!)),
style: widget.message.self == true
? InheritedProperties.of(context)
.theme
.sentTimeTextStyle
: InheritedProperties.of(context)
.theme
.receivedTimeTextStyle),
const SizedBox(
width: 5,
),
if (widget.message.self == true)
_statusProvider(widget.message),
]),
),
],
),
),
),
)),
),
],
),
),
),
],
);
}
Widget? _messageProviderWidget(Message message) {
MessageType type = message.type;
switch (type) {
case MessageType.text:
return TextMessageWidget(
message: message as TextMessage,
);
case MessageType.gif:
return GifMessageWidget(message: message as GifMessage);
case MessageType.audio:
message as AudioMessage;
return AudioMessageWidget(message: message);
default:
return null;
}
}
bool? _nipGiver(Message currentMessage, Message? prevMessage) {
if (prevMessage != null &&
sameDay(currentMessage.createdAt!, prevMessage.createdAt) == true &&
currentMessage.self == prevMessage.self) {
return false;
} else {
return true;
}
}
Widget _dateProvider(Message currentMessage, Message? prevMessage) {
if (prevMessage != null &&
sameDay(currentMessage.createdAt!, prevMessage.createdAt) == false) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Text(
DateFormat('dd MMM yyyy').format(
DateTime.fromMicrosecondsSinceEpoch(currentMessage.createdAt!)),
style: InheritedProperties.of(context).theme.dateHeaderTextStyle,
),
);
} else if (prevMessage == null) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
DateFormat('dd MMM yyyy').format(
DateTime.fromMicrosecondsSinceEpoch(currentMessage.createdAt!)),
style: InheritedProperties.of(context).theme.dateHeaderTextStyle,
),
);
} else {
return const SizedBox();
}
}
Widget _statusProvider(Message message) {
switch (message.status) {
case Status.seen:
return SvgPicture.asset(
'assets/double_tick.svg',
color: Colors.blue,
height: 10,
width: 10,
);
case Status.delivered:
return SvgPicture.asset(
'assets/double_tick.svg',
color: Colors.grey,
height: 10,
width: 10,
);
case Status.error:
return const Icon(Icons.error_outline, color: Colors.red, size: 18);
case Status.sending:
return const SizedBox(
height: 10, width: 10, child: CupertinoActivityIndicator());
case Status.sent:
return SvgPicture.asset(
'asset/single_tick.svg',
color: Colors.grey,
height: 10,
width: 10,
);
default:
return const SizedBox();
}
}
Color _bubbleColorGiver(Message message) {
if (["text", 'audio', 'video'].contains(message.type.name)) {
return widget.message.self == true
? InheritedProperties.of(context).theme.sentMessageBubbleColor
: InheritedProperties.of(context).theme.receivedMessageBubbleColor;
} else if (message.type.name == 'gif') {
return Colors.transparent;
} else {
return Colors.transparent;
}
}
}
TextMessageWidget
class TextMessageWidget extends StatefulWidget {
const TextMessageWidget({
Key? key,
required this.message,
}) : super(key: key);
final TextMessage message;
#override
State<TextMessageWidget> createState() => _TextMessageWidgetState();
}
class _TextMessageWidgetState extends State<TextMessageWidget> {
#override
Widget build(BuildContext context) {
final bodyTextStyle = widget.message.self == true
? InheritedProperties.of(context).theme.sentMessageTextStyle
: InheritedProperties.of(context).theme.receivedMessageTextStyle;
final boldTextStyle = widget.message.self == true
? InheritedProperties.of(context).theme.sentMessageBoldTextStyle
: InheritedProperties.of(context).theme.receivedMessageBoldTextStyle;
final codeTextStyle = widget.message.self == true
? InheritedProperties.of(context).theme.sentMessageBodyCodeTextStyle
: InheritedProperties.of(context)
.theme
.receivedMessageBodyCodeTextStyle;
RegExp exp = RegExp(r'(?:(?:https?|ftp):)?[\w/\-?=%.]+\.[\w/\-?=%.]+');
String? _urlGiver(String text) {
String? urlText;
Iterable<RegExpMatch> matches = exp.allMatches(text);
for (var match in matches) {
urlText = (text.substring(match.start, match.end));
}
return urlText;
}
_urlGiver(widget.message.text);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_urlGiver(widget.message.text) != null)
AnyLinkPreview.builder(
placeholderWidget: const SizedBox(
height: 0,
width: 0,
),
errorWidget: const SizedBox(
height: 0,
width: 0,
),
link: _urlGiver(widget.message.text)!,
itemBuilder: (_, metadata, image) {
return GestureDetector(
onTap: () {
if (metadata.url != null) {
launchUrl(Uri.parse(metadata.url!));
}
},
child: Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (image != null) ...[
ClipRRect(
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(5),
bottomLeft: Radius.circular(5)),
child: Image(image: image),
),
],
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
metadata.title!,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
if (metadata.desc != null &&
metadata.desc != '' &&
metadata.desc !=
'A new Flutter project.') ...[
const SizedBox(height: 10),
Text(metadata.desc!)
]
],
),
),
],
),
),
);
}),
const SizedBox(
height: 5,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ParsedText(
selectable: true,
text: widget.message.text,
style: bodyTextStyle,
parse: [
MatchText(
onTap: (mail) async {
final url = Uri(scheme: 'mailto', path: mail);
if (await canLaunchUrl(url)) {
await launchUrl(url);
}
},
pattern:
r'([a-zA-Z0-9+._-]+#[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)',
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
MatchText(
onTap: (urlText) async {
final protocolIdentifierRegex = exp;
if (!urlText.startsWith(protocolIdentifierRegex)) {
urlText = 'https://$urlText';
}
{
final url = Uri.tryParse(urlText);
if (url != null && await canLaunchUrl(url)) {
await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
}
}
},
pattern:
r'((http|ftp|https):\/\/)?([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,#?^=%&:/~+#-]*[\w#?^=%&/~+#-])?',
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
MatchText(
pattern: PatternStyle.bold.pattern,
style: boldTextStyle ??
bodyTextStyle.merge(PatternStyle.bold.textStyle),
renderText:
({required String str, required String pattern}) => {
'display': str.replaceAll(
PatternStyle.bold.from,
PatternStyle.bold.replace,
),
},
),
MatchText(
pattern: PatternStyle.italic.pattern,
style: bodyTextStyle.merge(PatternStyle.italic.textStyle),
renderText:
({required String str, required String pattern}) => {
'display': str.replaceAll(
PatternStyle.italic.from,
PatternStyle.italic.replace,
),
},
),
MatchText(
pattern: PatternStyle.lineThrough.pattern,
style:
bodyTextStyle.merge(PatternStyle.lineThrough.textStyle),
renderText:
({required String str, required String pattern}) => {
'display': str.replaceAll(
PatternStyle.lineThrough.from,
PatternStyle.lineThrough.replace,
),
},
),
MatchText(
pattern: PatternStyle.code.pattern,
style: codeTextStyle ??
bodyTextStyle.merge(PatternStyle.code.textStyle),
renderText:
({required String str, required String pattern}) => {
'display': str.replaceAll(
PatternStyle.code.from,
PatternStyle.code.replace,
),
},
),
MatchText(
pattern: PatternStyle.at.pattern,
style: codeTextStyle ??
bodyTextStyle.merge(PatternStyle.at.textStyle),
renderText:
({required String str, required String pattern}) => {
'display': str.replaceAll(
PatternStyle.code.from,
PatternStyle.code.replace,
),
},
),
],
)
// SelectableLinkify(text: widget.message.text,onOpen: (element){
// _launchInBrowser(Uri.parse(element.url));
// },),
),
],
);
}
}

Flutter Bloc doesn't update data in UI using BLOC pattern

I am working on an ordering application. where I have an object called "Comanda" that contains a list of objects called "CommandLine". I want to handle with the BLOC pattern the whole issue of adding lines to the command, deleting and/or editing.
I show the implemented code.
CLASSES:
class Comanda extends Equatable {
Comanda({this.lineasComanda = const <LineaComanda>[], this.mesa, this.zona});
List<LineaComanda> lineasComanda;
Mesa? mesa;
Zona? zona;
factory Comanda.fromJson(Map<String, dynamic> json) => Comanda(
lineasComanda: json["lineasComanda"] ?? [],
mesa: Mesa.fromJson(json["mesa"]),
zona: Zona.fromJson(json["mesa"]));
Map<String, dynamic> toJson() => {
"lineasComanda": lineasComanda,
"mesa": mesa != null ? mesa!.toJson() : null,
"zona": zona != null ? zona!.toJson() : null,
};
Comanda copyWith({
List<LineaComanda>? lineasComanda,
Mesa? mesa,
Zona? zona,
}) {
return Comanda(
lineasComanda: lineasComanda!,
mesa: mesa ?? this.mesa,
zona: zona ?? this.zona,
);
}
#override
List<Object?> get props => [lineasComanda, mesa, zona];
}
class LineaComanda extends Equatable {
LineaComanda({
required this.articulo,
required this.unidades,
this.unidadesPendientes = 0,
this.anotaciones,
});
Articulo? articulo;
int unidades;
int unidadesPendientes;
List<String>? anotaciones = [];
//UI VARIALBLES
bool isExpanded = false;
LineaComanda copyWith(
{Articulo? articulo,
int unidades = 0,
int unidadesPendientes = 0,
List<String>? anotaciones = const []}) {
return LineaComanda(
articulo: articulo ?? this.articulo,
unidades: unidades,
unidadesPendientes: unidadesPendientes,
anotaciones: anotaciones ?? this.anotaciones,
);
}
#override
List<Object?> get props => [articulo, unidades, unidadesPendientes, anotaciones];
}
BLOC:
part of 'Comanda_bloc.dart';
abstract class ComandaState extends Equatable {
const ComandaState();
}
class ComandaInitial extends ComandaState {
#override
List<Object> get props => [];
}
class ComandaLoading extends ComandaState {
#override
List<Object> get props => [];
}
class ComandaLoaded extends ComandaState {
final Comanda comanda;
const ComandaLoaded({required this.comanda});
#override
List<Object> get props => [comanda];
}
class ComandaError extends ComandaState {
final String error;
const ComandaError(this.error);
#override
List<Object?> get props => [error];
}
part of 'Comanda_bloc.dart';
abstract class ComandaEvent extends Equatable {
const ComandaEvent();
}
class StartComanda extends ComandaEvent {
#override
List<Object?> get props => [];
}
class AddLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
const AddLineaComanda(this.lineaComanda);
#override
List<Object> get props => [lineaComanda];
}
class RemoveLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveLineaComanda(this.lineaComanda);
#override
List<Object> get props => [lineaComanda];
}
class AddUnidades extends ComandaEvent {
final LineaComanda lineaComanda;
AddUnidades(this.lineaComanda);
#override
List<Object> get props => [lineaComanda];
}
class RemoveUnidades extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveUnidades(this.lineaComanda);
#override
List<Object> get props => [lineaComanda];
}
class RemoveAllLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveAllLineaComanda(this.lineaComanda);
#override
List<Object> get props => [lineaComanda];
}
class SelectMesa extends ComandaEvent {
final Mesa mesa;
SelectMesa(this.mesa);
#override
List<Object> get props => [mesa];
}
class LoadComanda extends ComandaEvent {
final List<LineaComanda> lineasComanda;
LoadComanda(this.lineasComanda);
#override
List<Object?> get props => [lineasComanda];
}
part 'Comanda_event.dart';
part 'Comanda_state.dart';
class ComandaBloc extends Bloc<ComandaEvent, ComandaState> {
final Comanda_Repository repository;
ComandaBloc(this.repository) : super(ComandaInitial()) {
//EVENTOS
on<LoadComanda>((event, emit) async {
emit(ComandaLoading());
try {
// final comanda = await repository.getone(1);
emit(ComandaLoaded(comanda: Comanda()));
} catch (error) {
emit(ComandaError(error.toString()));
}
});
on<StartComanda>((event, emit) async {
emit(ComandaLoading());
try {
// final comanda = await repository.getone(1);
emit(ComandaLoaded(comanda: Comanda()));
} catch (error) {
emit(ComandaError(error.toString()));
}
});
on<AddLineaComanda>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(
lineasComanda: List.from(state.comanda.lineasComanda)
..add(event.lineaComanda))),
);
} catch (_) {}
}
});
on<RemoveLineaComanda>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(
lineasComanda: List.from(state.comanda.lineasComanda)
..remove(event.lineaComanda))),
);
} catch (_) {}
}
});
on<AddUnidades>((event, emit) async {
emit(ComandaLoading());
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
lineasComanda.firstWhere((item) => item == event.lineaComanda).unidades += 1;
emit(
ComandaLoaded(comanda: state.comanda.copyWith(lineasComanda: lineasComanda)),
);
}
} catch (_) {}
}
});
on<RemoveUnidades>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
lineasComanda.firstWhere((item) => item == event.lineaComanda).unidades -= 1;
emit(
ComandaLoaded(comanda: state.comanda.copyWith(lineasComanda: lineasComanda)),
);
}
// LineaComanda lineaComanda =
// state.comanda.lineasComanda.firstWhere((element) => element == event.lineaComanda);
} catch (_) {}
}
});
}
}
UI:
class Comanda extends StatefulWidget {
const Comanda({Key? key}) : super(key: key);
#override
State<Comanda> createState() => _ComandaState();
}
class _ComandaState extends State<Comanda> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: BlocBuilder<ComandaBloc, ComandaState>(
builder: (context, state) {
if (state is ComandaLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is ComandaLoaded) {
return state.comanda.lineasComanda.isEmpty
? const EmptyList(
text: 'No existen artículos en la comanda',
)
: ListView(children: [
ExpansionPanelList(
elevation: 3,
// dividerColor: Colors.blue,
expandedHeaderPadding: const EdgeInsets.all(0),
expansionCallback: (index, isExpanded) {
setState(() {
state.comanda.lineasComanda[index].isExpanded = !isExpanded;
});
},
animationDuration: const Duration(milliseconds: 200),
children: state.comanda.lineasComanda
.map(
(item) => LineaComandaCard(item),
)
.toList(),
// Card_lineaComanda(flatButtonStyle),
),
]);
}
if (state is ComandaError) {
return Center(
child: Text(state.error.toString()),
);
}
return Container();
},
),
),
);
}
ExpansionPanel LineaComandaCard(LineaComanda lineaComanda) {
//Conusltamos el color de la famila
Color color = Colors.grey;
;
// do stuff here based on BlocA's state
return ExpansionPanel(
canTapOnHeader: true,
// backgroundColor: item['isExpanded'] == true ? Colors.cyan[100] : Colors.white,
headerBuilder: (context, isExpanded) {
return Container(
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(3),
border: Border(left: BorderSide(color: color, width: 3))),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(lineaComanda.articulo!.nombre, style: AppTheme.tituloCard),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: Row(
children: [
Text(
'Unidades:',
style: AppTheme.textTagsCard,
),
BlocBuilder<ComandaBloc, ComandaState>(
builder: (context, state) {
if (state is ComandaLoaded)
return Text(
' ${state.comanda.lineasComanda.firstWhere((item) => item == lineaComanda).unidades}',
style: AppTheme.textTagsCard,
key: ValueKey(lineaComanda.unidades),
);
return Container();
},
),
],
),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
),
const Text(" | "),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2),
child: Text(
'Pendientes: ${lineaComanda.unidades}',
style: AppTheme.textTagsCard,
),
),
(lineaComanda.unidadesPendientes >= lineaComanda.unidades)
? const Icon(
Icons.check_box_rounded,
color: AppTheme.greenOscuro,
size: 15,
)
: (lineaComanda.unidades == lineaComanda.unidadesPendientes)
? const Icon(Icons.disabled_by_default_rounded,
color: AppTheme.redOscuro, size: 15)
: const Icon(Icons.indeterminate_check_box_rounded,
color: AppTheme.yellow, size: 15),
const SizedBox(
width: 10,
),
// const SizedBox(
// width: 20,
// ),
],
),
],
));
},
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(top: 8, bottom: 3, left: 10, right: 10),
child: Text(
"Anotaciones:",
style: TextStyle(fontSize: 12),
),
),
const Padding(
padding: EdgeInsets.only(top: 0, left: 30),
child: Text(
"· Sin pepinillo.",
style: TextStyle(fontSize: 12),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
// alignment: MainAxisAlignment.spaceAround,
// buttonHeight: 12.0,
// buttonMinWidth: 10.0,
children: <Widget>[
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(AddUnidades(lineaComanda));
// setState(() {
// lineaComanda.unidades += 1;
// });
},
child: Column(
children: const <Widget>[
Icon(
Icons.add,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Más',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(RemoveUnidades(lineaComanda));
// setState(() {
// // lineaComanda.unidades -= 1;
// });
},
child: Column(
children: const <Widget>[
Icon(
Icons.remove,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Menos',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.edit_note_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Anotaciones',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(RemoveLineaComanda(lineaComanda));
},
child: Column(
children: const <Widget>[
Icon(
Icons.delete_outline_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Eliminar',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.local_offer_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Invitar',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
)
],
),
),
],
),
isExpanded: lineaComanda.isExpanded,
);
}
}
The add and remove command line events do work correctly and the UI is redrawed, but the add and remove unit events do not redraw the UI, although the event is executed.
I show console logs:
ADD/REMOVE Command Line:
I/flutter (13260): ComandaBloc AddLineaComanda(LineaComanda(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, - 1, YesNO.No, YesNO.No), 2, 0, [])) I/flutter (13260): Transition { currentState: CommandLoaded(Command([], null, null)), event: AddCommandLine(CommandLine(Item (389, , CHIVITO SNACK, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, -1, YesNO.No, YesNO.No), 2, 0, [])), nextState: CommandLoaded(Command([CommandLine(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, SioNO.No, SioNO.No, SioNO.No, -1 , 0, -1, YesNO.No, YesNO.No), 2, 0, [])], null, null)) }
ADD / REMOVE UNITS:
I/flutter (13260): CommandBloc AddUnits(CommandLine(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, - 1, YesNO.No, YesNO.No), 4, 0, []))
Any solution to redraw the UI correctly???
Thank you very much.
When you handle RemoveLineaComanda and AddLineaComanda events, you create a new instance of a List with List.from().
In the case of the RemoveUnidades and AddUnidades you emit a state with the same instance of List from the previous state. If you create a new List in for these events as well it should work properly.
This implementation works for me:
on<AddUnidades>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
// find the order of the event
int index = lineasComanda.indexOf(event.lineaComanda);
// make a new list from the state.
List<LineaComanda> nuevaLista = List.of(lineasComanda);
// edit the order by creating a new LineaComanda to replace the edited one.
nuevaLista[index] = lineasComanda[index]
.copyWith(unidades: lineasComanda[index].unidades + 1);
// emit a new state with the new list of orders.
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(lineasComanda: nuevaLista)),
);
}
} catch (_) {}
}
});
Similarly RemoveUnidades should become:
on<RemoveUnidades>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
int index = lineasComanda.indexOf(event.lineaComanda);
// make a new list from the state.
List<LineaComanda> nuevaLista = List.of(lineasComanda);
// edit the order by creating a new LineaComanda to replace the edited one.
nuevaLista[index] = lineasComanda[index]
.copyWith(unidades: lineasComanda[index].unidades - 1);
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(lineasComanda: nuevaLista)),
);
}
} catch (_) {}
}
});
If the user interface isn't getting redrawn it's because the emmited state is the same as before.
With "Same as before" i mean that the == operator returns true.
I see that you are using equatable.
I personally prefer to override the == so that I have more control when deciding wether or not the two objects are the same.
Anyway, in your case, carefully check that you have correctly overridden the propery props of all the types you are using (IE, I can't see the implementation for the class Articulo).
You could also write some unit tests to make sure that all the types you create correctly implement Equatable.

Exception caught by widgets library - Bad state

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static const String screenRoute = 'chat_screen';
const ChatScreen({Key? key}) : super(key: key);
#override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _firestore = FirebaseFirestore.instance;
final _auth = FirebaseAuth.instance;
late User signedInUser;
String? messageText;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() {
try {
final user = _auth.currentUser;
if (user != null) {
signedInUser = user;
print(signedInUser.email);
}
} catch (e) {
print(e);
}
}
//void getMessages() async {
// final messages = await _firestore.collection('messages').get();
//for (var message in messages.docs) {
// print(message.data());
// }
//}
void messagesStream() async {
await for (var snapshot in _firestore.collection('messages').snapshots()) {
for (var message in snapshot.docs) {
print(message.data());
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.yellow[900],
title: Row(
children: [
Image.asset('assets/images/logo.png', height: 25),
const SizedBox(width: 10),
const Text('ChatMe'),
],
),
actions: [
IconButton(
onPressed: () {
messagesStream();
//_auth.signOut();
//Navigator.pop(context);
},
icon: const Icon(Icons.download),
),
],
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
List<Text> messageWidgets = [];
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
final messages = snapshot.data!.docs;
for (var message in messages) {
final messageText = message.get('text');
final messageSender = message.get('sender');
final messageWidget = Text('$messageText - $messageSender');
messageWidgets.add(messageWidget);
}
return ListView(
children: messageWidgets,
);
},
),
Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.orange,
width: 2,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextField(
onChanged: (value) {
messageText = value;
},
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 20,
),
hintText: 'Write Your Message Here..',
border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_firestore.collection('messages').add({
'text': messageText,
'sender': signedInUser.email,
});
},
child: Text(
'send',
style: TextStyle(
color: Colors.blue[800],
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
),
],
),
),
);
}
}
════════ Exception caught by widgets library ═══════════════════════════════════
Bad state: field does not exist within the DocumentSnapshotPlatform
The relevant error-causing widget was
StreamBuilder<QuerySnapshot<Object?>>
lib\…\screens\chat_screen.dart:82
════════════════════════════════════════════════════════════════════════════════

Provider not rebuilding on flutter

suddently from nowhere i came up with provider not re rendering my home page when it's updated. I've inspected it and it IS UPDATED. It has newer data when i change it in firebase but the UI won't re-render showing the new data. That's my code:
Main function
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:my_event_app/pages/HomePage/home_page.dart';
import 'package:my_event_app/pages/Login/login_page.dart';
import 'package:my_event_app/providers/User/user.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// is not restarted.
primarySwatch: Colors.blue,
),
home: const Wrapper(),
),
);
}
}
class Wrapper extends StatelessWidget {
const Wrapper({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.userChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User? user = snapshot.data;
if (user == null) {
return const LoginPage();
}
return StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.snapshots(),
builder: (context, userSnapshot) {
if (userSnapshot.hasData) {
Provider.of<UserModel>(context, listen: true)
.fromJson(userSnapshot.data!.data());
return const HomePage();
}
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
});
} else {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
And this is the home page:
import 'package:flutter/material.dart';
import 'package:my_event_app/http/auth/user/sign_out.dart';
import 'package:my_event_app/pages/Create_Event/create_event_page.dart';
import 'package:my_event_app/pages/Onboarding/onboarding_page.dart';
import 'package:my_event_app/providers/User/user.dart';
import 'package:my_event_app/widgets/event_card_widget.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Consumer<UserModel>(builder: (context, user, child) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.help_outline, color: Colors.black, size: 30),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const OnboardingPage();
}));
},
),
actions: [
IconButton(
icon: const Icon(
Icons.arrow_forward_ios_sharp,
color: Colors.black,
),
onPressed: () {
signOut();
},
),
],
elevation: 0,
backgroundColor: Colors.white,
),
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Container(
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircleAvatar(
radius: 25,
backgroundImage: NetworkImage(
"https://cdnnmundo1.img.sputniknews.com/img/07e5/09/13/1116212032_100:0:1273:1173_1920x0_80_0_0_efb734331af13dfe11ff6d43293c60e2.png"),
),
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Colors.orange[400],
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Center(
child: IconButton(
color: Colors.white,
onPressed: () {
// Navigate to add event widget
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return const CreateEventPage();
}));
},
icon: const Icon(Icons.add),
),
),
),
],
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: Text('Welcome, ${user.name}',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
fontFamily: "Roboto")),
),
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.all(16),
height: 100,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(alignment: Alignment.center, children: [
SizedBox(
height: 45,
width: 45,
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation(Colors.orange[400]),
value: 14 / 20,
semanticsValue: "14/20",
color: Colors.black,
),
),
const Text("78%",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
fontFamily: "Roboto")),
]),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text("Weekly progress",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
)),
Text("14/20 tasks completed"),
],
),
const Icon(Icons.bar_chart),
],
),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: const [
Text("You have 5 tasks for today",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
)),
Icon(Icons.calendar_today_outlined)
],
),
),
_renderEvents(user),
],
),
),
),
);
});
}
}
Column _renderEvents(UserModel user) {
return Column(
children: [
for (var event in user.events)
EventCard(
eventId: event,
),
],
);
}
And here's the provider:
import 'package:flutter/material.dart';
class UserModel extends ChangeNotifier {
String _name = '';
String _surnames = '';
String _uid = '';
String _email = '';
List<dynamic> _events = [];
String get name => _name;
String get surnames => _surnames;
String get uid => _uid;
String get email => _email;
List<dynamic> get events => _events;
UserModel();
set name(String value) {
_name = value;
notifyListeners();
}
set surnames(String value) {
_surnames = value;
notifyListeners();
}
set uid(String value) {
_uid = value;
notifyListeners();
}
set email(String value) {
_email = value;
notifyListeners();
}
set events(List<dynamic> value) {
_events = value;
notifyListeners();
}
void addEvent(String event) {
_events.add(event);
notifyListeners();
}
void removeEvent(String event) {
_events.remove(event);
notifyListeners();
}
void updateUser(String name, String uid) {
name = name;
uid = uid;
notifyListeners();
}
void clearUser() {
_name = '';
_uid = '';
notifyListeners();
}
Map<String, dynamic> toJson() {
return {
'name': _name,
'surnames': _surnames,
'uid': _uid,
'email': _email,
'events': _events
};
}
fromJson(Object? json) {
try {
Map<dynamic, dynamic> map = json as Map<dynamic, dynamic>;
_name = map['name'];
_surnames = map['surnames'];
_uid = map['uid'];
_email = map['email'];
_events = map['events'];
} catch (e) {
print(e);
}
}
}
```
As you can see i use Consumer in order to read data and i have a change notifier in the begginig, but it won't re render and show for example new name if i change it in fireabase.
Thank you so much!
You are using fromJson method to update values in UserModel, but it does not call notifyListeners. Add notifyListeners(); to the end of this method:
fromJson(Object? json) {
try {
Map<dynamic, dynamic> map = json as Map<dynamic, dynamic>;
_name = map['name'];
_surnames = map['surnames'];
_uid = map['uid'];
_email = map['email'];
_events = map['events'];
notifyListeners(); // add this
} catch (e) {
print(e);
}
}
Also some other things:
Consider declaring class UserModel with ChangeNotifier instead of class UserModel extends ChangeNotifier.
fromJson methods usually are acting as factory methods, meaning these return a new instance, and don't set members in an existing instance.
Instead of Provider.of<UserModel>(context, listen: true).fromJson(userSnapshot.data!.data()); you can try: context.read<UserModel>().fromJson(userSnapshot.data!.data());. Here you don't really need listening, you just want to find the provider and call fromJson. Your Consumer is the one which is listening to the changes accordingly.

FLUTTER - DRAGGABLE & DRAG TARGET - How to deal with the "on will accept" and "on accept" parameters

I am building a small ANAGRAM game as a way to learn how to use the "drag and drop" features in Flutter.
I have managed to write some code, but for some reason, the dragged "letters" never seem to be accepted in the drag target.... I probably am using the "on will accept" parameter in a wrong way.
Here is the code :
class Anagram extends StatefulWidget {
static const String id = 'anagram';
final String? word;
Anagram({#required this.word});
#override
_AnagramState createState() => _AnagramState();
}
class _AnagramState extends State<Anagram> {
#override
Widget build(BuildContext context) {
List letters = widget.word!.split("");
letters.shuffle();
print(letters);
return Scaffold(
backgroundColor: Colors.indigo[900],
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_rounded,
color: Colors.red[400], size: 25.0),
onPressed: () {
Navigator.pop(context);
}),
title: Center(
child: Text(
'ANAGRAMS',
style: TextStyle(color: Colors.indigo[900], fontSize: 20),
),
),
actions: [
Container(
margin: const EdgeInsets.only(right: 10),
child: Icon(Icons.memory, size: 45, color: Colors.red[900]),
),
],
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
...letters.map((e) {
return TargetBox(letter: e);
}),
]),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
...letters.map((e) => DragBox(letter: e)),
]),
],
),
);
}
}
class DragBox extends StatelessWidget {
const DragBox({
required this.letter,
});
final String letter;
#override
Widget build(BuildContext context) {
return Draggable<String>(
feedback: LetterBox(letter: letter),
child: LetterBox(letter: letter),
childWhenDragging: LetterBox(letter: ' '),
);
}
}
class TargetBox extends StatelessWidget {
const TargetBox({
required this.letter,
});
final String letter;
#override
Widget build(BuildContext context) {
return DragTarget<String>(
builder: (context, candidateData, rejectedData) {
return LetterBox(letter: ' ');
},
onWillAccept: (data) => data == letter,
onAccept: (data) {
print('Je suis la');
print(data);
print(letter);
},
onLeave: (data) {
print('Voici data : $data');
print('Je suis parti');
print('Voici Letter : $letter');
});
}
}
class LetterBox extends StatelessWidget {
const LetterBox({
required this.letter,
});
final String letter;
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
border: Border.all(
width: 1,
color: Colors.indigo[900]!,
)),
child: Center(
child: Text(
letter.toUpperCase(),
style: TextStyle(
color: Colors.red[900],
fontSize: 18,
),
),
),
);
}
}
I also tried these changes : but same problem : DATA is always NULL :
class TargetBox extends StatelessWidget {
const TargetBox({
required this.letter,
});
final String letter;
#override
Widget build(BuildContext context) {
return DragTarget<DragBox>(
builder: (context, candidateData, rejectedData) {
return LetterBox(letter: ' ');
},
onWillAccept: (data) => data == DragBox(letter: letter),
onAccept: (data) {
print('Je suis la');
print(data);
print(letter);
},
onLeave: (data) {
print('Voici data : $data');
print('Je suis parti');
print('Voici Letter : $letter');
});
}
}
I found the answer to the problem : I had forgotten to specify the DATA field in the DRAGGABLE object...