I would like to keep multiple TextField to hold values - flutter

I am developing a fill-in-the-blanks quiz app.
Each quiz contains 5 sentences and they are arranged by PageView. The sentences are retrieved from a List.
Each sentence is a TextWithBlanks class and they have several BlankWord class (which are TextField).
class TextWithBlanks extends StatefulWidget {
final String text;
static final regex = RegExp("(?={)|(?<=})");
List correctList = [];
TextWithBlanks({Key? key, required this.text, required this.correctList})
: super(key: key);
#override
State<TextWithBlanks> createState() => _TextWithBlanksState();
}
class _TextWithBlanksState extends State<TextWithBlanks> {
#override
Widget build(BuildContext context) {
final split = widget.text.split(TextWithBlanks.regex);
return Padding(
padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
child: Text.rich(
TextSpan(
style: const TextStyle(fontSize: 15, height: 3.0),
children: <InlineSpan>[
for (String text in split)
text.startsWith('{')
? WidgetSpan(
child: blankWord(text.substring(1, text.length - 1),
widget.correctList),
)
: TextSpan(text: text),
],
),
),
);
}
}
class blankWord extends StatefulWidget {
final String answer;
int answerLength = 0;
double answerWidth = 0.0;
String answerHint = "";
List correctList;
String value = "";
bool answerBool = false;
blankWord(this.answer, this.correctList, {Key? key}) : super(key: key) {
answerLength = answer.length;
answerWidth = answerLength * 15.0;
answerHint = answer;
}
#override
State<blankWord> createState() => _blankWordState();
}
class _blankWordState extends State<blankWord> {
Widget build(BuildContext context) {
return SizedBox(
width: widget.answerWidth,
child: TextFormField(
maxLines: null,
cursorColor: Colors.grey,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
autofocus: false,
maxLength: widget.answerLength + 5,
onChanged: (enterWord) {
widget.value = enterWord;
if (enterWord == widget.answer) {
if (widget.answerBool == false) {
widget.answerBool = true;
widget.correctList.add(widget.answer);
}
} else {
if (widget.answerBool == true) {
widget.answerBool = false;
widget.correctList.remove(widget.answer);
}
}
},
decoration: InputDecoration(
counterText: "",
hintText: widget.answerHint,
hintStyle: const TextStyle(color: Colors.grey, fontSize: 12),
),
),
);
}
}
If I enter text in one TextWithBlank BlankWord and then enter text in another TextWithBlank BlankWord, what I entered before disappears.
I want to keep the value in the BlankWord (TextField) of each TextWithBlank. What is the best way to do this?
Thank you.
TextWithBlank is included in the QuizText class.
class PlayGame extends StatefulWidget {
final List document;
List correctList = [];
PlayGame({Key? key, required this.document}) : super(key: key);
#override
State<PlayGame> createState() => _PlayGameState();
}
class _PlayGameState extends State<PlayGame> {
int quizNum = 0;
int quizCount = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Center(
child: Text(
"$quizCount/5",
style: const TextStyle(
fontSize: 25,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold),
),
),
actions: [
if (quizCount == 5)
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Result(),
),
);
print(widget.correctList.length);
},
child: Row(
children: const [
Text(
"採点する",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Icon(
Icons.arrow_forward,
size: 20,
),
SizedBox(
width: 10,
)
],
),
)
else
const SizedBox.shrink()
],
automaticallyImplyLeading: false,
),
body: PageView(
onPageChanged: (counter) {
setState(
() {
quizCount = counter + 1;
},
);
},
children: [
QuizText(widget: widget, quizNum: 0),
QuizText(widget: widget, quizNum: 1),
QuizText(widget: widget, quizNum: 2),
QuizText(widget: widget, quizNum: 3),
QuizText(widget: widget, quizNum: 4)
],
),
);
}
}
class QuizText extends StatelessWidget {
const QuizText({
Key? key,
required this.widget,
required this.quizNum,
}) : super(key: key);
final PlayGame widget;
final int quizNum;
#override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints.expand(),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Card(
child: SizedBox(
height: double.infinity,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: TextWithBlanks(
text: widget.document[quizNum],
correctList: widget.correctList),
),
),
),
),
),
);
}
}

