How to remove item from cart in flutter/ dart - flutter

How to remove cart items from a list in flutter when you have a seperate list widget ?
I have three files that contain the following code.
carts.dart => where I display all the list items
cart_list_item => where I have created a widget.
api_services.dart => from where I am accessing the functions.
It was working when the cart list widget and cart list was in a single file.
but to increment cart quantity I had to separate them.
CARTS.DART
class _CartsListState extends State<CartsList> {
List cartList = [];
getProducts() async {
var resp = await http.get(
Uri.parse("https://abc"));
cartList.addAll(jsonDecode(resp.body));
setState(()=>{});
return jsonDecode(resp.body);
}
#override
void initState() {
super.initState();
getProducts();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(10),
child: FutureBuilder(
future: getProducts(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: cartList.length,
itemBuilder: (BuildContext context, int index) {
var path = "https://abc";
var img = cartList[index]['image'] ?? 'default.png';
return MyCartListItem(
cartName: cartList[index]['english_name'],
cartQuantity: 2,
cartImage: path + img,
cartPrice: cartList[index]['mrp'].toString(),
cartIndex: 5);
},
);
} else {
return const LinearProgressIndicator();
}
},
),
),
);
}
}
cart_list_item.dart
class MyCartListItem extends StatefulWidget {
const MyCartListItem(
{Key? key,
required this.cartName,
required this.cartQuantity,
required this.cartImage,
required this.cartPrice,
required this.cartIndex})
: super(key: key);
final String cartName;
final int cartQuantity;
final String cartImage;
final String cartPrice;
final int cartIndex;
#override
State<MyCartListItem> createState() => _MyCartListItemState();
}
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(10.5),
child: Row(children: [
const SizedBox(
width: 10,
),
SizedBox(
width: 70,
height: 70,
child: Image.network(widget.cartImage),
),
const SizedBox(
width: 50,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
// ignore: prefer_const_literals_to_create_immutables
children: [
Container(
child: Text(
widget.cartName,
style: const TextStyle(
overflow: TextOverflow.clip,
),
),
),
const SizedBox(
width: 10,
),
],
),
const SizedBox(
height: 7,
),
Text(
"Rs. ${widget.cartPrice}",
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.grey),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
// setState() {
// _itemCount--;
// print(_itemCount);
// }
},
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.deepOrange[50],
),
child: const Icon(
CupertinoIcons.minus,
)),
),
const SizedBox(
width: 10,
),
Text(_itemCount.toString()),
const SizedBox(
width: 10,
),
InkWell(
// onTap: () {
// setState() => {_itemCount++};
// },
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.deepOrange[50],
),
child: const Icon(
CupertinoIcons.plus,
)),
),
const SizedBox(
width: 15,
),
InkWell(
onTap: () {
ApiServices.removeCartItem(0);
// setState(() {});
final snackBarData = SnackBar(
behavior: SnackBarBehavior.floating,
content: Row(
children: const [
Icon(Icons.shopping_bag),
SizedBox(
width: 10,
),
Text('Product removed from cart !!!'),
],
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBarData);
},
child: const Icon(
CupertinoIcons.trash,
color: Colors.red,
),
)
],
)
],
)
]),
),
);
}
}
api services.dart*
static removeCartItem(int indexNumber) async {
cartList.removeAt(indexNumber);
}

You need to update your view because data in list was changed. If you are using setState for in, implement callback in CartListItem that allows you to handle item deletion and update view in this callback. It should be looks like:
class MyCartListItem extends StatefulWidget {
const MyCartListItem(
{Key? key,
required this.cartName,
required this.cartQuantity,
required this.cartImage,
required this.cartPrice,
required this.cartIndex,
required this.onItemRemoved,
}) : super(key: key);
final String cartName;
final int cartQuantity;
final String cartImage;
final String cartPrice;
final int cartIndex;
final Function onItemRemoved;
#override
State<MyCartListItem> createState() => _MyCartListItemState();
}
And in CartListItem:
onTap: () => widget.onItemRemoved(index);

