flutter want call parent view upon its child to refresh main view - flutter

I have a Stateful widget that has a child buttons and set of stateful containers (visible and invisible).
I want to try here is when i call the specific button, the specific button will refresh all layout and change it on its specifict view by setting visible while the others are not visible.
like this:
button1 = view1;
button2 = view2;
button3 = view3;
if (button1 is pressed){
view1 is visible}
else{
not visible}
upon my code, upon my first view(login button),I've set to go with my main_page like this:
child: MaterialButton(
minWidth: MediaQuery.of(context).size.width,
padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainPage(change1: true,change2: false,change3: false,)),
);
},
now i was display my main_page view (with a child view that has visibility property).
this is my code on main_page:
class MainPage extends StatefulWidget {
final bool change1 ;
final bool change2;
final bool change3 ;
const MainPage({Key key, this.change1,this.change2,this.change3}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Container(
constraints: BoxConstraints.expand(
height: 280.0,
),
decoration: BoxDecoration(
image: new DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.blueAccent, BlendMode.colorBurn),
image: new ExactAssetImage("images/vector.jpg"),
),
),
child: Stack(
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
child: Row(
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
print("i pressed Official Business");
MainPage(change1: true,change2: false,change3: false);
//TODO: here is my problem, when i call the main_page on its page,
// the value of change1, change2, and chaange3 is not updating
// so that icanot update my view .
});
},
child: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
"Official Business",
style: TextStyle(
fontSize: 20.0, color: Colors.white),
),
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
print("i pressed file an OB");
MainPage(change1: false,change2: true,change3: false);
//TODO: here is my problem, when i call the main_page on its page,
// the value of change1, change2, and chaange3 is not updating
// so that icanot update my view .
});
},
child: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
"File an OB",
style: TextStyle(
fontSize: 20.0, color: Colors.white),
),
),
),
),
),
),
],
),
),
],
),
),
//TODO:
new Visibility(
//Called changed and viewOne
visible: widget.change1,
child: OfficialBusiness(),
),
new Visibility(
//Called not changed and viewTwo
visible: widget.change2,
child: FilingOb(),
),
new Visibility(
//Called not changed and viewTwo
visible: widget.change3,
child: ViewOfficialBusiness(),
)
],
),
),
);
}
}
the fillingob/officialbusiness/ViewOfficialBusiness includded was set of stateful layout, i didn't add the code to prevent over views.
sorry, in new on this programming language and i've like to enlighten with these problems i've encountered if my code is possible or not.Also if you need more reference just ping on comment so that i can provide my other codes

bool change1;
bool change2;
bool change3;
#override
initState() {
super.initState();
change1 = widget.change1;
change2 = widget.change2;
change3 = widget.change3;
}
void setChange1() {
setState(() {
change1 = true;
change2 = change3 = false;
});
}
// GestureDetector(onTap: setChange1)
// Visibility(
// visible: change1,
// child: OfficialBusiness(),
// )
Or use enum :
enum MyView {
officialBusiness,
filingOb,
viewOfficialBusiness,
}
MyView current;
// GestureDetector(onTap: () => setState(() { current = MyView.officialBusiness; }))
// Visibility(
// visible: current == MyView.officialBusiness,
// child: OfficialBusiness(),
// )