Once the widget is not used, it disposes from widget tree and value lost. You can use state-management property. For now I am using AutomaticKeepAliveClientMixin to preserve the widget .
Changes will be here
class _TextWithBlanksState extends State<TextWithBlanks>
with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
final split = widget.text.split(TextWithBlanks.regex);
return Padding(
padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
child: Text.rich(
TextSpan(
style: const TextStyle(fontSize: 15, height: 3.0),
children: <InlineSpan>[
for (String text in split)
WidgetSpan(
child: blankWord(
text.substring(1, text.length - 1),
widget.correctList,
),
)
],
),
),
);
}
#override
bool get wantKeepAlive => true;
}

Related

How to call setsate function from a different widget?

Well, I am coding a chatbot-like page in my app. But, I am stuck at calling setState function for page inside of chatBubble widget. Here is my page as MedicBot and chat question code as FirstQuestion. What I do want to do that whenever, user triggers radio tile's on tap condition. It should be trigger setState function in MedicBot, any suggestions?
import 'package:medicte/assets/back_button.dart';
import 'package:medicte/assets/first_question.dart';
class MedicBot extends StatefulWidget {
const MedicBot({Key? key}) : super(key: key);
#override
State<MedicBot> createState() => _MedicBotState();
}
class _MedicBotState extends State<MedicBot> {
late final List<Widget> _messages;
late final List<dynamic> botMessages;
FocusNode _focusNode = FocusNode();
setMainState() {
print('bum');
this.setState(() {});
}
#override
void initState() {
print('bumbeyarag');
botMessages = [
_buildChatBubbles(
widget: SizedBox.shrink(),
text:
'Do you have further medical information you can share? (e.g. lab results)',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: ['1-2 weeks', 'A Month', '1-3 Months', 'Other'],
setMainState: setMainState,
),
text: 'Where do you currently live?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: [
'Online Consultation',
'Second Opinion',
'A treatment cost',
'Other'
],
setMainState: setMainState,
),
text: 'How soon do you want to get the treatment done?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: ['Yes', 'No'],
setMainState: () {
setState(() {});
},
),
text: 'What service are you looking for?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: [],
setMainState: () {
setState(() {});
},
),
text: 'Have you already spoken a doctor?',
userControl: false),
_buildChatBubbles(
text: 'Which treatment are you interested in?',
userControl: false,
widget:
const Text('Enter a treatment name (e.g Hair Transplant, IVF)')),
_buildChatBubbles(
text: 'You are inquiring for',
userControl: false,
widget: FirstQuestion(
radioButtons: const ['Myself', 'For someone else'],
focus: _focusNode,
setMainState: () {
setState(() {});
},
)),
];
_messages = [
const SizedBox(
height: 1,
),
const SizedBox(
height: 10,
)
];
super.initState();
}
final TextEditingController _controller = TextEditingController();
bool value = false;
#override
Widget build(BuildContext context) {
if (botMessages.isNotEmpty) {
_messages.insert(1, botMessages.removeLast());
}
return Scaffold(
bottomSheet: Container(
color: Colors.white30,
child: Padding(
padding: const EdgeInsets.only(bottom: 30, right: 15, left: 15),
child: TextFormField(
focusNode: _focusNode,
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
),
hintText: 'Type your message',
suffixIcon: IconButton(
onPressed: () {
print(_controller.text);
print(_controller.value);
setState(() {
_messages.insert(
1,
_buildChatBubbles(
text: _controller.text,
userControl: true,
widget: const SizedBox.shrink()));
_controller.clear();
});
},
icon: const Icon(Icons.send),
),
),
),
),
),
appBar: AppBar(
leadingWidth: 101,
backgroundColor: Colors.blue.shade300,
leading: Row(
children: [
const BackWardButton(),
ClipRRect(
borderRadius: BorderRadius.circular(1000),
child: Container(
color: Colors.white,
child: Image.asset(
'lib/images/Lovepik_com-401792159-medical-robot.png',
height: 53,
width: 53),
),
),
],
),
title: const Text(
"MedicBot",
style: TextStyle(color: Colors.black54),
),
),
body: SafeArea(
minimum:
const EdgeInsets.only(top: 2, left: 10, right: 10, bottom: 90),
child: ListView.builder(
itemCount: _messages.length,
reverse: true,
itemBuilder: ((context, index) {
return _messages[index];
}),
)));
}
}
class _buildChatBubbles extends StatelessWidget {
bool userControl;
String text;
Widget widget;
_buildChatBubbles(
{required this.widget,
required this.text,
required this.userControl,
super.key});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment:
userControl ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
userControl
? const SizedBox.shrink()
: Container(
margin: const EdgeInsets.only(right: 10),
child: const CircleAvatar(
radius: 20,
backgroundImage: AssetImage(
'lib/images/Lovepik_com-401792159-medical-robot.png'),
),
),
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4,
maxWidth: MediaQuery.of(context).size.width * 0.6),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: userControl ? Colors.green.shade300 : Colors.blue.shade300,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userControl ? 'You' : 'Medicte Bot',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 5),
Flexible(
child: Text(
text,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
widget
],
),
),
],
),
);
;
}
}
import 'package:flutter/material.dart';
import 'package:group_button/group_button.dart';
import 'package:medicte/pages/chat_ui.dart';
// ignore: must_be_immutable
class FirstQuestion extends StatefulWidget {
List<String> radioButtons;
FocusNode focus;
void Function() setMainState;
FirstQuestion(
{required this.setMainState,
required this.focus,
required this.radioButtons,
Key? key})
: super(key: key);
#override
State<FirstQuestion> createState() => _FirstQuestionState();
}
class _FirstQuestionState extends State<FirstQuestion> {
late GroupButtonController _radioController;
// ignore: prefer_typing_uninitialized_variables
late final _radioButtons;
#override
void initState() {
_radioButtons = widget.radioButtons;
_radioController = GroupButtonController(
selectedIndexes: [0, 1, 2, 3],
);
super.initState();
}
#override
Widget build(BuildContext context) {
return GroupButton(
controller: _radioController,
isRadio: true,
options: const GroupButtonOptions(groupingType: GroupingType.column),
buttons: _radioButtons,
buttonIndexedBuilder: (selected, index, context) {
return RadioTile(
title: _radioButtons[index],
selected: _radioController.selectedIndex,
index: index,
onTap: () {
print(_radioButtons[index].toString());
widget.setMainState();
_radioController.selectIndex(index);
/* Future.delayed(Duration(seconds: 1), () {
widget.setMainState();
}); */
},
);
},
onSelected: (val, i, selected) {
print('object');
});
}
}
class RadioTile extends StatelessWidget {
const RadioTile({
Key? key,
required this.selected,
required this.onTap,
required this.index,
required this.title,
}) : super(key: key);
final String title;
final int index;
final int? selected;
final VoidCallback onTap;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
onTap: onTap,
leading: Radio<int>(
groupValue: selected,
value: index,
onChanged: (val) {
print(val);
onTap();
},
),
);
}
}
Try something like this. This is the code snippet of an application of mine. I used StatefulBuilder as the parent of the widgets I want to update and I sent the setState parameter to the widget where I trigger.
import 'package:flutter/material.dart';
class CryptoassetsPage extends StatefulWidget {
const CryptoassetsPage({Key? key}) : super(key: key);
#override
_CryptoassetsPageState createState() => _CryptoassetsPageState();
}
class _CryptoassetsPageState extends State<CryptoassetsPage> {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
child: SingleChildScrollView(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
//My other class/widget
return OrderOptions(setState);
}),
),
);
}
}
class OrderOptions extends StatefulWidget {
const OrderOptions(this.setState, {Key? key}) : super(key: key);
final StateSetter setState;
#override
_OrderOptionsState createState() => _OrderOptionsState();
}
class _OrderOptionsState extends State<OrderOptions> {
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
StateSetter setState = widget.setState;
setState(() {});
},
);
}
}