its already answered, but if you still had i problem, i try to make it clear, you can ask later if not working.
in you listViewBuilder :
return ListView.builder(
itemCount: cartList.length,
itemBuilder: (BuildContext context, int index) {
var path = "https://abc";
var img = cartList[index]['image'] ?? 'default.png';
return MyCartListItem(
cartName: cartList[index]['english_name'],
cartQuantity: 2,
cartImage: path + img,
cartPrice: cartList[index]['mrp'].toString(),
cartIndex: 5,
//------- this line -------------
removeFunction:(){
ApiServices.removeCartItem(index);
}
);
},
);
in your Cardlistitem :
class MyCartListItem extends StatefulWidget {
const MyCartListItem(
{Key? key,
required this.cartName,
required this.cartQuantity,
required this.cartImage,
required this.cartPrice,
required this.cartIndex,
required this.removeFuntion,
}) : super(key: key);
final String cartName;
final int cartQuantity;
final String cartImage;
final String cartPrice;
final int cartIndex;
final Function removeFunction;
#override
State<MyCartListItem> createState() => _MyCartListItemState();
}
.... Rest OF code
// and here :
InkWell(
onTap: () {
//------- THIS LINE -----:
widget.removeFunction;
//-----------------------
// setState(() {});
final snackBarData = SnackBar(
behavior: SnackBarBehavior.floating,
content: Row(
children: const [
Icon(Icons.shopping_bag),
SizedBox(
width: 10,
),
Text('Product removed from cart !!!'),
],
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBarData);
},
child: const Icon(
CupertinoIcons.trash,
color: Colors.red,
),
)

Related

Search from a list of Firebase Users with TextField and StreamProvider in Flutter

I'm building a chat app with Firebase in flutter and I want to be able to search from a list of users,. I also want that when no text is typed, no user should be shown
I tried a lot of things but I never got it right. This is what I did :
search_page.dart:
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
#override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
TextEditingController searchController = TextEditingController();
#override
void initState() {
super.initState();
searchController.addListener(_onSearchChanged);
}
_onSearchChanged() {
print(searchController.text);
}
#override
void dispose() {
searchController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamProvider<List<AppUserData>>.value(
initialData: [],
value: DatabaseService().users,
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: dDarkGrey,
body:
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
const SizedBox(
height: 3,
),
Stack(
alignment: Alignment.center,
children: [
Container(
height: 90,
decoration: BoxDecoration(color: dDarkGrey, boxShadow: [
BoxShadow(
color: dBlack.withOpacity(0.16),
spreadRadius: 3,
blurRadius: 3,
offset: const Offset(0, 4))
]),
),
Column(
children: [
const SizedBox(
height: 20,
),
Row(
children: [
IconButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const HomePage()));
},
icon: const Icon(SocketIconv2.ic_back),
color: dLightGrey,
),
SizedBox(
width: MediaQuery.of(context).size.width - 60,
child: TextField(
enabled: true,
controller: searchController,
style: const TextStyle(color: dLightGrey),
decoration: const InputDecoration(
contentPadding:
EdgeInsets.fromLTRB(0, 0, 0, 0),
filled: true,
fillColor: dDarkGrey,
prefixIcon: Icon(
SocketIconv2.ic_search,
color: dLightGrey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15))),
hintStyle: TextStyle(
color: dLightGrey,
fontFamily: 'SegoeUI',
fontSize: 18,
fontWeight: FontWeight.w300),
hintText: 'Search',
))),
],
),
],
),
],
),
// search bar
SizedBox(
height: MediaQuery.of(context).size.height - 95,
width: MediaQuery.of(context).size.width,
child: SearchList(
controller: searchController,
),
)
])),
);
}
}
search_list.dart:
class SearchList extends StatelessWidget {
SearchList({Key? key, required this.controller}) : super(key: key);
final TextEditingController controller;
#override
Widget build(BuildContext context) {
final users = Provider.of<List<AppUserData>>(context);
return Scaffold(
backgroundColor: Colors.transparent,
body: ListView.separated(
itemBuilder: (context, index) {
if (controller.text.isEmpty) {
return Container();
}
if (controller.text.isNotEmpty) {
return searchAccount(context, name, username, users[index]);
}
if (users[index].name.startsWith(controller.text.toLowerCase())) {
return searchAccount(context, name, username, users[index]);
} else {
return Container();
}
},
itemCount: users.length,
separatorBuilder: (context, index) => const SizedBox(height: 20),
));
}
}
search_account.dart:
Widget searchAccount(
BuildContext context, String name, String username, AppUserData users) {
return Row(
children: [
const SizedBox(
width: 20,
),
Row(
children: [
ClipOval(
child: Image.asset(
imagePp,
scale: 9,
),
),
const SizedBox(
width: 30,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(users.name,
style: SegoeUiPreset(dLightGrey).customTileName()),
Text(users.username,
style: SegoeUiPreset(dLightGrey).customTileSubtitle())
],
)
],
),
],
);
}
user.dart:
class AppUser {
final String? uid;
AppUser({this.uid});
}
class AppUserData {
final String? uid;
final String name;
final String username;
AppUserData({this.uid, required this.name, required this.username});
}
database.dart:
class DatabaseService {
final String? uid;
DatabaseService({this.uid});
final CollectionReference chatRoomCollection =
FirebaseFirestore.instance.collection("chatrooms");
final CollectionReference userCollection =
FirebaseFirestore.instance.collection("users");
Future<void> saveUser(String name, String username) async {
return await userCollection
.doc(uid)
.set({'name': name, 'username': username});
}
AppUserData _userFromSnapshot(DocumentSnapshot snapshot) {
return AppUserData(
name: snapshot['name'],
uid: snapshot.id,
username: snapshot['username']);
}
Stream<AppUserData> get user {
return userCollection.doc(uid).snapshots().map(_userFromSnapshot);
}
List<AppUserData> _userListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return _userFromSnapshot(doc);
}).toList();
}
Stream<List<AppUserData>> get users {
return userCollection.snapshots().map(_userListFromSnapshot);
}
}
Got any hints ? I'm a begginner :/
Thank you in advance :)

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));
// },),
),
],
);
}
}