In Flutter, the standard way to call functions on a parent widget when something occurs in one of its children would be to pass a function from the parent widget to the child, so that the function gets triggered from the child, with data that only the parent widget knows.
In your case, in your first view, you could define a new method like this:
void onChildPressed() {
setState(() {
// Here you change the boolean change1, change2, whatever you want
});
}
And then, in the child view, you have to define a function parameter, such that your child can receive the function as a parameter, and trigger it from wherever you want in your child widget.
class MainPage extends StatefulWidget {
final bool change1 ;
final bool change2;
final bool change3 ;
final void Function() onPressed;
const MainPage({Key key, this.change1,this.change2,this.change3, this.onPressed}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
This way, you can instantiate the MainPage from your first view like this:
MainPage(change1: false,change2: true,change3: false, onPressed: onChildPressed);
And, finally, you can call the onPressed function in your MainPage, so that your child view updates the parent view the way you want.

Related

Flutter onPressed trigger State of another widget

I need help with flutter build management. My Goal is to rebuild my side navigation bar with the SmallSideMenu() or the opposite SideMenu().
Do you have an idea how I can trigger the Build process of my LargeView widget, so that it's rebuilt with the correspondent SideMenu?
The button is defined like this:
IconButton(
onPressed: () {
checkState();
},
icon: HeroIcon(
HeroIcons.arrowNarrowRight,
size: 16.0,
),
),
The value of the sideMenuOpen variable and function is set globally;
checkState() {
if (sideMenuOpen == true) {
sideMenuOpen = false;
} else {
sideMenuOpen = true;
}
}
the SideMenu is defined here.
class LargeView extends StatefulWidget {
const LargeView({
Key? key,
}) : super(key: key);
#override
State<LargeView> createState() => _LargeViewState();
}
class _LargeViewState extends State<LargeView> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: SizedBox(
width: 100,
child: sideMenuOpen ? SideMenu() : SmallSideMenu(),
),
),
Expanded(
flex: 10,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(20.0),
color: greyColor,
child: Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0)),
child: localNavigator())),
),
)
],
);
}
}
make sideMenuOpen a state in stateful widget and use setState as
checkState() {
if (sideMenuOpen == true) {
setState((){
sideMenuOpen = false;
});
} else {
setState((){
sideMenuOpen = true;
});
}
}
if you want to keep state (open/close) of navigation bar globally for that use some state management like provider instead of using global function.
You can use ValueNotifier instead of single bool. And to update UI it can be used on ValueListenableBuilder.
final ValueNotifier<bool> sideMenuOpen = ValueNotifier(false);
And
child: SizedBox(
width: 100,
child: ValueListenableBuilder<bool>(
valueListenable: sideMenuOpen,
builder: (context, value, child) => value
? SideMenu(),
: SmallSideMenu(),
),
),
And change value like
sideMenuOpen.value = true;

How do I keep a instantiated variable unchanged after using Provider.of

I have a page where I can choose a user theme color that is used as colors in my app. I have it set up so a modal bottom sheet pops up with the color options and then sets it using Provider so when the navigation is popped the new color can be seen on MyView.
Problem
When the user makes a change BUT hits the close button I essentially want to revert all changes made, so to try and tackle this I have a variable called loggedInUser which I initialise in my init State function and I keep out of the build method. So its set once and that's it. The plan is that if the user hits the close button I use Provider to set the details back to the data in loggedInUser (which shoulldn't have the updated color choices).
This does not happen and loggedInUser though not reinitialised has the new colors I chose.
Code
class MyView extends StatefulWidget {
static const String id = "my_view";
#override
State<MyView> createState() => _MyViewState();
}
class _MyViewState extends State<MyView> {
UserDto loggedInUser;
#override
void initState() {
super.initState();
loggedInUser = Provider.of<UserData>(context, listen: false).user;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kThemeColor,
body: Column(
children: [
SafeArea(
child: CloseButton(
onPressed: () {
var test = loggedInUser;
//when debugged, test has the new color, not the old one it was initialised to back in initState();
//i want the old values to persist
Navigator.pop(context);
},
color: Colors.white,
),
),
Expanded(
child: Container(
height: double.infinity,
width: double.infinity,
decoration: kCurvedContainerBoxDecoration,
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context).viewInsets.bottom),
child: AccountThemePickerView(),
),
),
);
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
UserHelper.getColorFromString(
Provider.of<UserData>(context).user.themeColor),
),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
)
],
),
),
),
)
],
),
);
}
}
class AccountThemePickerView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Color(0xff757575),
child: Container(
decoration: kModalBottomSheetBoxDecoration,
padding: EdgeInsets.only(left: 15, bottom: 30, right: 15, top: 15),
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
crossAxisSpacing: 30,
mainAxisSpacing: 30,
children: [
AccountThemePickerColor(
colorName: "Coral Red", color: Color(0xffff6961)),
AccountThemePickerColor(
colorName: "Forest Green", color: Color(0xff129a7d)),
],
),
),
);
}
}
class AccountThemePickerColor extends StatelessWidget {
final Color color;
final String colorName;
AccountThemePickerColor({this.colorName, this.color});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
Provider.of<UserData>(context, listen: false)
.updateUserThemeColor(colorName, color.toString());
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
backgroundColor: MaterialStateProperty.all<Color>(color),
),
);
}
}
UserData class
class UserData extends ChangeNotifier{
UserDto user;
void setUser(UserDto userDto){
user = userDto;
notifyListeners();
}
void updateUserThemeColor(String themeColorName, String themeColor){
//note I have a helper method which simply converts string to color, for your debug purposes you can just use an actual Color value
user.themeColor = themeColor;
user.themeColorName = themeColorName;
notifyListeners();
}
}
I believe it has something to do with copy constructors.
For example, this code:
class X{
int y;
int z;
X(this.y, this.z);
}
void main() {
X obj1 = X(2,3);
X obj2 = obj1;
X obj3 = obj2;
obj1.y = 10;
print(obj2.y);
print(obj3.y);
}
outputs
10
10
because variables are references to objects. And when you assign an object to another object, it points to the same location in memory instead of copying its elements.
Provider.of<UserData>(context, listen: false).user; would return the same object each time it is called. So, you change its value. And hence, the loggedInUser also changes.
Try to create a new object and store data in it.