The value entered in TextField is inherited

I'm developing a fill-in-the-blanks quiz app.
There are 5 question statements in one quiz, but when I move on to the next question statement, the value entered in the text field remains. Could you please tell me what are the possible causes?
class PlayGame extends StatefulWidget {
final List document;
List correctList = [];
PlayGame({Key? key, required this.document}) : super(key: key);
#override
State<PlayGame> createState() => _PlayGameState();
}
class _PlayGameState extends State<PlayGame> {
int quizNum = 0;
int quizCount = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Center(
child: Text(
"$quizCount/5",
style: const TextStyle(
fontSize: 25,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold),
),
),
actions: [
Row(
children: [
IconButton(
onPressed: () {
setState(
() {
if (quizNum < 4) {
quizNum += 1;
quizCount += 1;
} else if (quizNum == 4) {
print(widget.correctList.length);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Result()),
);
}
},
);
},
icon: const Icon(
Icons.arrow_circle_right_outlined,
size: 40,
),
),
const SizedBox(
width: 10,
)
],
)
],
automaticallyImplyLeading: false,
),
body: SizedBox(
height: double.infinity,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: TextWithBlanks(
text: widget.document[quizNum],
correctList: widget.correctList),
),
),
),
);
}
}
This is the code I was taught here. Words surrounded by "{}" are BlankWord.
class TextWithBlanks extends StatefulWidget {
final String text;
static final regex = RegExp("(?={)|(?<=})");
List correctList = [];
TextWithBlanks({Key? key, required this.text, required this.correctList})
: super(key: key);
#override
State<TextWithBlanks> createState() => _TextWithBlanksState();
}
class _TextWithBlanksState extends State<TextWithBlanks> {
#override
Widget build(BuildContext context) {
final split = widget.text.split(TextWithBlanks.regex);
return Padding(
padding: const EdgeInsets.only(top: 30.0, right: 30.0, left: 30.0),
child: Text.rich(
TextSpan(
style: const TextStyle(fontSize: 15, height: 3.0),
children: <InlineSpan>[
for (String text in split)
text.startsWith('{')
? WidgetSpan(
child: blankWord(text.substring(1, text.length - 1),
widget.correctList),
)
: TextSpan(text: text),
],
),
),
);
}
}
This is the BlankWord.
class _blankWordState extends State<blankWord> {
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.answerWidth,
child: TextField(
maxLines: null,
cursorColor: Colors.grey,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
autofocus: false,
maxLength: widget.answerLength + 5,
onChanged: (enterWord) {
widget.value = enterWord;
if (enterWord == widget.answer) {
if (widget.answerBool == false) {
widget.answerBool = true;
widget.correctList.add(widget.answer);
}
} else {
if (widget.answerBool == true) {
widget.answerBool = false;
widget.correctList.remove(widget.answer);
}
}
},
decoration: InputDecoration(
counterText: "",
hintText: widget.answerHint,
hintStyle: const TextStyle(color: Colors.grey, fontSize: 12),
),
),
);
}
}
When you update the page by changing the quiz number also reset the value that you are sending to the blank widget. When the blank widget is updated the widget.value is being updated, and that value stays in the class and when a new blank widget is added the value is being sent to the blank widget again I think
widget.value = enterWord;