Get all the scores from all widgets

I am building a quiz app and I created a custom widget to save me a lot of time as I have a lot of questions for the quiz. Everything works apart from the scoring system. If I create multiple instances of the same widget the score will not be incremented and it will stay on 1. Is there any way I can pass each score of the widgets to a global variable in my main widget so then I can add all the scores? (I'm new to flutter).
Custom Widget
class Questions extends StatefulWidget {
final String imagePath;
final String question;
final String answer1;
final String answer2;
final String answer3;
final String answer4;
final bool iscorrectAnswer1;
final bool iscorrectAnswer2;
final bool iscorrectAnswer3;
final bool iscorrectAnswer4;
int score = 0;
bool questionsAnswered = false;
Questions(
this.imagePath,
this.question,
this.answer1,
this.answer2,
this.answer3,
this.answer4,
this.iscorrectAnswer1,
this.iscorrectAnswer2,
this.iscorrectAnswer3,
this.iscorrectAnswer4,
);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
disableButton() {
setState(() {
widget.questionsAnswered = true;
Quiz().score += widget.score;
});
}
#override
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: 600,
height: 600,
child: Image.asset(widget.imagePath),
),
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(
top: 20,
),
child: Text(
widget.question,
style: TextStyle(
color: Colors.white,
fontSize: 38,
),
),
)),
Padding(
padding: EdgeInsets.only(
top: 40,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color(0xFF304e60),
),
),
child: Text(
widget.answer1,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer1 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
disableButton();
widget.score += 1;
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
print(widget.iscorrectAnswer1);
print(widget.score);
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF565462))),
child: Text(
widget.answer2,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer2 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF84693b))),
child: Text(
widget.answer3,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer3 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
),
)
],
);
}
}
Main widget where I call this custom widget
class Quiz extends StatefulWidget {
Quiz({Key? key}) : super(key: key);
int score = 0;
#override
_QuizState createState() => _QuizState();
}
class _QuizState extends State<Quiz> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('CyberQuiz'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Questions(
'images/malware_quiz.jpeg',
'1. What is a malware?',
'Designed to damage computers, servers or any other devices',
"Used to get user's credentials",
"It's used to destroy networks",
'',
true,
false,
false,
false,
),
],
)));
}
}
As you suggest in your question, you could create a global variable and increment/decrease/reset that.
Basic example code:
import 'package:flutter/material.dart';
class Score {
static int score = 0;
}
class ScoreCounter extends StatefulWidget {
const ScoreCounter({Key? key}) : super(key: key);
#override
State<ScoreCounter> createState() => _ScoreCounterState();
}
class _ScoreCounterState extends State<ScoreCounter> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
Score.score++;
});
},
child: Text('increase score'),
),
),
Expanded(child: Text(Score.score.toString()))
],
);
}
}
Another option is to use the Provider package - link here which has an example
Provider Package