Flutter remove OverlayEntry if touch outside

I have a CustomDropDown, done with a OverlayEntry. The problem is that I have a StatefulWidget for that, which I place in my Screen simply like that:
CustomDropDownButton(
buttonLabel: 'Aus Vorauswahl wählen',
options: [
'1',
'2',
'3',
'4',
],
),
Now inside that CustomDropDownButton I can simply call floatingDropdown.remove(); where ever I want but how can I call that from a Parent-Widget?? I hope you understand my problem. Right now the only way to remove the overlay is by pressing the DropDownButton again, but it should be removed everytime the user taps outside the actual overlay.
I am quite lost here so happy for every help! Let me know if you need any more details!
This is the code for my CustomDropDownButton if that helps:
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../constants/styles/colors.dart';
import '../../../constants/styles/text_styles.dart';
import '../../../services/size_service.dart';
import 'drop_down.dart';
class CustomDropDownButton extends StatefulWidget {
String buttonLabel;
final List<String> options;
CustomDropDownButton({
required this.buttonLabel,
required this.options,
});
#override
_CustomDropdownState createState() => _CustomDropdownState();
}
class _CustomDropdownState extends State<CustomDropDownButton> {
late GlobalKey actionKey;
late double height, width, xPosition, yPosition;
bool _isDropdownOpened = false;
int _selectedIndex = -1;
late OverlayEntry floatingDropdown;
#override
void initState() {
actionKey = LabeledGlobalKey(widget.buttonLabel);
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
key: actionKey,
onTap: () {
setState(() {
if (_isDropdownOpened) {
floatingDropdown.remove();
} else {
findDropdownData();
floatingDropdown = _createFloatingDropdown();
Overlay.of(context)!.insert(floatingDropdown);
}
_isDropdownOpened = !_isDropdownOpened;
});
},
child: Container(
height: scaleWidth(50),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.0, color: AppColors.black),
),
color: AppColors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: scaleWidth(10),
),
Text(
widget.buttonLabel,
style: AppTextStyles.h5Light,
),
Spacer(),
_isDropdownOpened
? SvgPicture.asset(
'images/icons/arrow_down_primary.svg',
width: scaleWidth(21),
)
: SvgPicture.asset(
'images/icons/arrow_up_primary.svg',
width: scaleWidth(21),
),
SizedBox(
width: scaleWidth(10),
),
],
),
),
);
}
void findDropdownData() {
RenderBox renderBox =
actionKey.currentContext!.findRenderObject()! as RenderBox;
height = renderBox.size.height;
width = renderBox.size.width;
Offset? offset = renderBox.localToGlobal(Offset.zero);
xPosition = offset.dx;
yPosition = offset.dy;
}
OverlayEntry _createFloatingDropdown() {
return OverlayEntry(builder: (context) {
return Positioned(
left: xPosition,
width: width,
top: yPosition + height,
height: widget.options.length * height + scaleWidth(5),
child: DropDown(
itemHeight: height,
options: widget.options,
onOptionTap: (selectedIndex) {
setState(() {
widget.buttonLabel = widget.options[selectedIndex];
_selectedIndex = selectedIndex;
floatingDropdown.remove();
_isDropdownOpened = !_isDropdownOpened;
});
},
selectedIndex: _selectedIndex,
),
);
});
}
}
1. Return a ListView instead GestureDetector
2. Under Listview use that GestureDetector containing DropDown as one of the children.
3. Add another children(widgets) as GestureDetector and set onTap of each one as:
GestureDetector(
onTap: () {
if(isDropdownOpened){
floatingDropDown!.remove();
isDropdownOpened = false;
}
},
child: Container(
height: 200,
color: Colors.black,
),
)
In short you have to add GestureDetector to the part wherever you want the tapping should close overlay entry
** Full Code **
//This is to close overlay when you navigate to another screen
#override
void dispose() {
// TODO: implement dispose
floatingDropDown!.remove();
super.dispose();
}
Widget build(BuildContext context) {
return ListView(
children: [
Padding(
padding: EdgeInsets.all(20),
child: GestureDetector(
key: _actionKey,
onTap: () {
setState(() {
if (isDropdownOpened) {
floatingDropDown!.remove();
} else {
findDropDownData();
floatingDropDown = _createFloatingDropDown();
Overlay.of(context)!.insert(floatingDropDown!);
}
isDropdownOpened = !isDropdownOpened;
});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.orangeAccent),
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: <Widget>[
Text(
widget.text,
style: TextStyle(color: Colors.white, fontSize: 20),
),
Spacer(),
Icon(
Icons.arrow_drop_down,
color: Colors.white,
),
],
),
),
),
),
GestureDetector(
onTap: () {
if(isDropdownOpened){
floatingDropDown!.remove();
isDropdownOpened = false;
}
},
child: Container(
height: 200,
color: Colors.black,
),
)
],
);
}
Let me know whether it helped or not
Listen to full screen onTapDown gesture and navigation event.
The screen' s gesture event:
#override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
PenetrableTapRecognizer: GestureRecognizerFactoryWithHandlers<PenetrableTapRecognizer>(
() => PenetrableTapRecognizer(),
(instance) {
instance.onTapDown = (_) => _handleGlobalGesture();
},
),
},
behavior: HitTestBehavior.opaque,
child: Scaffold(
),
);
}
void _handleGlobalGesture {
// insert or remove the popup menu
// a bool flag maybe helpful
}
class PenetrableTapRecognizer extends TapGestureRecognizer {
#override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}

How to disable the tooltip dynamically in flutter? [duplicate]

This question already has answers here:
how to disable tooltip dynamcically in flutter?
(4 answers)
Closed 2 years ago.
I can disable the tooltip statically.
But I want to disable tooltip dynamically when i click flatbutton.But Couldnt disable dynamically and i have no idea to do that.
If I give statically false. it works fine.
For example : If add child like TopToolbar(showTooltip : false),it works fine,
But If i give toolbar.showTooltip = false in Flatbutton onPressed method,it doesnt work.
I want to disble it in dynamically. please help me to do that.
This is my code:
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(home: HelloWorld(),debugShowCheckedModeBanner: false,));
}
class HelloWorld extends StatefulWidget {
#override
_HelloWorldState createState() => _HelloWorldState();
}
class _HelloWorldState extends State<HelloWorld> {
bool check = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: <Widget>[
TopToolbar(),
FlatButton(
child: Text("Disable Tooltip"),
onPressed: () {
setState(() {
TopToolbar toolbar = new TopToolbar();
toolbar.showTooltip = false;
});
},
),
]),
),
));
}
}
class TopToolbar extends StatefulWidget {
bool showTooltip;
final Color backgroundColor;
final double height;
bool isVisible;
TopToolbar({
this.height = 55,
this.isVisible = true,
this.backgroundColor = const Color(0xFFEEEEEE),
Key key,this.showTooltip=true,
}) : super(key: key);
#override
_TopToolbarState createState() => _TopToolbarState();
}
class _TopToolbarState extends State<TopToolbar> {
#override
Widget build(BuildContext context) {
if (widget.isVisible) {
return Container(
foregroundDecoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey,
),
),
),
margin: EdgeInsets.only(bottom: 1),
color: widget.backgroundColor,
height: widget.height,
child: Stack(
children: <Widget>[
Positioned(
top: 7,
right: 60,
height: 40,
width: 40,
child: RawMaterialButton(
elevation: 0.0,
fillColor: widget.backgroundColor,
splashColor: Colors.grey[300],
child: IconButton(
icon: Icon(
Icons.bookmark,
color: Colors.grey[500],
size: 25,
),
onPressed: (){},
tooltip: widget.showTooltip ? "Bookmark" : null,
),
onPressed: (){},
),
),
],
),
);
} else {
return Container();
}
}
}
You have to store whether to show the tooltip in _HelloWorldState, not in the TopToolbar.
This would lead to doing something like this in _HelloWorldState:
class _HelloWorldState extends State<HelloWorld> {
bool showTip = true;
bool check = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: <Widget>[
TopToolbar(showTip),
FlatButton(
child: Text("Disable Tooltip"),
onPressed: () {
setState(() {
showTip = false;
});
},
),
]),
),
));
}
}
showTooltip should also be marked as final in TopToolbar class.
Your current implementation creates a new TopToolbar widget, it doesn't modify the existing widget. TopToolbar toolbar = new TopToolbar(); creates a completely different widget, just one that isn't ever mounted and shown. Therefore, toolbar.showTooltip = false; has no visible effect.
Alternatively to what I have shown you can access of the State of the TopToolbar using a GlobalKey, but I wouldn't recommend this for a beginner, it isn't necessary for your implementation at the moment, and GlobalKeys are relatively expensive.
This is too simple buddy,
make 1 global variable below main method
bool isTooltipActive = true;
Now change onPressed method like this
FlatButton(
child: Text("Disable Tooltip"),
onPressed: () {
setState(() {
if(isToolTipAvtive == false){
isToolTipAvtive = true;
}else{
isToolTipAvtive = false;
}
});
},
),
And change bookmark tooltip line like this
tooltip: isToolTipAvtive ? "Bookmark" : null,