Function callback to change text color (just for one) | Flutter

I'm trying to create a SideMenu with different SideMenuItems.
For that I created a new class and want to update the color of Text when the SideMenuItem is clicked. For that I want to transfer the activeState and all that stuff you see in the code below:
The use of my class in the Widget:
bool isActive = false;
...
SideMenuItem(
icon: Icon(
Icons.inbox,
size: 20,
color: isActive ? kPrimaryColor : kGrayColor,
),
activeState: isActive,
title: "Archiv",
toggleActiveState: (activeState) {
setState(() {
isActive = !activeState;
});
},
),
And here is my class:
import 'package:flutter/material.dart';
import 'package:gastronomy/constants.dart';
class SideMenuItem extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SideMenuItem({
Key? key,
required this.activeState,
this.itemCount = 0,
this.showBorder = true,
#required this.icon,
#required this.title,
required this.toggleActiveState,
}) : super(key: key);
final bool activeState;
final bool showBorder;
final int itemCount;
final Icon? icon;
final String? title;
final Function(bool) toggleActiveState;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: kDefaultPadding),
child: InkWell(
onTap: () {
toggleActiveState(activeState);
},
child: Row(
children: [
const SizedBox(width: 15),
const SizedBox(width: kDefaultPadding / 4),
Expanded(
child: Container(
padding: const EdgeInsets.only(bottom: 15, right: 5),
decoration: showBorder
? const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFDFE2EF)),
),
)
: null,
child: Row(
children: [
icon!,
const SizedBox(width: kDefaultPadding * 0.75),
Text(
title!,
style: Theme.of(context).textTheme.button?.copyWith(
color: activeState ? kTextColor : kGrayColor,
),
),
const Spacer(),
// if (itemCount != null) CounterBadge(count: itemCount)
],
),
),
),
],
),
),
);
}
}
I ended up with that pieces of code but well, how you might know, all SideMenuItems change there color when I click one.
I'm pretty new at using this way of code so I would be thankful to all informations you can include into your answer.
One option is to render all the menu items through a map function and compare each item with the selected option, like in the example below:
import 'package:flutter/material.dart';
class MenuExample extends StatefulWidget {
const MenuExample({Key? key}) : super(key: key);
#override
_MenuExampleState createState() => _MenuExampleState();
}
class _MenuExampleState extends State<MenuExample> {
List<String> menuOptions = const ['Item 1', 'Item 2', 'Item 3'];
String selectedOption = '';
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
backgroundColor: Colors.amber,
child: ListView(
children: menuOptions.map((menuOption) {
return InkWell(
onTap: () => setState(() {
selectedOption = menuOption;
}),
child: MenuItem(
name: menuOption,
isSelected: menuOption == selectedOption,
),
);
}).toList()),
),
);
}
}
class MenuItem extends StatelessWidget {
const MenuItem({Key? key, this.isSelected = false, required this.name})
: super(key: key);
final bool isSelected;
final String name;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
name,
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal),
),
);
}
}

