Add new items in list without rerendering old items - flutter

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

Related

Change stepper's line color flutter

I want to change color of line in stepper, Look at the below picture
my try here :
Theme(
data: ThemeData(canvasColor: whiteColor),
child: Stepper(
type: StepperType.horizontal,
steps: getSteps(),
currentStep: currentStep,
elevation: 2,
),
),
Please help to continue my code.
Let me be short and clear.
TO CHANGE STEPPER LINE COLOR
Unfortunately, the Flutter code for the Stepper does not support the change of the Stepper line color (_buildLine) and more.
So I created a custom stepper called CusStepper that uses CusStep
NOTE: This CusStepper has the same properties and behaves like the normal Stepper with the only difference of lineColor property.
Steps on how to use the CusStepper
Create a new .dart file to store the CusStepper code
Paste the code below for the CusStepper in the file.
Import it to the file you want to use it in.
Finally change the stepper line color with the lineColor params. on the CusStepper
CusStepper code to paste in the .dart file created in step 1
// THIS IS A CUSTOM STEPPER THAT ADDS LINE COLOR
import 'package:flutter/material.dart';
enum CusStepState {
indexed,
editing,
complete,
disabled,
error,
}
enum CusStepperType {
vertical,
horizontal,
}
#immutable
class ControlsDetails {
const ControlsDetails({
required this.currentStep,
required this.stepIndex,
this.onStepCancel,
this.onStepContinue,
});
final int currentStep;
final int stepIndex;
final VoidCallback? onStepContinue;
final VoidCallback? onStepCancel;
bool get isActive => currentStep == stepIndex;
}
typedef ControlsWidgetBuilder = Widget Function(
BuildContext context, ControlsDetails details);
const TextStyle _kStepStyle = TextStyle(
fontSize: 12.0,
color: Colors.white,
);
const Color _kErrorLight = Colors.red;
final Color _kErrorDark = Colors.red.shade400;
const Color _kCircleActiveLight = Colors.white;
const Color _kCircleActiveDark = Colors.black87;
const Color _kDisabledLight = Colors.black38;
const Color _kDisabledDark = Colors.white38;
const double _kStepSize = 24.0;
const double _kTriangleHeight = _kStepSize * 0.866025;
#immutable
class CusStep {
const CusStep({
required this.title,
this.subtitle,
required this.content,
this.state = CusStepState.indexed,
this.isActive = false,
this.label,
});
final Widget title;
final Widget? subtitle;
final Widget content;
final CusStepState state;
final bool isActive;
final Widget? label;
}
class CusStepper extends StatefulWidget {
const CusStepper({
super.key,
required this.steps,
this.physics,
this.type = CusStepperType.vertical,
this.currentStep = 0,
this.onStepTapped,
this.onStepContinue,
this.onStepCancel,
this.controlsBuilder,
this.elevation,
this.margin,
this.lineColor = Colors.grey,
}) : assert(0 <= currentStep && currentStep < steps.length);
final List<CusStep> steps;
final ScrollPhysics? physics;
final CusStepperType type;
final int currentStep;
final ValueChanged<int>? onStepTapped;
final VoidCallback? onStepContinue;
final VoidCallback? onStepCancel;
final ControlsWidgetBuilder? controlsBuilder;
final double? elevation;
final EdgeInsetsGeometry? margin;
final Color lineColor;
#override
State<CusStepper> createState() => _CusStepperState();
}
class _CusStepperState extends State<CusStepper> with TickerProviderStateMixin {
late List<GlobalKey> _keys;
final Map<int, CusStepState> _oldStates = <int, CusStepState>{};
#override
void initState() {
super.initState();
_keys = List<GlobalKey>.generate(
widget.steps.length,
(int i) => GlobalKey(),
);
for (int i = 0; i < widget.steps.length; i += 1) {
_oldStates[i] = widget.steps[i].state;
}
}
#override
void didUpdateWidget(CusStepper oldWidget) {
super.didUpdateWidget(oldWidget);
assert(widget.steps.length == oldWidget.steps.length);
for (int i = 0; i < oldWidget.steps.length; i += 1) {
_oldStates[i] = oldWidget.steps[i].state;
}
}
bool _isFirst(int index) {
return index == 0;
}
bool _isLast(int index) {
return widget.steps.length - 1 == index;
}
bool _isCurrent(int index) {
return widget.currentStep == index;
}
bool _isDark() {
return Theme.of(context).brightness == Brightness.dark;
}
bool _isLabel() {
for (final CusStep step in widget.steps) {
if (step.label != null) {
return true;
}
}
return false;
}
Widget _buildLine(bool visible) {
return Container(
width: visible ? 1.0 : 0.0,
height: 16.0,
color: widget.lineColor,
);
}
Widget _buildCircleChild(int index, bool oldState) {
final CusStepState state =
oldState ? _oldStates[index]! : widget.steps[index].state;
final bool isDarkActive = _isDark() && widget.steps[index].isActive;
switch (state) {
case CusStepState.indexed:
case CusStepState.disabled:
return Text(
'${index + 1}',
style: isDarkActive
? _kStepStyle.copyWith(color: Colors.black87)
: _kStepStyle,
);
case CusStepState.editing:
return Icon(
Icons.edit,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case CusStepState.complete:
return Icon(
Icons.check,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
size: 18.0,
);
case CusStepState.error:
return const Text('!', style: _kStepStyle);
}
}
Color _circleColor(int index) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
if (!_isDark()) {
return widget.steps[index].isActive
? colorScheme.primary
: colorScheme.onSurface.withOpacity(0.38);
} else {
return widget.steps[index].isActive
? colorScheme.secondary
: colorScheme.background;
}
}
Widget _buildCircle(int index, bool oldState) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kStepSize,
height: _kStepSize,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: BoxDecoration(
color: _circleColor(index),
shape: BoxShape.circle,
),
child: Center(
child: _buildCircleChild(
index, oldState && widget.steps[index].state == CusStepState.error),
),
),
);
}
Widget _buildTriangle(int index, bool oldState) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kStepSize,
height: _kStepSize,
child: Center(
child: SizedBox(
width: _kStepSize,
height:
_kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
child: CustomPaint(
painter: _TrianglePainter(
color: _isDark() ? _kErrorDark : _kErrorLight,
),
child: Align(
alignment: const Alignment(
0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(index,
oldState && widget.steps[index].state != CusStepState.error),
),
),
),
),
);
}
Widget _buildIcon(int index) {
if (widget.steps[index].state != _oldStates[index]) {
return AnimatedCrossFade(
firstChild: _buildCircle(index, true),
secondChild: _buildTriangle(index, true),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: widget.steps[index].state == CusStepState.error
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
);
} else {
if (widget.steps[index].state != CusStepState.error) {
return _buildCircle(index, false);
} else {
return _buildTriangle(index, false);
}
}
}
Widget _buildVerticalControls(int stepIndex) {
if (widget.controlsBuilder != null) {
return widget.controlsBuilder!(
context,
ControlsDetails(
currentStep: widget.currentStep,
onStepContinue: widget.onStepContinue,
onStepCancel: widget.onStepCancel,
stepIndex: stepIndex,
),
);
}
final Color cancelColor;
switch (Theme.of(context).brightness) {
case Brightness.light:
cancelColor = Colors.black54;
break;
case Brightness.dark:
cancelColor = Colors.white70;
break;
}
final ThemeData themeData = Theme.of(context);
final ColorScheme colorScheme = themeData.colorScheme;
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
const OutlinedBorder buttonShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)));
const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0);
return Container(
margin: const EdgeInsets.only(top: 16.0),
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
child: Row(
children: <Widget>[
TextButton(
onPressed: widget.onStepContinue,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return states.contains(MaterialState.disabled)
? null
: (_isDark()
? colorScheme.onSurface
: colorScheme.onPrimary);
}),
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return _isDark() || states.contains(MaterialState.disabled)
? null
: colorScheme.primary;
}),
padding: const MaterialStatePropertyAll<EdgeInsetsGeometry>(
buttonPadding),
shape:
const MaterialStatePropertyAll<OutlinedBorder>(buttonShape),
),
child: Text(themeData.useMaterial3
? localizations.continueButtonLabel
: localizations.continueButtonLabel.toUpperCase()),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 8.0),
child: TextButton(
onPressed: widget.onStepCancel,
style: TextButton.styleFrom(
foregroundColor: cancelColor,
padding: buttonPadding,
shape: buttonShape,
),
child: Text(themeData.useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()),
),
),
],
),
),
);
}
TextStyle _titleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodyLarge!;
case CusStepState.disabled:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
TextStyle _subtitleStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodySmall!;
case CusStepState.disabled:
return textTheme.bodySmall!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodySmall!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
TextStyle _labelStyle(int index) {
final ThemeData themeData = Theme.of(context);
final TextTheme textTheme = themeData.textTheme;
switch (widget.steps[index].state) {
case CusStepState.indexed:
case CusStepState.editing:
case CusStepState.complete:
return textTheme.bodyLarge!;
case CusStepState.disabled:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kDisabledDark : _kDisabledLight,
);
case CusStepState.error:
return textTheme.bodyLarge!.copyWith(
color: _isDark() ? _kErrorDark : _kErrorLight,
);
}
}
Widget _buildHeaderText(int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedDefaultTextStyle(
style: _titleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].title,
),
if (widget.steps[index].subtitle != null)
Container(
margin: const EdgeInsets.only(top: 2.0),
child: AnimatedDefaultTextStyle(
style: _subtitleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
child: widget.steps[index].subtitle!,
),
),
],
);
}
Widget _buildLabelText(int index) {
if (widget.steps[index].label != null) {
return AnimatedDefaultTextStyle(
style: _labelStyle(index),
duration: kThemeAnimationDuration,
child: widget.steps[index].label!,
);
}
return const SizedBox.shrink();
}
Widget _buildVerticalHeader(int index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: <Widget>[
Column(
children: <Widget>[
_buildLine(!_isFirst(index)),
_buildIcon(index),
_buildLine(!_isLast(index)),
],
),
Expanded(
child: Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index),
),
),
],
),
);
}
Widget _buildVerticalBody(int index) {
return Stack(
children: <Widget>[
PositionedDirectional(
start: 24.0,
top: 0.0,
bottom: 0.0,
child: SizedBox(
width: 24.0,
child: Center(
child: SizedBox(
width: _isLast(index) ? 0.0 : 1.0,
child: Container(
color: widget.lineColor,
),
),
),
),
),
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Container(
margin: widget.margin ??
const EdgeInsetsDirectional.only(
start: 60.0,
end: 24.0,
bottom: 24.0,
),
child: Column(
children: <Widget>[
widget.steps[index].content,
_buildVerticalControls(index),
],
),
),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isCurrent(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration,
),
],
);
}
Widget _buildVertical() {
return ListView(
shrinkWrap: true,
physics: widget.physics,
children: <Widget>[
for (int i = 0; i < widget.steps.length; i += 1)
Column(
key: _keys[i],
children: <Widget>[
InkWell(
onTap: widget.steps[i].state != CusStepState.disabled
? () {
// In the vertical case we need to scroll to the newly tapped
// step.
Scrollable.ensureVisible(
_keys[i].currentContext!,
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
);
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != CusStepState.disabled,
child: _buildVerticalHeader(i),
),
_buildVerticalBody(i),
],
),
],
);
}
Widget _buildHorizontal() {
final List<Widget> children = <Widget>[
for (int i = 0; i < widget.steps.length; i += 1) ...<Widget>[
InkResponse(
onTap: widget.steps[i].state != CusStepState.disabled
? () {
widget.onStepTapped?.call(i);
}
: null,
canRequestFocus: widget.steps[i].state != CusStepState.disabled,
child: Row(
children: <Widget>[
SizedBox(
height: _isLabel() ? 104.0 : 72.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (widget.steps[i].label != null)
const SizedBox(
height: 24.0,
),
Center(child: _buildIcon(i)),
if (widget.steps[i].label != null)
SizedBox(
height: 24.0,
child: _buildLabelText(i),
),
],
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
),
),
if (!_isLast(i))
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: widget.lineColor,
),
),
],
];
final List<Widget> stepPanels = <Widget>[];
for (int i = 0; i < widget.steps.length; i += 1) {
stepPanels.add(
Visibility(
maintainState: true,
visible: i == widget.currentStep,
child: widget.steps[i].content,
),
);
}
return Column(
children: <Widget>[
Material(
elevation: widget.elevation ?? 2,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: children,
),
),
),
Expanded(
child: ListView(
physics: widget.physics,
padding: const EdgeInsets.all(24.0),
children: <Widget>[
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: stepPanels),
),
_buildVerticalControls(widget.currentStep),
],
),
),
],
);
}
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(() {
if (context.findAncestorWidgetOfExactType<Stepper>() != null) {
throw FlutterError(
'Steppers must not be nested.\n'
'The material specification advises that one should avoid embedding '
'steppers within steppers. '
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage',
);
}
return true;
}());
switch (widget.type) {
case CusStepperType.vertical:
return _buildVertical();
case CusStepperType.horizontal:
return _buildHorizontal();
}
}
}
class _TrianglePainter extends CustomPainter {
_TrianglePainter({
required this.color,
});
final Color color;
#override
bool hitTest(Offset point) => true;
#override
bool shouldRepaint(_TrianglePainter oldPainter) {
return oldPainter.color != color;
}
#override
void paint(Canvas canvas, Size size) {
final double base = size.width;
final double halfBase = size.width / 2.0;
final double height = size.height;
final List<Offset> points = <Offset>[
Offset(0.0, height),
Offset(base, height),
Offset(halfBase, 0.0),
];
canvas.drawPath(
Path()..addPolygon(points, true),
Paint()..color = color,
);
}
}
EXAMPLE CODEBASE ON HOW TO USE THE CusStepper
import 'package:flutter/material.dart';
import 'package:tester/stepper/stepper.dart'; // this is the file path of where you store your `cusStepper`
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Tester',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _index = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CusStepper(
lineColor: Colors.red, // new line add for the color change
currentStep: _index,
onStepCancel: () {
if (_index > 0) {
setState(() {
_index -= 1;
});
}
},
onStepContinue: () {
if (_index <= 0) {
setState(() {
_index += 1;
});
}
},
onStepTapped: (int index) {
setState(() {
_index = index;
});
},
steps: <CusStep>[
CusStep(
title: const Text('Step 1 title'),
content: Container(
alignment: Alignment.centerLeft,
child: const Text('Content for Step 1')),
),
const CusStep(
title: Text('Step 2 title'),
content: Text('Content for Step 2'),
),
],
),
),
);
}
}
OUTPUT
Leave a comment below if you have any questions or help on this.
Bye!
The Flutter Default stepper Line has Static color use so can't change this.
chack below Image.
Here https://fluttergems.dev/stepper mention many stepper package used as you want.

