why setState does not change my variable Flutter? - flutter

I have a variable to which i assign a value inside gridView.Builder and there is a button, when clicked on which my variable should change, I use setState for this, but it does not change, what could be the reason?
class _CatalogItemsState extends State<CatalogItems> {
Set<int> _isFavLoading = {};
bool isFavorite = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(name!),
),
body: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Column(
children: [
Expanded(
child: FutureBuilder<List<Product>>(
future: productFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return buildGridShimmer();
} else if (snapshot.hasData) {
final catalog = snapshot.data;
if (catalog!.isEmpty) {
return const Center(
child: Text(
'Нет товаров',
style: TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
),
);
}
return buildCatalog(catalog);
} else {
print(snapshot.error);
return const Text("No widget to build");
}
}),
),
],
),
),
);
}
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(),
delegate: SliverChildBuilderDelegate(childCount: product.length,
(BuildContext context, int index) {
final media =
product[index].media?.map((e) => e.toJson()).toList();
final photo = media?[0]['links']['local']['thumbnails']['350'];
final productItem = product[index];
isFavorite = productItem.is_favorite; // this is the new value of the variable, work good
IconButton(
icon: Icon(_isFavLoading.contains(index) || isFavorite ? Icons.favorite : Icons.favorite_border, color: Colors.red,),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
print('t: ${isFavorite}'); // the value of the variable does not change
},
)

This is because you're not really updating your product object. You must change its value when icon is pressed
onPressed: () {
setState(() {
productItem.is_favorite = !isFavorite;
});
print('t: ${productItem.is_favorite}'); // the value of the variable does not change
}

Related

Flutter, how to return different widget based on future value?

I would like to base on a future bool value, to set different icons pass back to a data card inside a list, I tried .then or FutureBuilder, but still not successful.
Scaffold:
child: ListView.builder(
itemCount: fullList.length,
itemBuilder: (BuildContext context, int index) {
return dataCard(context, fullList, index);
}),
dataCard:
Row(
children: [
Expanded(
flex: 8,
child: Text(dl[i].Name,
style:
TextStyle(color: Colors.blue[400], fontSize: 16)),
),
Expanded(
flex: 1,
child: setFavouriteIcon(dl[i].ID),
),
],
),
setFavouriteIcon:
Widget setFavouriteIcon(_id) {
final marked = markedFavourites(_id).then((value) { //markedFavourites returns Future<bool>
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
});
return Text(''); //Without this line, Error: A non-null value must be returned
}}
You can include other state as well on FutureBuilder
Widget setFavouriteIcon(_id) {
return FutureBuilder(
future: markedFavourites(_id),// you shouldn't call method directly here on statefulWidget case
builder: (context, snapshot) {
final value = snapshot.hasData && (snapshot.data as bool? ?? false);
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
},
);
}
you should use FutureBuilder
class FavoriteWidget extends StatelessWidget {
const FavoriteWidget({super.key});
// some future value
Future<bool> markedFavorites() async {
//do smth
return true;
// or return false
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<bool>(
future: markedFavorites(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {
return const Icon(
Icons.favorite,
color: Colors.red,
);
}
return const Icon(Icons.favorite_border_outlined);
}
},
),
);
}
}

Flutter dropdownbutton changed value is not displaying