How to call a function from stateless Widget that points to state class function?

I am trying to create a responsive chatbot with quick replies. I want to make a button on pressed function call to another class's function. I tried using the callback. But i think i am doing something wrong. Kindly help me.
typedef void mycallback(String label);
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
late DialogFlowtter dialogFlowtter;
final TextEditingController messageController = TextEditingController();
#override
void initState() {
super.initState();
DialogFlowtter.fromFile().then((instance) => dialogFlowtter = instance);
}
#override
Widget build(BuildContext context) {
var themeValue = MediaQuery.of(context).platformBrightness;
Body(
hi: sendMessage,
);
return Scaffold(
backgroundColor: themeValue == Brightness.dark
? HexColor('#262626')
: HexColor('#FFFFFF'),
appBar: AppBar(
//app bar ui
),
actions: [
//list if widget in appbar actions
PopupMenuButton(
icon: Icon(Icons.menu),
color: Colors.blue,
itemBuilder: (context) => [
PopupMenuItem<int>(
value: 0,
child: Text(
"Log out",
style: TextStyle(color: Colors.white),
),
),
],
onSelected: (item) => {logout(context)},
),
],
),
body: SafeArea(
child: Column(
children: [
Expanded(child: Body(messages: messages)),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: messageController,
style: TextStyle(
color: themeValue == Brightness.dark
? Colors.white
: Colors.black,
fontFamily: 'Poppins'),
decoration: new InputDecoration(
enabledBorder: new OutlineInputBorder(
borderSide: new BorderSide(
color: themeValue == Brightness.dark
? Colors.white
: Colors.black),
borderRadius: BorderRadius.circular(15)),
hintStyle: TextStyle(
color: themeValue == Brightness.dark
? Colors.white54
: Colors.black54,
fontSize: 15,
fontStyle: FontStyle.italic,
),
labelStyle: TextStyle(
color: themeValue == Brightness.dark
? Colors.white
: Colors.black),
hintText: "Type here...",
),
),
),
IconButton(
color: themeValue == Brightness.dark
? Colors.white
: Colors.black,
icon: Icon(Icons.send),
onPressed: () {
sendMessage(messageController.text);
messageController.clear();
},
),
],
),
),
],
),
),
);
}
void sendMessage(String text) async {
if (text.isEmpty) return;
setState(() {
//do main function
});
}
}
The class from where i want to call the function
class Body extends StatelessWidget {
final List<Map<String, dynamic>> messages;
final mycallback? hi;
const Body({
Key? key,
this.messages = const [],
this.buttons = const [],
this.hi,
this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (context, i) {
var obj = messages[messages.length - 1 - i];
Message message = obj['message'];
bool isUserMessage = obj['isUserMessage'] ?? false;
String label = obj['label'];
return Row(
mainAxisAlignment:
isUserMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_MessageContainer(
message: message,
isUserMessage: isUserMessage,
),
ElevatedButton(
child: Text(label),
onPressed: () => {hi ?? (label)},//This is where i want to call
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
textStyle: TextStyle(fontWeight: FontWeight.bold)),
),
],
);
},
separatorBuilder: (_, i) => Container(height: 10),
itemCount: messages.length,
reverse: true,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 20,
),
);
}
}
The code runs without errors but nothing happens when i press the buttons.
This is how I'd implement something like that. You're basically asking for a void as parameter inside your widget. Almost like a TextButton or another widget like that.
You can use this with two stateful widets as well, since you're borrowing the function from one to another.
Also I think this would be done better with provider so I suggest you look into it. (I don't have enough experience with it)
https://pub.dev/packages/provider
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int x = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('An app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$x'),
TestWidget(onTap: () {
setState(() {
x++;
});
})
],
),
),
);
}
}
class TestWidget extends StatelessWidget {
final VoidCallback onTap;
const TestWidget({Key? key, required this.onTap}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
color: Colors.blue,
child: Text('test')),
);
}
}
I found the error.
In the class HomeScreen, I missed this line.
child: Body(
messages: messages,
hi: (text) => {sendMessage(text)}, //this line
)
After adding this line, the callback worked fine!

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