How to remove item from cart in flutter/ dart

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,
),
)

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.

Translate chat message based on language that user picks from drop down list

I am trying to get messages translated in real-time in the chat portion of my app depending on the language that the user picks in real-time. For example, if the user only speaks Spanish but messages from the user that they are chatting with are in English, then the user can select 'Spanish' from the dropdown list and all messages that have already been received and all future messages that they will receive will be translated into Spanish. I am capturing the sent message and its translation in each language in firebase but not sure how to get the messages to actually translate on the frontend. Any help would be much appreciated. Thank you in advance!
chat.dart
class Chat extends StatelessWidget {
final String? peerId;
final String? peerAvatar;
final String? name;
Chat({Key? key, this.peerId, this.peerAvatar, this.name}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: SkapeColors.pageBackgroundFifth,
resizeToAvoidBottomInset: true,
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.white,
),
backgroundColor: SkapeColors.pageBackground,
title: Text(
name!,
textAlign: TextAlign.start,
style: TextStyle(fontFamily: 'Brand-Bold', fontWeight: FontWeight.bold, color: Colors.white, fontSize: 25),
),
centerTitle: true,
),
body: ChatScreen(
peerId: peerId,
peerAvatar: peerAvatar,
name: name,
),
);
}
}
class ChatScreen extends StatefulWidget {
final String? peerId;
final String? peerAvatar;
final String? name;
ChatScreen({Key? key, this.peerId, this.peerAvatar, this.name})
: super(key: key);
#override
State createState() =>
ChatScreenState(peerId: peerId, peerAvatar: peerAvatar);
}
class ChatScreenState extends State<ChatScreen> {
ChatScreenState({Key? key, this.peerId, this.peerAvatar, this.name});
String? peerId;
String? peerAvatar;
String? name;
String? id;
String? language1 = Translations.languages.first;
String? language2 = Translations.languages.first;
final translator = GoogleTranslator();
static final _apiKey = 'hidden';
List<QueryDocumentSnapshot> listMessage = new List.from([]);
int _limit = 20;
int _limitIncrement = 20;
String groupChatId = "";
SharedPreferences? prefs;
File? imageFile;
bool isLoading = false;
bool isShowSticker = false;
String imageUrl = "";
final TextEditingController textEditingController = TextEditingController();
final ScrollController listScrollController = ScrollController();
final FocusNode focusNode = FocusNode();
_scrollListener() {
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
setState(() {
_limit += _limitIncrement;
});
}
}
#override
void initState() {
super.initState();
focusNode.addListener(onFocusChange);
listScrollController.addListener(_scrollListener);
readLocal();
}
readLocal() async {
prefs = await SharedPreferences.getInstance();
id = await getUserID();
if (id.hashCode <= peerId.hashCode) {
groupChatId = '$id-$peerId';
} else {
groupChatId = '$peerId-$id';
}
FirebaseFirestore.instance
.collection('users')
.doc(id)
.update({'chattingWith': peerId});
setState(() {});
}
static Future<String> translate(String message, String toLanguageCode) async {
final response = await http.post(
Uri.parse('https://translation.googleapis.com/language/translate/v2?target=$toLanguageCode&key=$_apiKey&q=$message'),
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
final translations = body['data']['translations'] as List;
final translation = translations.first;
return HtmlUnescape().convert(translation['translatedText']);
} else {
throw Exception();
}
}
static Future<String> translate2(
String message, String fromLanguageCode, String toLanguageCode) async {
final translation = await GoogleTranslator().translate(
message,
from: fromLanguageCode,
to: toLanguageCode,
);
return translation.text;
}
Future<void> onSendMessage(String content, int type) async {
// type: 0 = text, 1 = image, 2 = sticker
if (content.trim() != '') {
textEditingController.clear();
var documentReference = FirebaseFirestore.instance
.collection('messages')
.doc(groupChatId)
.collection(groupChatId)
.doc(DateTime.now().millisecondsSinceEpoch.toString());
FirebaseFirestore.instance.runTransaction((transaction) async {
transaction.set(
documentReference,
{
'idFrom': id,
'idTo': peerId,
'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
'content': content,
'translated': {
'english': await translate(content, 'en'),
'spanish': await translate(content, 'es'),
'german': await translate(content, 'de'),
'french': await translate(content, 'fr'),
'russian': await translate(content, 'ru'),
'italian': await translate(content, 'it'),
'selectedTranslation': language1,
},
'type': type
},
);
});
listScrollController.animateTo(0.0,
duration: Duration(milliseconds: 300), curve: Curves.easeOut);
try {
String body = content;
if (content.contains("firebasestorage")) {
body = "Image";
}
var tempResp = await getUserInformation();
await sendNotificationToUser(
peerId, "New message from " + tempResp["fullName"], body);
} catch (e) {
print(e);
}
} else {
Fluttertoast.showToast(
msg: 'Nothing to send. Please insert your message',
backgroundColor: Colors.white24,
textColor: SkapeColors.colorPrimary);
}
}
Widget buildItem(int index, DocumentSnapshot document) {
String language1 = Translations.languages.first;
String language2 = Translations.languages.first;
if (document != null) {
if (document.get('idFrom') == id) {
// Right (my message)
return Row(
children: <Widget>[
document.get('type') == 0
// Text
? Container(
child: TranslationWidget(
message: document.get('content'),
fromLanguage: language1,
toLanguage: language1,
builder: (translatedMessage)=> MessageWidget(message: document.get('content'), translatedMessage: document.get('content'), isMe: true),
),
padding: EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
width: 215.0,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
)
: document.get('type') == 1
// Image
? Container(
child: OutlinedButton(
child: Material(
child: Image.network(
document.get("content"),
loadingBuilder: (BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
width: 200.0,
height: 200.0,
child: Center(
child: CircularProgressIndicator(
color: SkapeColors.colorPrimary,
value: loadingProgress
.expectedTotalBytes !=
null &&
loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius:
BorderRadius.all(Radius.circular(8.0)),
clipBehavior: Clip.hardEdge,
),
onPressed: () {
print("here");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullPhoto(
url: document.get('content'),
),
),
);
},
// Sticker
: Container(
child: Image.asset(
'images/${document.get('content')}.gif',
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
),
],
mainAxisAlignment: MainAxisAlignment.end,
);
} else {
// Left (peer message)
return Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
isLastMessageLeft(index)
? Material(
child: Image.network(
peerAvatar!,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
color: SkapeColors.colorPrimary,
value: loadingProgress.expectedTotalBytes !=
null &&
loadingProgress.expectedTotalBytes !=
null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, object, stackTrace) {
return Image.asset(
'images/user_icon.png',
height: 50,
width: 50,
);
},
width: 40,
height: 40,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(35.0),
),
clipBehavior: Clip.hardEdge,
)
: Container(width: 35.0),
document.get('type') == 0
? Container(
child:
TranslationWidget(
message: document.get('content'),
fromLanguage: language1,
toLanguage: language1,
builder: (translatedMessage)=> MessageWidget(message: document.get('content'), translatedMessage: document.get('content'), isMe: false),
),
padding: EdgeInsets.fromLTRB(10.0, 5.0, 5.0, 5.0),
width: 215.0,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
).paddingOnly(left: 12)
: document.get('type') == 1
? Container(
child: TextButton(
child: Material(
child: Image.network(
document.get('content'),
loadingBuilder: (BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
width: 200.0,
height: 200.0,
child: Center(
child: CircularProgressIndicator(
color: SkapeColors.colorPrimary,
value: loadingProgress
.expectedTotalBytes !=
null &&
loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius:
BorderRadius.all(Radius.circular(8.0)),
clipBehavior: Clip.hardEdge,
),
onPressed: () {
},
)
: Container(
),
],
),
)
],
crossAxisAlignment: CrossAxisAlignment.start,
),
margin: EdgeInsets.only(bottom: 2.0),
);
}
} else {
return SizedBox.shrink();
}
}
bool isLastMessageLeft(int index) {
if ((index > 0 && listMessage[index - 1].get('idFrom') == id) ||
index == 0) {
return true;
} else {
return false;
}
}
bool isLastMessageRight(int index) {
if ((index > 0 && listMessage[index - 1].get('idFrom') != id) ||
index == 0) {
return true;
} else {
return false;
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()=> Future.value(true),
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
// List of messages
buildListMessage(),
// Input content
buildInput(),
],
),
// Loading
buildLoading()
],
),
// onWillPop: onBackPress,
);
}
Widget buildLoading() {
return Positioned(
child: isLoading ? const Loading() : Container(),
);
}
Widget buildInput() {
return Container(
child: SingleChildScrollView(
child: Column(
children: [
buildTitle().paddingBottom(10),
Row(
),
SizedBox(width: 10,),
Flexible(
child: Container(
child: TextField(
cursorColor: SkapeColors.colorPrimary,
autocorrect: true,
onSubmitted: (value) {
onSendMessage(textEditingController.text, 0);
},
style: TextStyle(color: Colors.white, fontSize: 18.0),
controller: textEditingController,
decoration: InputDecoration.collapsed(
hintText: 'Send Message....',
hintStyle: TextStyle(color: SkapeColors.colorTextSemiLight),
),
focusNode: focusNode,
),
),
),
// Button send message
color: SkapeColors.pageBackground,
),
],
),
],
),
),
width: double.infinity,
height: 155.0,
// height: 100,
);
}
Widget buildListMessage() {
return Flexible(
child: groupChatId.isNotEmpty
? StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('messages')
.doc(groupChatId)
.collection(groupChatId)
.orderBy('timestamp', descending: true)
.limit(_limit)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
listMessage.addAll(snapshot.data!.docs);
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) => buildItem(index, snapshot.data!.docs[index]),
itemCount: snapshot.data?.docs.length,
reverse: true,
controller: listScrollController,
);
} else {
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(SkapeColors.colorPrimary),
),
);
}
},
)
: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(SkapeColors.colorPrimary),
),
),
);
}
Widget buildTitle() => TitleWidget(
language1: language1,
onChangedLanguage1: (newLanguage) => setState(() {
language1 = newLanguage;
}), key: ValueKey(DropDownWidget),
);
}
TitleWidget.dart
import 'package:flutter/material.dart';
import '../screens/messaging/chatWidgets/DropDownWidget.dart';
class TitleWidget extends StatelessWidget {
final String? language1;
final ValueChanged<String?> onChangedLanguage1;
const TitleWidget({
required this.language1,
required this.onChangedLanguage1,
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.translate, color: Colors.grey, size: 30,),
SizedBox(width: 12,),
DropDownWidget(
value: language1??"",
onChangedLanguage: onChangedLanguage1, key: key!,
),
],
);
}
translations.dart
class Translations {
static final languages = <String>[
'English',
'Spanish',
'French',
'German',
'Italian',
'Russian'
];
static String getLanguageCode(String language) {
switch (language) {
case 'English':
return 'en';
case 'French':
return 'fr';
case 'Italian':
return 'it';
case 'Russian':
return 'ru';
case 'Spanish':
return 'es';
case 'German':
return 'de';
default:
return 'en';
}
}
}
MessageWidget.dart
import 'package:flutter/material.dart';
class MessageWidget extends StatelessWidget {
final String? message;
final String? translatedMessage;
final bool isMe;
const MessageWidget({
required this.message,
required this.translatedMessage,
required this.isMe,
});
#override
Widget build(BuildContext context) {
final radius = Radius.circular(4);
final borderRadius = BorderRadius.all(radius);
return Row(
//To align at different positions based on if message is from the user or not
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
// padding: EdgeInsets.only(right: 10),
// margin: EdgeInsets.only(right: 10),
constraints: BoxConstraints(maxWidth: 190),
decoration: BoxDecoration(
color: isMe ? Theme.of(context).colorScheme.secondary : Colors.grey,
borderRadius: isMe
? borderRadius.subtract(BorderRadius.only(bottomRight: radius))
: borderRadius.subtract(BorderRadius.only(bottomLeft: radius)),
),
child: buildMessage(),
),
],
);
}
Widget buildMessage() => Column(
crossAxisAlignment:
isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
// Text(
// message,
// style: TextStyle(
// color: isMe ? Colors.black54 : Colors.white70,
// fontSize: 14,
// ),
// textAlign: isMe ? TextAlign.end : TextAlign.start,
// ),
Text(
translatedMessage!,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.start,
),
],
);
}
TranslationWidget.dart
class TranslationWidget extends StatefulWidget {
final String? message;
final String? fromLanguage;
final String? toLanguage;
final Widget Function(String? translation) builder;
const TranslationWidget({
required this.message,
required this.fromLanguage,
required this.toLanguage,
required this.builder,
Key? key,
}) : super(key: key);
#override
_TranslationWidgetState createState() => _TranslationWidgetState();
}
class _TranslationWidgetState extends State<TranslationWidget> {
String? translation;
#override
Widget build(BuildContext context) {
// final fromLanguageCode = Translations.getLanguageCode(widget.fromLanguage);
final toLanguageCode = Translations.getLanguageCode(widget.toLanguage!);
return FutureBuilder(
future: TranslationApi.translate(widget.message!, toLanguageCode),
//future: TranslationApi.translate2(
// widget.message, fromLanguageCode, toLanguageCode),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return buildWaiting();
default:
if (snapshot.hasError) {
translation = 'Could not translate due to Network problems';
} else {
translation = snapshot.data!;
}
return widget.builder(translation??"");
}
},
);
}
Widget buildWaiting() =>
translation == null ? Container() : widget.builder(translation??"");
}

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