I'm new in flutter. I've created a simple project.
It is fetching documents of person collection from cloud firestore.
There is a modal screen to create new person document (it is opening When I touch the + button)
I have a problem In that modalBottomSheet
I can see the new value of department dropDownButton on the log screen but user interface are not changing.
I think it is related to 'context' but I couldn't solve the problem
Here is my code:
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Person extends StatefulWidget {
const Person({Key? key}) : super(key: key);
#override
_PersonState createState() => _PersonState();
}
class _PersonState extends State<Person> {
final TextEditingController _nameController = TextEditingController();
final CollectionReference _person = FirebaseFirestore.instance.collection('person');
final CollectionReference _department = FirebaseFirestore.instance.collection('department');
String? _usersDeptName;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: StreamBuilder(
stream: _person.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['personName']),
subtitle: Text(documentSnapshot['departmentName'] ?? '?'),
),
);
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => {_create()},
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat);
}
Future<void> _create() async {
_usersDeptName = null;
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return Padding(
padding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(controller: _nameController, decoration: InputDecoration(labelText: 'person_name'.tr())),
const SizedBox(height: 10),
StreamBuilder<QuerySnapshot>(
stream: _department.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("loading").tr();
} else {
List<DropdownMenuItem> departments = [];
int? howManyRecords = snapshot.data?.size;
for (int i = 0; i < howManyRecords!; i++) {
DocumentSnapshot snap = snapshot.data?.docs[i] as DocumentSnapshot<Object?>;
departments.add(DropdownMenuItem(child: Text(snap.get('departmentName')), value: snap.get('departmentName')));
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: DropdownButton(
value: _usersDeptName,
items: departments,
onChanged: (newValue) {
setState(() {
_usersDeptName = newValue.toString();
print('$_usersDeptName is selected');
});
},
isExpanded: true,
),
),
],
);
}
},
),
ElevatedButton(
child: const Text('save').tr(),
onPressed: () async {
final String name = _nameController.text;
if (name != null) {
await _person.add({"personName": name, 'departmentName': _usersDeptName});
_nameController.text = '';
Navigator.of(context).pop();
}
},
)
],
),
);
},
);
}
}
Try using StatefulBuilder to update the bottomSheet state.
Future<void> _create() async {
_usersDeptName = null;
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return StatefulBuilder(
builder: (context, setState) => Padding(
padding: EdgeInsets.only(
If you like to change the widget-state(main UI) at the same time, you can rename the StatefulBuilder's setState and call both on onChanged.
when setting up the value for the _usersDeptName, you are converting it to string, this is not right because now the items and the selected items is 2 different thing,
so if you want it to be the department name, then when setting up the value for _usersDeptName, make it bind to the department name:
example
setState(() {
_usersDeptName = newValue.departmentName;
print('$_usersDeptName is selected');
});

flutter: operator '[]' isn't defined for the type 'JsonCodec'

I working on flutter project . I trying to fetch data from server with pagination . I have problem here :
myList = List.generate(10, (index) => DataObd.fromJson(json[index]));
error : operator '[]' isn't defined for the type 'JsonCodec' .
My code :
class _StatusState extends State<Status> {
List<DataObd> myList;
ScrollController _scrollController = ScrollController();
int _currentMax = 10;
ObdApi obdApi = ObdApi();
#override
void initState() {
super.initState();
myList = List.generate(10, (index) => DataObd.fromJson(json[index]));
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
_getMoreData() {
print('hello');
/* for (int i = _currentMax; i < _currentMax + 10; i++) {
myList.add("Item : ${i + 1}");
}
_currentMax = _currentMax + 10;
setState(() {});*/
}
#override
Widget build(BuildContext context) {
return SafeArea(
minimum: const EdgeInsets.all(10.0),
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Color(0xFF010611),
iconTheme: IconThemeData(color: Colors.white),
automaticallyImplyLeading: true,
centerTitle: true,
title: Text(
'Status',
style: TextStyle(
color: Colors.white,
),
),
elevation: 0.0,
leading: Row(
children: [
IconButton(
icon: Icon(
CommunityMaterialIcons.arrow_left_circle_outline,
color: Colors.yellow[600],
),
onPressed: () {
Navigator.pop(context);
},
)
],
)),
body: Container(
child: FutureBuilder<ActiveObd>(
future: obdApi.getActiveObd(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('no connection');
case ConnectionState.active:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
break;
case ConnectionState.done:
if (snapshot.hasError) {
return Center(
child:
new CircularProgressIndicator(),
);
} else {
if (snapshot.hasData) {
var activeobd = snapshot.data;
return ListView.builder(
controller: _scrollController,
itemExtent: 80,
itemBuilder: (context , index){
if (index == myList.length) {
return CupertinoActivityIndicator();
}
final obd = activeobd.obds[index];
DateTime date = DateTime.parse(obd.dateOBDCommand);
String result = DateFormat('yyyy-MM-dd H:m:s').format(date);
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Container(
height: 30,
child: Text("${obd.description}")),
subtitle: Text(result.toString()),
trailing: Text("${obd.value}"),
)
itemCount:myList.length +1,
);
} else {
return Text('No Data');
}
}
break;
default:
return Container();
break;
}
How i can solve that ?
thanks in advance
This error The operator '[]' isn't defined for the type Map <String, dynamic> Function() tells you that the object you are trying to do the [] operator on is a function. In your case, .data is actually not a member variable but a function. It is solved by just adding () next to the data keyword, so your error line (fixed) would look like that:
document.data()['listCategories'] ?? [];
That might've happened because you updated the firebase core package. It used to be just .data[] but now it's .data()[].

How do i remove range error running in flutter?

I have created a string List and applied a checkbox and when the checkbox clicked, the string list will be shown on the next screen but I am getting a range error. please help.
var _suggestions = <String>['this is me1','this is me2','this is me3' ];
final _saved = <String>['this is me1','this is me2','this is me3' ];
final _biggerFont = TextStyle(fontSize: 18.0);
this is the string that I have defined.
void _pushSaved(){
Navigator.of(context).push(
MaterialPageRoute<void>(
// NEW lines from here...
builder: (BuildContext context) {
final tiles = _saved.map(
(String pair) {
return ListTile(
title: Text(
pair,
style: _biggerFont,
),
);
},
);
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved Suggestions'),
),
body: ListView(children: divided),
);
}, // ...to here.
),
);
}
this is some page route
Widget _buildSuggestions() {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemBuilder: /*1*/ (context, i) {
if (i.isOdd) return Divider(); /*2*/
final index = i ~/ 2; /*3*/
_suggestions = <String>['this is me1','this is me2','this is me3'];
return _buildRow(_suggestions[index]);
});
}
There is no problem in showing selected checkbox data into the next screen but I don't know why the range error is showing.
Widget _buildRow(String pair) {
final alreadySaved = _saved.contains(pair);
return Container(
decoration: new BoxDecoration (
color: HexColor('#F2FFFF'),
border: Border.all(color: HexColor('#09B9B6')),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: ListTile(
title: Text(
pair,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.check_box : Icons.check_box_outline_blank_outlined,
color: alreadySaved ? HexColor('#09B9B6') :null,
),
onTap: (){
setState(() {
if (alreadySaved){
_saved.remove(pair);
}
else{
_saved.add(pair);
}
});
},
),
);
}
this is the build row function
We can also use ListView.separated
ListView.separated(
padding: EdgeInsets.all(16.0),
itemBuilder: (context, index) {
return _buildRow(_suggestions[index]);
},
separatorBuilder: (_, __) => Divider(),
itemCount: _suggestions.length);

Flutter: setState on showDialog()

I need to update the value within the showDialog() [inside confirmBox()] function, when ' + ' or ' - ' is pressed; and render it onto the Container Widget. The setState() doesn't seem to work on that pop up Container.
How do I go about doing this?
(I'm a beginner)
int _n = 0; //counter variable
void add() {
setState(() {
_n++;
});
}
void minus() {
setState(() {
if (_n != 0)
_n--;
});
}
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context){
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
Center(
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: add,
child: Icon(Icons.add, color: Colors.black,),
backgroundColor: Colors.white,),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: minus,
child: Icon(
const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
],
),
),
);
}
);
}
EDIT: In this showDialog document, google say
EDIT2: This code will work
int _n = 0; //counter variable
void add(setState) {
setState(() {
_n++;
});
}
void minus(setState) {
setState(() {
if (_n != 0) _n--;
});
}
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(builder: (context, StateSetter setState) {
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
Center(
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => add(setState),
child: Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: Colors.white,
),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: () => minus(setState),
child: Icon(
const IconData(0xe15b,
fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
],
),
),
);
});
});
}
Use a StatefulBuilder or a custom StatefulWidget if the dialog needs to update dynamically.
Put this widget and other functions into new StatefulWidget
Container(
child: Scaffold(
body: Column(...
And call it inside builder of showDialog
Wrap all the content of the dialog inside of a StatefulBuilder:
https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html
Example:
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
There can be two approaches,
Method 1
Just declare a StatefulBuilder or StatefulWidget inside your dialog.
Method 2
Declare a abstract class
abstract class AlertDialogCallback {
void onPositive(Object object);
void onNegative();
}
then implement this class to your widget like this,
class _ContactUsState extends State<ContactUs> implements AlertDialogCallback {
...
//open dialog and pass this to provide callback a context
onPressed:(){CustomAlertDialog(this).openDialog();}
...
//
#override
void onNegative() {
Navigator.of(context).pop();
}
#override
void onPositive(Object object) {
// do your logic here
}
}
Inside CustomAlertDialog get your mAlertDialogCallback and pass a object there
class CustomAlertDialog {
AlertDialogCallback mAlertDialogCallback;
CustomAlertDialog([this.mAlertDialogCallback]);
openDialog() {
// flutter defined function
showDialog(
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
// usually buttons at the bottom of the dialog
FlatButton(
child: Text(
actionButtonText1.toString().toUpperCase(),
),
onPressed: () {
Navigator.of(context).pop();
mAlertDialogCallback.onPositive(obj);
},
)
],
);
},
);
}
}
Create a StatefulWidget with the widgets you need to display in a Dialog
class MyDialog extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyDialogState();
}
}
class _MyDialogState extends State<MyDialog> {
int _n = 0; //counter variable
void add() {
setState(() {
_n++;
});
}
void minus() {
setState(() {
if (_n != 0) _n--;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: add,
child: Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: Colors.white,
),
Text("$_n", //_n value is not updating yet
style: TextStyle(fontSize: 60.0)),
FloatingActionButton(
onPressed: minus,
child: Icon(const IconData(0xe15b, fontFamily: 'MaterialIcons'),
color: Colors.black),
backgroundColor: Colors.white,
),
],
),
),
);
}
}
And then make change in your 'confirmBox' method as,
void confirmBox() {
showDialog(
context: context,
builder: (BuildContext context) {
return MyDialog();
},
);
}
In case you want to know if the string only contains the decimal digits you can use this function
bool validateIsInt(String str) {
const String digits = "0123456789";
for (int i = 0; i < str.length; i++) {
bool wasFound = false;
for (int j = 0; j < digits.length; j++) {
if (str[i] == digits[j]) {
wasFound = true;
break;
}
}
if (!wasFound) {
return false;
}
}
return true;
}