Flutter ListView widgets resets icon when scrolling items out of screen

I have a ListView with custom statefull widgets as items.
These items has an onTap event that switch the icon from check_box_outline_blank to check_box, and the other way around.
However when I select an item in the list and scroll down and back up again the item has reset the icon from check_box to check_box_outline_blank.
How can I prevent this from happening?
new Expanded(
child: ListView.builder(
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return listTile(_pveList[index], index)
},
)
)
//Adds extra space(height:100) after the last tile
StatefulWidget listTile(String text, int index){
return (index < _pveList.length - 1)?new SelectableListItem(text: text,
onTap: (){
_handleOnTap(text);
},
): new Column(
children: <Widget>[
new SelectableListItem(text: text,
onTap: (){
_handleOnTap(text);
},
),
SizedBox(
height: 100,
)
],
);
}
The tile widget(SelectableListItem):
import 'package:flutter/material.dart';
class SelectableListItem extends StatefulWidget {
SelectableListItem({Key key, this.text, this.onTap})
: super(key: key);
final String text;
final GestureTapCallback onTap;
#override
_SelectableListItemState createState() => _SelectableListItemState();
}
class _SelectableListItemState extends State<SelectableListItem> {
bool isSelected = false;
Icon _checked = new Icon(Icons.check_box_outline_blank);
handleOnTap() {
isSelected = !isSelected;
if (isSelected) {
setState(() {
_checked = new Icon(Icons.check_box);
});
} else {
setState(() {
_checked = new Icon(Icons.check_box_outline_blank);
});
}
return widget.onTap();
}
#override
Widget build(context) {
// Pass the text down to another widget
return new GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => handleOnTap(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Padding(
padding: EdgeInsets.all(22),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(widget.text, style: TextStyle(fontSize: 18)),
_checked
],
),
),
new Container(
width: double.infinity,
height: 1,
color: Color(0x1a222222),
)
],
),
);
}
}
Updated
I believe the OP wants to achieve a standard Checkbox with a label text aside.
CheckboxListTile widget provides such functionality.
Usage example:
bool selected = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
CheckboxListTile(
onChanged: (val) {
setState(() {
selected = !selected;
});
},
value: selected,
title: Text("Test"),
)
]),
);
}
If you want to have the checkbox located on the left side of the screen - set controlAffinity property accordingly:
CheckboxListTile(
// ...,
controlAffinity: ListTileControlAffinity.leading,
)
Original answer
#1
You should never use functions to build the widget tree.
See this answer for more details.
#2
I'd recommend to use ValueNotifier, declared in SelectableListItem class.
It allows you to change value of a variable in StatefulWidget without the usual warnings for non-final variables.
The value inside of ValueNotifier gets preserved too, just like any other variables that are declared in StatefulWidget class.
e.g.
class SelectableListItem extends StatefulWidget {
// ...
final ValueNotifier<bool> isSelected = ValueNotifier<bool>(false);
// ...
}
class _SelectableListItemState extends State<SelectableListItem> {
handleOnTap() {
setState(() {
widget.isSelected.value = !widget.isSelected.value;
});
return widget.onTap();
}
// ... build() { ...
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(widget.text, style: TextStyle(fontSize: 18)),
Icon(widget.isSelected.value ? Icons.check_box : Icons.check_box_outline_blank),
]
),
// ... } ...
}
ValueNotifier<bool> also eliminates the need to store the icon widget in a separate variable. Instead, you can use it in inline boolean condition, just like it's shown in the code.
PS: I did not test the code, however general instructions are valid. Let me know if this helped.