Shopping Cart Counter not updating value when pressed, no Error is shown for the matter

I have a small code for a shopping cart counter in my app, when running the app it does not update upon pressing the add or remove button (+ & - icons), although I assigned the functions for both of them, no errors are shown as to why this is happening...
This is the code for the counter:
import 'package:flutter/material.dart';
class CartCounter extends StatefulWidget {
#override
_CartCounterState createState() => _CartCounterState();
}
class _CartCounterState extends State<CartCounter> {
int numOfItems = 0;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
buildOutlineButton(
icon: Icons.remove,
press: () {
if (numOfItems > 0) {
setState(() {
numOfItems--;
});
}
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: kDefaultPaddin / 2),
child: Text(
// if our item is less then 10 then it shows 01 02 like that
numOfItems.toString().padLeft(2, "0"),
style: Theme.of(context).textTheme.headline6,
),
),
buildOutlineButton(
icon: Icons.add,
press: () {
if (numOfItems < 10) {
setState(() {
numOfItems++;
});
}
}),
],
);
}
SizedBox buildOutlineButton(
{required IconData icon, required Function press}) {
return SizedBox(
width: 40,
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(13.0)),
),
onPressed: press(),
child: Icon(icon),
),
);
}
}
And this is the code where I call the Cart Counter class, it also has a rating bar that works perfectly fine:
class CounterWithRateBar extends StatefulWidget {
#override
_CounterWithRateBarState createState() => _CounterWithRateBarState();
}
class _CounterWithRateBarState extends State<CounterWithRateBar> {
// const CounterWithRateBar({
// Key? key,
// }) : super(key: key);
late double _rating;
int _ratingBarMode = 1;
double _initialRating = 2.0;
IconData? _selectedIcon;
#override
void initState() {
super.initState();
_rating = _initialRating;
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CartCounter(),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children:
<Widget>[
SizedBox(
height: 10.0,
),
_ratingBar(_ratingBarMode),
SizedBox(height: 5.0),
Text(
'Rating: $_rating',
style: TextStyle(fontWeight: FontWeight.bold),
),
]
),
],
);
}
Widget _ratingBar(int mode) {
return RatingBar.builder(
initialRating: _initialRating,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
unratedColor: Colors.amber.withAlpha(50),
itemCount: 5,
itemSize: 25.0,
itemPadding: EdgeInsets.symmetric(horizontal: 2.0),
itemBuilder: (context, _) => Icon(
_selectedIcon ?? Icons.star,
color: Colors.amber,
),
onRatingUpdate: (rating) {
setState(() {
_rating = rating;
});
},
updateOnDrag: true,
);
}
}
You have small mistake in buildOutlineButtonDefinition
You immediately calling press function and not using is as callback;
From
onPressed: press(),
To
onPressed: () => press(),
Full definition would be
SizedBox buildOutlineButton(
{required IconData icon, required Function press}) {
return SizedBox(
width: 40,
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(13.0)),
),
onPressed: () => press(),
child: Icon(icon),
),
);
}

How to show all dates in a horizontal scroll view

