Related
Why do I keep getting 'Instance of...' when I'm trying to get a String. What's wrong with the function?
Future<string?> counter() async {
Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
int count = widget.food.quantity;
// print(count);
return count;
}
This is what I'm trying to do:
class FoodQuantity extends StatefulWidget {
final Food food;
FoodQuantity(this.food);
#override
State<FoodQuantity> createState() => _FoodQuantityState();
}
class _FoodQuantityState extends State<FoodQuantity> {
final int amount = 0;
String getCurrency() {
var format = NumberFormat.simpleCurrency(name: 'NGN');
return format.currencySymbol;
}
Future<int> counter() async {
final int result = await Future.delayed(const Duration(seconds: 5), () {
int result = context.read<FoodCount>().display(widget.food);
return result;
});
return result;
}
#override
Widget build(BuildContext context) {
return Container(
width: double.maxFinite,
height: 40,
child: Stack(
children: [
Align(
alignment: const Alignment(-1, 0), //0.3
child: Container(
width: 120,
height: double.maxFinite,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
),
child: Row(
children: [
const SizedBox(width: 15), //Spacing
Text(
getCurrency(),
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
Text(
widget.food.price.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
)
],
),
),
),
Align(
alignment: const Alignment(1, 0), //0.3
child: Container(
height: double.maxFinite,
width: 120,
decoration: BoxDecoration(
color: Color(0xff453658),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
if (context.read<Counter>().count != 0) {
context.read<Counter>().decrement();
// widget.food.quantity--;
userOrders.remove(widget.food);
context.read<FoodCount>().decrement(widget.food);
setState(() {});
} else {
context.read()<Counter>();
}
},
child: const Text(
'-',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Text(
counter().toString(),
// context
// .watch<FoodCount>()
// .display(widget.food)
// .toString(),
// widget.food.quantity.toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
GestureDetector(
onTap: () {
context.read<Counter>().increment();
context.read<FoodCount>().increment(widget.food);
// widget.food.quantity++;
userOrders.add(widget.food);
setState(() {});
},
child: const Text(
'+',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
],
),
);
}
}
I made a provider class FoodCount that monitors the value quantity of object type Food. The async function is supposed to simply return the quantity of the Food provided to it
Provider:
class FoodCount with ChangeNotifier {
int increment(Food food) {
food.quantity++;
int foodCount = food.quantity;
notifyListeners();
return foodCount;
}
int decrement(Food food) {
food.quantity--;
int foodCount = food.quantity;
notifyListeners();
return foodCount;
}
int display(Food food) {
int count = food.quantity;
notifyListeners();
return count;
}
void update() {
notifyListeners();
}
}
Food:
class Food {
String imgUrl;
String desc;
String name;
String waitTime;
num score;
int price;
int quantity;
bool favourited;
List<Map<String, String>> ingredients;
String about;
bool highlight;
Food(this.imgUrl, this.desc, this.name, this.waitTime, this.score, this.price,
this.quantity, this.ingredients, this.about, this.favourited,
{this.highlight = false});
}
Future.delayed is by itself a Future, so you cannot track it without an await to keep the result.
Take a look here, how you can make it, then take care of the difference about a sequential method and a Future method;
Future<String?> counter() async {
// Future.delayed is by itself a future, so you connot track it without an await to get the result
final String result = await Future.delayed(const Duration(seconds: 5), () {
var a = "I'm a Future after 5 seconds" ;
return a;
});
return result;
// Here is not the result you want because this method might be not a Future I think
// int count = widget.food.quantity;
// print(count);
// return count;
}
Or
Future<String?> counter2() async {
return await Future.delayed(const Duration(seconds: 5), () {
var a = "I'm a Future after 5 seconds" ;
return a;
});
// Here is not the result you want because this method might be not a Future I think
// int count = widget.food.quantity;
// print(count);
// return count;
}
When you work with Future and you want to get value from it, you should use await or then()
try to use this code:
await Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
First off, here's a tip: you're using Future.delayed as a way to get a value after a delay. Try splitting that up into two parts. Instead of
Future.delayed(const Duration(seconds: 5), () {
context.watch<FoodCount>().display(widget.food).toString();
return widget.food.quantity.toString();
});
int count = widget.food.quantity;
Try
await Future.delayed(const Duration(seconds: 5));
context.watch<FoodCount>().display(widget.food.toString());
return widget.food.quantity.toString();
Secondly, the other users are right: when you receive a Future<String>, you can't actually get to the String without awaiting it. Problem is, you can use await in an async function, and build is not async. Conceptually, think of it as "you need to wait 5 seconds for the delay, but your user needs a UI now".
You can solve this using FutureBuilder, which allows you to return some widget until the future finishes.
// In your State class:
late final Future<int> futureCounter; // the future containing your data
#override
void initState() {
// Start your counter now, before the UI loads
futureCounter = counter();
super.initState();
}
// in your build:
Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: FutureBuilder(
future: futureCounter,
builder: (context, snapshot) => Text(
snapshot.hasData ? snapshot.data : "Loading...",
)
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
I am working on creating a signup process with multiple screens. For now I have first name and age screen. I have the following usermodel. The idea is whenever I update one of the field, it triggers rebuild of the signup-screen, so that the next item in sign-up is shown. I have 2 questions:
Can I pass around the setter from the user model, like I did below. Or is there a better way.
I am getting the following error when I click the 'Next_na' button inorder to add the first name
LateInitializationError: Field 'firstName' has not been initialized.
Thank you for your help in advance!
class UserModel extends ChangeNotifier {
late String firstName;
late int userAge;
int indexer = 0;
set addFName(String firstName) {
firstName = this.firstName;
indexer = indexer + 1;
notifyListeners();
}
String get fName {
return firstName;
}
set addUAge(int userAge) {
userAge = this.userAge;
indexer = indexer + 1;
notifyListeners();
}
int get uAge {
return userAge;
}
}
The signup screen is as follows
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider(
create: (context) => UserModel(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Consumer<UserModel>(builder: (context, user, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Sign Up",
style: TextStyle(fontSize: 24),
textAlign: TextAlign.center,
),
const Divider(
height: 30,
endIndent: 20,
indent: 20,
thickness: 1.5,
color: Colors.grey,
),
const SizedBox(
height: 30,
),
if (user.indexer == 0)
FirstNameWidget(
user: user,
)
else
FirstNameWidget(
user: user,
)
],
);
}),
)),
));
}
and the 'firstNameWidget' that shows the first name field and the 'next' button is as follows:
class FirstNameWidget extends StatelessWidget {
FirstNameWidget({Key? key, required this.user}) : super(key: key);
final UserModel user;
final firstNameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Form(
//key: _firstNameFormKey,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.text,
controller: firstNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter First Name',
),
//autovalidateMode: AutovalidateMode.onUserInteraction,
),
const SizedBox(
height: 20,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
child: ElevatedButton(
onPressed: () {
print(firstNameController.text);
commitFirstName(fName: firstNameController.text, user: user);
//user.addFName = firstNameController.text;
},
child: const Text('Next_na'),
),
),
],
),
);
}
}
void commitFirstName({required String fName, required UserModel user}) {
user.addFName = fName;
}
You did a mistake in your setter:
firstName = this.firstName;
You assign the class member (which has not been initialized yet) to your firstName param which comes from the TextEditingController.
If you turn this around it should work:
this.firstName = firstName;
I have a problem with the listview builder with the radio button. I used two listview builders. The first one for questions and the second for the answers list but the problem arrived in the second listview builder with a radio button.
Example:
I have 3 questions and I select option b answer for the first question then all question's answers are selected b and after that when I select next question answer c then all question's answers select c in the radio button.
Code below:
class SurveyScreen extends StatefulWidget {
const SurveyScreen({Key? key}) : super(key: key);
#override
_SurveyScreenState createState() => _SurveyScreenState();
}
Future<GetSurveyData> getData() async {
GetSurveyData? getSurveyData;
var response = await http.get(
Uri.https('api.ascs.link', 'api/GetUserData'),
headers: {
"content-type": "application/json",
"accept": "application/json",
"Authorization":
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy9304M2E4LTAwNTA1NjQ3N2Y5YyIsIlhhZlNlY3VyaXR5QXV0aFBhc3NlZCI6IlhhZlNlY3VyaXR5QXV0aFBhc3NlZCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJtdXN0YWZhLnNhZWVkIiwiWGFmU2VjdXJpdHkiOiJYYWZTZWN1cml0eSIsIlhhZkxvZ29uUGFyYW1zIjoicTFZcUxVNHQ4a3ZNVFZXeVVzb3RMUzVKVEV2VUswNU1UVTFSMGxFcVNDd3VMczh2U2dGS0dRYWFGQnViK0ZRbzFRSUEiLCJleHAiOjE2NjI3ODI3MDV9.OFZho2bs_ZIR_bcPdL9fXHSmWH5_K58E66OV0VDFOvc"
},
);
var jsonResponse = json.decode(response.body);
print(jsonResponse);
getSurveyData = GetSurveyData.fromJson(jsonResponse);
return getSurveyData;
}
class _SurveyScreenState extends State<SurveyScreen> {
var groupValue = -1;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xff98C337),
body: Stack(
children: [
SingleChildScrollView(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(
// top: MediaQuery.of(context).size.height * 0.5,
top: 50,
left: 30,
right: 30),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.arrow_back_ios),
),
),
const SizedBox(
height: 30,
),
FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot<GetSurveyData> snapshot) {
if (snapshot.data != null) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
snapshot.data?.data.enabledSurveys[0]
.englishName ??
"",
style: const TextStyle(
color: Colors.black,
fontSize: 25,
fontFamily: 'Josefin Sans',
fontWeight: FontWeight.w700),
)),
const SizedBox(
height: 10,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
snapshot.data?.data.enabledSurveys[0]
.arabicName ??
"",
style: const TextStyle(
color: Colors.black, fontSize: 12),
)),
const SizedBox(
height: 30,
),
SizedBox(
height: 500,
child: Align(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(0.0),
scrollDirection: Axis.horizontal,
// itemCount: userDashboard.joinedUpcomingClasses.length,
itemCount: snapshot.data?.data
.enabledSurveys[0].questions.length,
itemBuilder:
(BuildContext context, int index) {
return SizedBox(
width: 300,
child: Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: [
Text(
snapshot
.data
?.data
.enabledSurveys[0]
.questions[index]
.englishText ??
'',
style: const TextStyle(
color: Colors.black,
fontSize: 18,
fontFamily:
'Josefin Sans',
fontWeight:
FontWeight.w700),
),
SizedBox(
height: 10,
),
Text(
snapshot
.data
?.data
.enabledSurveys[0]
.questions[index]
.arabicText ??
'',
style: const TextStyle(
color: Colors.grey,
fontSize: 12)),
Builder(builder: (context) {
if (snapshot
.data
?.data
.enabledSurveys[0]
.questions[index]
.answers
.length ==
0) {
return textField();
} else {
return answeringListData(
snapshot
.data
?.data
.enabledSurveys[
0]
.questions[
index]
.answers ??
[]);
}
}),
],
),
)),
);
},
),
),
),
],
);
} else {
return SizedBox(
height: double.infinity,
child: Center(
child: Column(
//Horizontal
crossAxisAlignment: CrossAxisAlignment.center,
//Vertical
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircularProgressIndicator(),
Text("Loading..."),
],
)),
);
}
}),
const SizedBox(
height: 20,
),
],
),
))
],
),
);
}
Widget answeringListData(List<Answers> answers) {
return SizedBox(
height: 200,
child: ListView.builder(
padding: EdgeInsets.zero,
physics: ClampingScrollPhysics(),
itemCount: answers.length,
itemBuilder: (context, index) => ButtonBar(
buttonPadding: EdgeInsets.zero,
alignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(answers[index].englishText,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black)),
Radio(
groupValue: groupValue,
value: index,
onChanged: (newValue) =>
setState(() => groupValue = newValue as int),
),
],
),
),
);
}
Widget textField() {
return const Flexible(
child: Align(
alignment: Alignment.topCenter,
child: TextField(
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
border: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
hintText: "Please Type Your Answer Here...",
hintStyle: TextStyle(color: Colors.black),
),
style: TextStyle(color: Colors.black),
textInputAction: TextInputAction.next),
),
);
}
}
My Model Class
class GetSurveyData {
GetSurveyData({
required this.message,
required this.data,
});
late final String message;
late final Data data;
GetSurveyData.fromJson(Map<String, dynamic> json){
message = json['message'];
data = Data.fromJson(json['data']);
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['message'] = message;
_data['data'] = data.toJson();
return _data;
}
}
class Data {
Data({
required this.doneBy,
required this.userName,
this.email,
this.mobileNumber,
required this.fullNameInEnglish,
required this.fullNameInArabic,
required this.enabledSurveys,
required this.shops,
});
late final String doneBy;
late final String userName;
late final Null email;
late final Null mobileNumber;
late final String fullNameInEnglish;
late final String fullNameInArabic;
late final List<EnabledSurveys> enabledSurveys;
late final List<Shops> shops;
Data.fromJson(Map<String, dynamic> json){
doneBy = json['doneBy'];
userName = json['userName'];
email = null;
mobileNumber = null;
fullNameInEnglish = json['fullNameInEnglish'];
fullNameInArabic = json['fullNameInArabic'];
enabledSurveys = List.from(json['enabledSurveys']).map((e)=>EnabledSurveys.fromJson(e)).toList();
shops = List.from(json['shops']).map((e)=>Shops.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['doneBy'] = doneBy;
_data['userName'] = userName;
_data['email'] = email;
_data['mobileNumber'] = mobileNumber;
_data['fullNameInEnglish'] = fullNameInEnglish;
_data['fullNameInArabic'] = fullNameInArabic;
_data['enabledSurveys'] = enabledSurveys.map((e)=>e.toJson()).toList();
_data['shops'] = shops.map((e)=>e.toJson()).toList();
return _data;
}
}
class EnabledSurveys {
EnabledSurveys({
required this.referenceTemplate,
required this.englishName,
required this.arabicName,
required this.sequence,
required this.questions,
});
late final String referenceTemplate;
late final String englishName;
late final String arabicName;
late final int sequence;
late final List<Questions> questions;
EnabledSurveys.fromJson(Map<String, dynamic> json){
referenceTemplate = json['referenceTemplate'];
englishName = json['englishName'];
arabicName = json['arabicName'];
sequence = json['sequence'];
questions = List.from(json['questions']).map((e)=>Questions.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['referenceTemplate'] = referenceTemplate;
_data['englishName'] = englishName;
_data['arabicName'] = arabicName;
_data['sequence'] = sequence;
_data['questions'] = questions.map((e)=>e.toJson()).toList();
return _data;
}
}
class Questions {
Questions({
required this.referenceQuestion,
required this.englishText,
required this.arabicText,
required this.questionType,
required this.required,
required this.evidencePhotoRequired,
required this.sequence,
required this.answers,
});
late final String referenceQuestion;
late final String englishText;
late final String arabicText;
late final String questionType;
late final bool required;
late final bool evidencePhotoRequired;
late final int sequence;
late final List<Answers> answers;
Questions.fromJson(Map<String, dynamic> json){
referenceQuestion = json['referenceQuestion'];
englishText = json['englishText'];
arabicText = json['arabicText'];
questionType = json['questionType'];
required = json['required'];
evidencePhotoRequired = json['evidencePhotoRequired'];
sequence = json['sequence'];
answers = List.from(json['answers']).map((e)=>Answers.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['referenceQuestion'] = referenceQuestion;
_data['englishText'] = englishText;
_data['arabicText'] = arabicText;
_data['questionType'] = questionType;
_data['required'] = required;
_data['evidencePhotoRequired'] = evidencePhotoRequired;
_data['sequence'] = sequence;
_data['answers'] = answers.map((e)=>e.toJson()).toList();
return _data;
}
}
class Answers {
Answers({
required this.referenceAnswer,
required this.englishText,
required this.arabicText,
required this.evidencePhotoRequired,
required this.commentRequired,
required this.sequence,
});
late final String referenceAnswer;
late final String englishText;
late final String arabicText;
late final bool evidencePhotoRequired;
late final bool commentRequired;
late final int sequence;
Answers.fromJson(Map<String, dynamic> json){
referenceAnswer = json['referenceAnswer'];
englishText = json['englishText'];
arabicText = json['arabicText'];
evidencePhotoRequired = json['evidencePhotoRequired'];
commentRequired = json['commentRequired'];
sequence = json['sequence'];
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['referenceAnswer'] = referenceAnswer;
_data['englishText'] = englishText;
_data['arabicText'] = arabicText;
_data['evidencePhotoRequired'] = evidencePhotoRequired;
_data['commentRequired'] = commentRequired;
_data['sequence'] = sequence;
return _data;
}
}
class Shops {
Shops({
required this.id,
required this.title,
required this.nameInEnglish,
required this.nameInArabic,
required this.addressInEnglish,
required this.addressInArabic,
required this.contactNumber,
required this.latitude,
required this.longitude,
required this.channel,
});
late final String id;
late final String title;
late final String nameInEnglish;
late final String nameInArabic;
late final String addressInEnglish;
late final String addressInArabic;
late final String contactNumber;
late final double latitude;
late final double longitude;
late final String channel;
Shops.fromJson(Map<String, dynamic> json){
id = json['id'];
title = json['title'];
nameInEnglish = json['nameInEnglish'];
nameInArabic = json['nameInArabic'];
addressInEnglish = json['addressInEnglish'];
addressInArabic = json['addressInArabic'];
contactNumber = json['contactNumber'];
latitude = json['latitude'];
longitude = json['longitude'];
channel = json['channel'];
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['id'] = id;
_data['title'] = title;
_data['nameInEnglish'] = nameInEnglish;
_data['nameInArabic'] = nameInArabic;
_data['addressInEnglish'] = addressInEnglish;
_data['addressInArabic'] = addressInArabic;
_data['contactNumber'] = contactNumber;
_data['latitude'] = latitude;
_data['longitude'] = longitude;
_data['channel'] = channel;
return _data;
}
}
The problem is that you're using one state, groupValue for all the answers for all questions. To solve it split groupValue between the questions. A Map<int, int> would do that where the key is the question index and the value is the answer index. It would be like the following:
Pass the question index to answeringListData and set the map accordingly.
Widget answeringListData(int questionIndex, List<Answers> answers) {
return SizedBox(
height: 200,
child: ListView.builder(
padding: EdgeInsets.zero,
physics: ClampingScrollPhysics(),
itemCount: answers.length,
itemBuilder: (context, index) => ButtonBar(
buttonPadding: EdgeInsets.zero,
alignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(answers[index].englishText,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black)),
Radio(
groupValue: groupValue[questionIndex],
value: index,
onChanged: (newValue) =>
setState(() => groupValue[questionIndex] = newValue!),
),
],
),
),
);
}
The call site is going to be like this:
answeringListData(index, snapshot.data?.data.enabledSurveys[0]
.questions[index].answers ??[]);
And here is the groupValue map declaration from just int to a Map:
final groupValue = <int, int>{};
Here's the result:
This answer is base on you model.
First change your question model to this:
class Questions {
Questions({
required this.referenceQuestion,
required this.englishText,
required this.arabicText,
required this.questionType,
required this.required,
required this.evidencePhotoRequired,
required this.sequence,
required this.answers,
required this.groupValue,
});
late final String referenceQuestion;
late final String englishText;
late final String arabicText;
late final String questionType;
late final bool required;
late final bool evidencePhotoRequired;
late final int sequence;
late final List<Answers> answers;
late final int groupValue;
Questions.fromJson(Map<String, dynamic> json) {
referenceQuestion = json['referenceQuestion'];
englishText = json['englishText'];
arabicText = json['arabicText'];
questionType = json['questionType'];
required = json['required'];
evidencePhotoRequired = json['evidencePhotoRequired'];
sequence = json['sequence'];
groupValue = -1;
answers =
List.from(json['answers']).map((e) => Answers.fromJson(e)).toList();
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['referenceQuestion'] = referenceQuestion;
_data['englishText'] = englishText;
_data['arabicText'] = arabicText;
_data['questionType'] = questionType;
_data['required'] = required;
_data['evidencePhotoRequired'] = evidencePhotoRequired;
_data['sequence'] = sequence;
// _data['groupValue'] = groupValue;
_data['answers'] = answers.map((e) => e.toJson()).toList();
return _data;
}
}
then change your answeringListData to this, as you can see I use StatefulBuilder, in this way your hole code wont rebuild and every time your api wont call:
Widget answeringListData(
List<Answers> answers, int questionIndex, List<Questions>? questions) {
return SizedBox(
height: 200,
child: StatefulBuilder(builder: (context, innerSetState) {
return ListView.builder(
padding: EdgeInsets.zero,
physics: ClampingScrollPhysics(),
itemCount: answers.length,
itemBuilder: (context, index) => ButtonBar(
buttonPadding: EdgeInsets.zero,
alignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(answers[index].englishText,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black)),
Radio(
groupValue: questions![questionIndex].groupValue,
value: index,
onChanged: (newValue) => innerSetState(() {
var oldQuestions = questions![questionIndex];
var newQuestion = Questions(
referenceQuestion: oldQuestions.referenceQuestion,
englishText: oldQuestions.englishText,
arabicText: oldQuestions.arabicText,
questionType: oldQuestions.questionType,
required: oldQuestions.required,
evidencePhotoRequired: oldQuestions.evidencePhotoRequired,
sequence: oldQuestions.sequence,
answers: oldQuestions.answers,
groupValue: newValue as int,
);
questions[questionIndex] = newQuestion;
}),
),
],
),
);
}),
);
}
then pass answeringListData like this in your main widget :
...
} else {
return answeringListData(
snapshot.data?.data.enabledSurveys[0].questions[index].anwers ?? [],
index,
snapshot.data?.data.enabledSurveys[0].questions);
}
I have a shopping app, Using Provider I am able to create a list of Cart items. Based on the price and the quantity from each item, a grand total is calculated. I want the user to enter their own quantity.
Here is my CartScreen
Widget build(BuildContext context) {
final cart = Provider.of<Cart>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Items Purchased"),
),
body: Column(
children: <Widget>[
Card(
margin: EdgeInsets.all(15),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Total',
style: TextStyle(
fontSize: 20,
),
),
Spacer(),
Chip(
label: Text(
'GHC ${cart.totalAmount}',
style: TextStyle(
color: Colors.white,
),
),
backgroundColor: Colors.redAccent,
),
FlatButton(
onPressed: () {
print(cart);
// Provider.of<Orders>(context).addOrder(
// cart.items.values.toList(), cart.totalAmount);
// cart.clear();
},
child: Text("CONFIRM"),
),
],
),
),
),
SizedBox(
height: 10,
),
Expanded(
child: ListView.builder(
itemCount: cart.itemCount,
itemBuilder: (ctx, i) => CartItemWidget(
cart.items.values.toList()[i].id,
cart.items.keys.toList()[i],
cart.items.values.toList()[i].price,
TextField(
controller: _controllers[i],
onChanged: (value) {
final x = double.tryParse(value);
setState(() {
itemTotal = x ?? 0;
itemTotal = cart.items.values.toList()[i].price * x;
print(itemTotal);
});
},
),
cart.items.values.toList()[i].quantity,
cart.items.values.toList()[i].title,
cart.items.values.toList()[i].image),
),
),
],
),
);
}
I think the calculation should be at the CartItemWidget instead.
class CartItemWidget extends StatelessWidget {
final String id;
final String productId;
final double price;
final Widget quantityField;
final int quantity;
final String title;
final String image;
CartItemWidget(this.id, this.productId, this.price, this.quantityField,
this.quantity, this.title, this.image);
#override
Widget build(BuildContext context) {
return Dismissible(
key: ValueKey(id),
direction: DismissDirection.endToStart,
background: Container(
color: Theme.of(context).errorColor,
child: Icon(
Icons.delete,
color: Colors.white,
size: 40,
),
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 4),
),
onDismissed: (direction) {
Provider.of<Cart>(context, listen: false).removeItem(productId);
},
child: Card(
margin: EdgeInsets.symmetric(
horizontal: 15,
vertical: 4,
),
child: Padding(
padding: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
child: FittedBox(child: Image.asset(image)),
backgroundColor: Colors.black38,
),
title: Text(title),
subtitle: Text('Total: GHC ${(price * quantity)}'),
trailing: Container(width: 70.0, child: TextField()),
// trailing: Text('$quantity X'),
),
),
),
);
}
}
I have a Model class with Provider too.
class CartItem with ChangeNotifier {
final String id;
final String title;
final int quantity;
final double price;
final String image;
CartItem(
{#required this.id,
#required this.title,
#required this.quantity,
#required this.price,
this.image});
}
class Cart with ChangeNotifier {
Map<String, CartItem> _items = {};
Map<String, CartItem> get items {
return {..._items};
}
int get itemCount {
return _items.length;
}
double get totalAmount {
double total = 0.0;
_items.forEach((key, cartItem) {
total += cartItem.price * cartItem.quantity;
});
return total;
}
double get totalForEachItem {
double total = 0.0;
_items(key, cartItem) {
total = cartItem.price * cartItem.quantity;
}
}
void addItems(String productId, double price, String title, String image) {
if (_items.containsKey(productId)) {
//change quantity
_items.remove(productId);
// clear();
} else {
_items.putIfAbsent(
productId,
() => CartItem(
id: DateTime.now().toString(),
title: title,
price: price,
quantity: 1,
image: image),
);
}
notifyListeners();
}
void removeItem(String productId) {
_items.remove(productId);
notifyListeners();
}
void getItemTotal(String productId, double price, int quantity) {}
void clear() {
_items = {};
notifyListeners();
}
}
What I want
Currently the product details such as name, price and quantity are passed from the products page. I want the user to enter their own quantity and total will automatically update by multiplying the quantity entered for cartItem[i] by the price at cartItem[i] and add the total for each item for the grandtotal.
What I have done
I created a list of controllers so i can get the values at each cartitem. I also used the onChange to get the value to multiply the price and set a total with that.
Create a void function (use a setter type function set )that sets the quantity of the item. then tie the onSubmitted parameter (or however you're receiving the data) of textField with the setter function of the provider.
Now the setter function sets the new value for the quantity of the product and rebuilds the rest of the widgets.
I may not be able to correctly drop you the code, but you could get some help with my idea.
Happy to help :)
The code below works and the like count is updated in the database..but it does not show up on the ui. How can i get the like count in the specific place as shown in the code? In this code it does update to the database ..but it does not show the updated number in the ui. I also used setState in the function to update it to the ui but it still does not show the number which is there in the ui.
class Brew {
final String id;
final String name;
final String sugars;
final int strength;
final int likeCount;
Brew({this.id, this.name, this.sugars, this.strength,
this.likeCount});
}
class BrewData {
final String id;
final String name;
final String sugars;
final int strength;
final int likeCount;
BrewData({this.id, this.name, this.sugars, this.strength,
this.likeCount});
factory BrewData.fromDoc(DocumentSnapshot doc) {
return BrewData(
id: doc.documentID,
name: doc['name'],
sugars: doc['sugars'],
strength: doc['strength'],
likeCount: doc['likeCount'],
);
}
}
class BrewTile extends StatefulWidget {
final Brew brew;
BrewTile({ this.brew});
#override
_BrewTileState createState() => _BrewTileState();
}
class _BrewTileState extends State<BrewTile> {
int _likeCount = 0;
bool _isLiked = false;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
child: ListTile(
leading: CircleAvatar(
radius: 25.0,
backgroundColor: Colors.brown[brew.strength],
backgroundImage:
AssetImage('assets/coffee_icon.png'),
),
title: Text(brew.name),
subtitle: Text('Takes ${brew.sugars} sugar(s)'),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
IconButton(
icon: _isLiked
? Icon(
Icons.favorite,
color: Colors.blue,
)
: Icon(Icons.favorite_border),
iconSize: 30.0,
onPressed: () {
if (_isLiked) {
_likeCount++;
_isLiked = false;
print(_likeCount);
DatabaseService()
.updateLikes(id: widget.brew.id, value:
1);
} else {
print(true);
_likeCount--;
_isLiked = true;
DatabaseService()
.updateLikes(id: widget.brew.id, value:
-1);
print(_likeCount);
}
});
},
),
Padding(
padding: EdgeInsets.symmetric(horizontal:
12.0),
child: Text(
'${_likeCount.toString()} likes',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
),
],
)
],
),
)
],
);
}
}
List<Brew> _brewListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
//print(doc.data);
return Brew(
id: doc.documentID ?? '',
name: doc.data['name'] ?? '',
strength: doc.data['strength'] ?? 0,
sugars: doc.data['sugars'] ?? '0',
likeCount: doc.data['likeCount'] ?? 0,);
}).toList();
}
Future<void> updateLikesCount({String id int value}) async {
return await brewCollection
.document(id)
.updateData({'likeCount': FieldValue.increment(value)});
}
Future<void> updateBrewData(String sugars, String name, int strength, int likeCount) async {
return await brewCollection.document(uid).setData({
'sugars': sugars,
'name': name,
'strength': strength,
'likeCount': likeCount,
});
}
Okay this is a really easy fix in brew_tile.dart make this change
bool _isLiked = false;
int _likeCount;
#override
void initState() {
_likeCount = widget.brew.likeCount;
super.initState();
}
#override
void didUpdateWidget(BrewTile oldWidget) {
if (_likeCount != widget.brew.likeCount) {
_likeCount = widget.brew.likeCount;
print('initState: ${widget.brew.bio} ${widget.brew.likeCount}');
}
super.didUpdateWidget(oldWidget);
}
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Text(
'$_likeCount likes',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
),