I want to have all the dates of a month in a horizontal scroll view. The current week 7 days should be displayed first and on scrolling right the previous dates should be shown. later on scrolling left the later weeks dates should be displayed an don tap of a date i should get the date in return. How to do this? I have tried using the below. It displays dates and scrolls horizontally as well but it displays only multiple of 7 and all the exact dates of a month. Also on tapping the date it does not return the position form listview builder as 0 and i returns the index.
Widget displaydates(int week) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
});
}
I am calling this like:
displaydates(0),
displaydates(1),
displaydates(2),
displaydates(3),
displaydates(4),
UPDATE:
You can get last day of month using below code :
DateTime(year,month + 1).subtract(Duration(days: 1)).day;
You should use ModalBottomSheet for the same and then pop that on selection with the Navigator.of(context).pop(result);
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int position) {
return Row(
children: <Widget>[
for (int i = 1; i < 8; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(position);
},
child: Text(
((week * 7) + i).toString() + " ",
style: TextStyle(),
),
),
),
],
);
}
);
}
);
I have created a widget that let me select date from a list and it is scrollable(along time ago). There lot of code but you can use the selected date under any widget which parent wrapped from InheritedWidget.
Here is the code(Note that I also created a package for this, if you dont like to write this much code for this):
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyInheritedWidget extends InheritedWidget {
final DateTime date;
final int selectedDay;
final int monthDateCount;
final bool isDateHolderActive;
final Map<int, bool> dayAvailabilityMap;
final ValueChanged<bool> toggleDateHolderActive;
final ValueChanged<int> setSelectedDay;
MyInheritedWidget({
Key key,
this.date,
this.selectedDay,
this.monthDateCount,
this.isDateHolderActive,
this.dayAvailabilityMap,
this.toggleDateHolderActive,
this.setSelectedDay,
Widget child,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.selectedDay != selectedDay ||
oldWidget.toggleDateHolderActive != toggleDateHolderActive;
}
}
class DateIndicatorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DateIndicator(),
Expanded(
child: Container(),
),
],
),
),
);
}
}
class DateIndicator extends StatefulWidget {
static MyInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
#override
_DateIndicatorState createState() => _DateIndicatorState();
}
class _DateIndicatorState extends State<DateIndicator> {
DateTime date = DateTime.now();
int selectedDay = 1;
int monthDateCount = 1;
bool isDateHolderActive = false;
Map<int, bool> dayAvailabilityMap = {};
void toggleDateHolderActive(bool flag) {
setState(() {
isDateHolderActive = flag;
});
}
void setSelectedDay(int index) {
setState(() {
selectedDay = index;
});
}
#override
void initState() {
final DateTime dateForValues = new DateTime(date.year, date.month + 1, 0);
monthDateCount = dateForValues.day;
// Just to show how to activate when something exist for this day(from network response or something)
dayAvailabilityMap[1] = true;
dayAvailabilityMap[2] = true;
dayAvailabilityMap[3] = true;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 68.0,
padding:
const EdgeInsets.only(left: 7.0, right: 3.0, top: 2.0, bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(.7),
offset: Offset(0.0, .5),
blurRadius: 3.0,
spreadRadius: 0.3),
],
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: monthDateCount, // to avoid showing zero
itemBuilder: (BuildContext context, int index) {
return MyInheritedWidget(
date: date,
selectedDay: selectedDay,
monthDateCount: monthDateCount,
isDateHolderActive: isDateHolderActive,
dayAvailabilityMap: dayAvailabilityMap,
toggleDateHolderActive: toggleDateHolderActive,
setSelectedDay: setSelectedDay,
child: DateHolder(index));
}),
);
}
}
class DateHolder extends StatelessWidget {
DateHolder(this.index);
final int index;
final Widget activeBubble = Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepOrangeAccent,
),
);
#override
Widget build(BuildContext context) {
final appState = DateIndicator.of(context);
return InkWell(
onTap: () {
appState.toggleDateHolderActive(true);
appState.setSelectedDay(index);
print("Date ${index} selected!");
},
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(
"${DateFormat('EEEE').format(DateTime(appState.date.year, appState.date.month, index)).substring(0, 1)}",
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.0),
)),
Container(
width: 45.0,
height: 45.0,
margin: const EdgeInsets.only(right: 5.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: (index == (appState.selectedDay) &&
appState.isDateHolderActive == true)
? Border.all(width: 2.0, color: Theme.of(context).primaryColor)
: Border.all(color: Colors.transparent),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
"${index + 1}", // to avoid showing zero
style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 16.0),
),
),
),
),
],
),
(appState.dayAvailabilityMap[index] ?? false)
? Positioned(right: 8.0, bottom: 5.0, child: activeBubble)
: Container(),
],
),
);
}
}