Related
I am new in flutter and trying to create a listview with load more functionality.
Here is my class. It is not displaying anything, blank screen. List has data I am getting result in console.
class ReportPurchaseNew extends StatefulWidget {
final AdminUserDetails userDetails;
final String title;
const ReportPurchaseNew({Key key, this.title, this.userDetails})
: super(key: key);
#override
State<StatefulWidget> createState() => new ReportPurchaseState();
}
class ReportPurchaseState extends State<ReportPurchaseNew> {
String fromDate = "", toDate = "", usageType = "";
int limit = 7, offset = 0;
static int page = 0;
List<Result> _List = new List();
List<Result> _filteredList;
Future<PurchaseReport> _PurchaseReportResponse;
List<UsageResult> _usageList = [];
UsageResult _usageVal;
ScrollController _sc = new ScrollController();
bool isLoading = false;
//List users = new List();
#override
void initState() {
this._getMorePurchase(page);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels ==
_sc.position.maxScrollExtent) {
_getMorePurchase(page);
}
});
}
#override
void dispose() {
_sc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Lazy Load Large List"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomInset: false,
);
}
Widget _buildList() {
return ListView.builder(
itemCount: _List.length + 1, // Add one more item for progress indicator
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (BuildContext context, int index) {
if (index == _List.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
leading: CircleAvatar(
radius: 30.0,
),
title :Text("my:"+(_List[index]
.product)),
subtitle: Text((_List[index]
.unitPrice)),
);
}
},
controller: _sc,
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
Future<PurchaseReport> getProjectDetails() async {
var result = await PurchaseReportRequest(
context,
widget.userDetails.result.raLoginId,
limit.toString(),
offset.toString(),
fromDate,
toDate,
_usageVal!=null ? _usageVal.name : "" ,
);
return result;
}
void _getMorePurchase(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
_PurchaseReportResponse = getProjectDetails();
setState(() {
isLoading = false;
_PurchaseReportResponse.then((response) {
if (response != null) {
_List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
print('name:' +_List[i].product );
}
} else {
errorRaisedToast(context);
}
});
});
}
}
}
Try This,
if (response != null) {
List newList = new List();
// _List.addAll(response.result);
page = page + limit;
print("printing length : "
+_List.length.toString());
for (int i = 0; i < response.result.length; i++) {
newList.add(response.result[i]);
print('name:' +_List[i].product);
}
isLoading = false;
_List.addAll(newList);
page++;
} else {
errorRaisedToast(context);
}
Trying to use a set of nested PopupMenuButtons in a flutter app. The first menu opens as expected. The second menu opens only after tapping many times, closing the first menu, re-opening it, i.e. random behavior. Same is true for the third menu. Sometimes the first or second menu close prematurely without having collected all three pieces of information from the user. What is wrong in my code below???
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:localstore/localstore.dart';
import 'package:http/http.dart' as http;
late Map<String, dynamic> dirList;
List eventList = [];
List eventYearList = [];
List eventDayList = [];
String eventName = '';
String eventYear = '';
String eventDay = '';
String eventDomain = '';
late Map<String, String> eventInfo;
String eventTitle = "Selecteer een evenement";
// create a list of maptypes, just with the names of the maptypes in Dutch
const List ourMapTypes = ['Wegenkaart', 'Satelliet met labels',
'Satelliet zonder labels', 'Terrein', 'Open Sea Map'];
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late GoogleMapController mapController;
MapType currentMapType = MapType.normal;
final LatLng initialMapPosition = const LatLng(52.2, 4.535);
#override
void initState() {
super.initState();
}
Future<void> _onMapCreated(GoogleMapController controller) async {
mapController = controller;
// Get the list of events ready for selection
dirList = await fetchDirList();
dirList.forEach((k, v) => eventList.add(k));
eventYearList = [];
eventDayList = [];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green[900],
title: PopupMenuButton(
offset: const Offset(0,40),
child: Text(eventTitle),
itemBuilder: (BuildContext context) {
return eventList.map((events) {
return PopupMenuItem(
height: 30.0,
value: events,
child: PopupMenuButton(
offset: const Offset(30,0),
child: Text(events),
itemBuilder: (BuildContext context) {
return eventYearList.map((years) {
return PopupMenuItem(
height: 30.0,
value: years,
child: PopupMenuButton(
offset: const Offset(30,0),
child: Text(years),
itemBuilder: (BuildContext context) {
return eventDayList.map((days) {
return PopupMenuItem(
height: 30.0,
value: days,
child: Text(days)
);
}).toList();
},
onSelected: (eventDayList == []) ? null : newEventSelected,
),
);
}).toList();
},
onSelected: (eventYearList == []) ? null : selectEventDay,
),
);
}).toList();
},
onSelected: selectEventYear,
),
actions: <Widget>[
PopupMenuButton(
child: Image.asset('assets/images/mapicon.png'),
offset: Offset(0,55),
tooltip: 'Selecteer een kaarttype',
onSelected: selectMapType,
itemBuilder: (BuildContext context) {
return ourMapTypes.map((types) {
return PopupMenuItem(
height: 30.0,
value: types,
child: Text(types)
);
}).toList();
},
),
],
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: initialMapPosition,
zoom: 12.0,
),
mapType: currentMapType,
),
bottomNavigationBar: Text('bottombar'),
),
);
}
// Routine to change the Type of the map based on the user selection
void selectMapType(selectedMapType) {
setState(() { // Causes the app to rebuild with the selected choice.
switch (selectedMapType) {
case "Wegenkaart":
currentMapType = MapType.normal;
break;
case "Satelliet met labels":
currentMapType = MapType.hybrid;
break;
case "Satelliet zonder labels":
currentMapType = MapType.satellite;
break;
case "Terrein":
currentMapType = MapType.terrain;
break;
case "Open Sea Map":
currentMapType = MapType.normal;
break;
default:
currentMapType = MapType.normal;
break;
}
});
}
void selectEventYear(event) {
setState(() {
eventName = event;
eventYearList = [];
dirList[event].forEach((k, v) => eventYearList.add(k));
eventYearList = eventYearList.reversed.toList();
eventDayList = [];
});
}
void selectEventDay(year) {
setState(() {
eventYear = year;
eventDayList = [];
eventTitle = eventName + '/' + year;
if (dirList[eventName][eventYear].length != 0) {
dirList[eventName][eventYear].forEach((k, v) => eventDayList.add(k));
} else {
newEventSelected('');
}
});
}
void newEventSelected(day) {
setState(() {
eventDay = day;
eventDomain = eventName + '/' + eventYear;
if (eventDay != '') eventDomain = eventDomain + '/' + eventDay;
eventTitle = eventDomain; // for the time being
eventYearList = [];
eventDayList = [];
});
}
Future<Map<String, dynamic>> fetchDirList() async {
final response = await http
.get(Uri.parse('https://tt.zeilvaartwarmond.nl/get-dirlist.php?tst=true&msg=simple'));
if (response.statusCode == 200) {
return (jsonDecode(response.body));
} else {
throw Exception('Failed to load dirList');
}
}
}
The default behavior of PopupMenuButton is to close it after selecting. While using nested PopupMenuButton you need to be careful about context, which one when and how it is closing.
Next issue comes from the padding of PopupMenuItem, each item does not take full size.
You can use PopupMenuItem's onTap or onSelected from PopupMenuButton to find selected value. If you want to update UI on dialog, check StatefulBuilder.
This is a test snippet:
PopupMenuButton(
child: const Text("POP U"),
onSelected: (value) {
print(value);
},
itemBuilder: (BuildContext context_p0) {
return [
const PopupMenuItem(value: "item: p1", child: Text("Item:p1 ")),
PopupMenuItem(
value: "item: p1",
onTap: () {},
padding: EdgeInsets.zero,
child: PopupMenuButton(
padding: EdgeInsets.zero,
child: Container(
alignment: Alignment.center,
height: 48.0, //default height
width: double.infinity,
child: Text("inner PopUp Menu"),
),
itemBuilder: (context_p1) {
return [
PopupMenuItem(
value: "inner p2",
child: Text("inner p2: close with parent "),
onTap: () {
Navigator.of(context_p1).pop();
},
),
const PopupMenuItem(
value: 'inner p1',
child: Text("inner p1, just close this one"),
),
];
},
),
)
];
},
),
I want to increment number on trailing ListTile Flutter when ontap ListTile,
but always return to 0?
i'm using future builder fyi,
thanks in advance
this my model
class ItemModel {
String name;
String price;
String image;
bool isSelected = false;
int countSelected = 0;
ItemModel(
this.name,
this.price,
this.image,
);
}
This method to get data from api json from local server
Future<List<ItemModel>> _getItems() async {
List<ItemModel> listItems = [];
var url = Uri.parse(BASEURLLOCAL.apiGetItems);
var data = await http.get(url);
var jsonData = jsonDecode(data.body);
for (var p in jsonData) {
ItemModel item = ItemModel(
p["name"],
p["price"],
p["image"],
// p["PRODUCT_NAME"],
// p["PRICE_SELL"],
// p["FILENAME"],
);
listItems.add(item);
}
return listItems;
}
and this the listview builder
#override
Widget build(BuildContext context) {
return FutureBuilder<List<ItemModel>>(
future: _getItems(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext contex, int idx) {
if (_itemCount.length < snapshot.data.length) {
_itemCount.add(0);
}
return Card(
child: ListTile(
leading: Image.network(
BASEURLLOCAL.imgItem + snapshot.data[idx].image),
title: Text(snapshot.data[idx].name),
subtitle: Text(snapshot.data[idx].price),
// trailing: _buildTrailing(),
trailing: Text(snapshot.data[idx].countSelected.toString()),
onTap: () {
setState(() {
snapshot.data[idx].countSelected++;
});
},
),
);
},
);
}
},
);
}
this is what the problem is
Try something like this, create a list
List<ItemModel> listItems = [];
then, in initState() call the _getItems()
_getItems() async {
var url = Uri.parse(BASEURLLOCAL.apiGetItems);
var data = await http.get(url);
var jsonData = jsonDecode(data.body);
for (var p in jsonData) {
ItemModel item = ItemModel(
p["name"],
p["price"],
p["image"],
// p["PRODUCT_NAME"],
// p["PRICE_SELL"],
// p["FILENAME"],
);
listItems.add(item);
}
}
And remove FutureBuilder
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: listItems.length,
itemBuilder: (BuildContext context, int idx) {
if (_itemCount.length < listItems.length) {
_itemCount.add(0);
}
return Card(
child: ListTile(
leading: Image.network(BASEURLLOCAL.imgItem + listItems[idx].image),
title: Text(listItems[idx].name),
subtitle: Text(listItems[idx].price),
// trailing: _buildTrailing(),
trailing: Text(listItems[idx].countSelected.toString()),
onTap: () {
setState(() {
listItems[idx].countSelected++;
});
},
),
);
},
);
}
By this way it won't fetch everytime when it rebuilds.
I have quiz app (flutter+Firebase), but I want to sort by question(1) not by question id(2). or hot to rename questions id in firebase?
what should I change in my code? Is there any way to do it?
and how to fix this error?
.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:quiz2/database/firebase.dart';
import 'package:quiz2/model/question.dart';
import 'package:quiz2/screens/words/results.dart';
import 'package:quiz2/widgets/play.dart';
class QuizPlay extends StatefulWidget {
final String quizId;
QuizPlay(this.quizId);
#override
_QuizPlayState createState() => _QuizPlayState();
}
int _correct = 0;
int _incorrect = 0;
int _notAttempted = 0;
int total = 0;
/// Stream
Stream infoStream;
class _QuizPlayState extends State<QuizPlay> {
QuerySnapshot questionSnaphot;
DatabaseService databaseService = new DatabaseService();
bool isLoading = true;
#override
void initState() {
databaseService.getQuizData(widget.quizId).then((value) {
questionSnaphot = value;
_notAttempted = questionSnaphot.docs.length;
_correct = 0;
_incorrect = 0;
isLoading = false;
total = questionSnaphot.docs.length;
setState(() {});
print("init don $total ${widget.quizId} ");
});
if (infoStream == null) {
infoStream = Stream<List<int>>.periodic(Duration(milliseconds: 100), (x) {
print("this is x $x");
return [_correct, _incorrect];
});
}
super.initState();
}
QuestionModel getQuestionModelFromDatasnapshot(
DocumentSnapshot questionSnapshot) {
QuestionModel questionModel = new QuestionModel();
questionModel.question = questionSnapshot["question"];
/// shuffling the options
List<String> options = [
questionSnapshot["option1"],
questionSnapshot["option2"],
questionSnapshot["option3"],
questionSnapshot["option4"]
];
options.shuffle();
questionModel.option1 = options[0];
questionModel.option2 = options[1];
questionModel.option3 = options[2];
questionModel.option4 = options[3];
questionModel.correctOption = questionSnapshot["option1"];
questionModel.answered = false;
print(questionModel.correctOption.toLowerCase());
return questionModel;
}
#override
void dispose() {
infoStream = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Align(
child: Text("#ЄВІ_ПІДГОТОВКА"),
alignment: Alignment.center,
),
),
body: isLoading
? Container(
child: Center(child: CircularProgressIndicator()),
)
: SingleChildScrollView(
child: Container(
child: Column(
children: [
InfoHeader(
length: questionSnaphot.docs.length,
),
SizedBox(
height: 10,
),
questionSnaphot.docs == null
? Container(
child: Center(
child: Text("No Data"),
),
)
: ListView.builder(
itemCount: questionSnaphot.docs.length,
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
return QuizPlayTile(
questionModel: getQuestionModelFromDatasnapshot(
questionSnaphot.docs[index]),
index: index,
);
})
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.check),
onPressed: (){
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (contex) => Results(
correct: _correct,
incorrect: _incorrect,
total: total,
) ));
},)
);
}
}
class InfoHeader extends StatefulWidget {
final int length;
InfoHeader({#required this.length});
#override
_InfoHeaderState createState() => _InfoHeaderState();
}
class _InfoHeaderState extends State<InfoHeader> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: infoStream,
builder: (context, snapshot) {
return snapshot.hasData
? Container(
height: 40,
margin: EdgeInsets.only(left: 14),
child: ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
children: <Widget>[
NoOfQuestionTile(
text: "Total",
number: widget.length,
),
NoOfQuestionTile(
text: "Correct",
number: _correct,
),
NoOfQuestionTile(
text: "Incorrect",
number: _incorrect,
),
NoOfQuestionTile(
text: "NotAttempted",
number: _notAttempted,
),
],
),
)
: Container();
});
}
}
class QuizPlayTile extends StatefulWidget {
final QuestionModel questionModel;
final int index;
QuizPlayTile({#required this.questionModel, #required this.index});
#override
_QuizPlayTileState createState() => _QuizPlayTileState();
}
class _QuizPlayTileState extends State<QuizPlayTile> {
String optionSelected = "";
#override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 20),
child: Text(
"Q${widget.index + 1}. ${widget.questionModel.question}",
style:
TextStyle(fontSize: 18, color: Colors.black.withOpacity(0.8)),
),
),
SizedBox(
height: 12,
),
GestureDetector(
onTap: () {
if (!widget.questionModel.answered) {
///correct
if (widget.questionModel.option1 ==
widget.questionModel.correctOption) {
setState(() {
optionSelected = widget.questionModel.option1;
widget.questionModel.answered = true;
_correct = _correct + 1;
_notAttempted = _notAttempted - 1;
});
} else {
setState(() {
optionSelected = widget.questionModel.option1;
widget.questionModel.answered = true;
_incorrect = _incorrect + 1;
_notAttempted = _notAttempted - 1;
});
}
}
},
child: OptionTile(
option: "A",
description: "${widget.questionModel.option1}",
correctAnswer: widget.questionModel.correctOption,
optionSelected: optionSelected,
),
),
SizedBox(
height: 4,
),
GestureDetector(
onTap: () {
if (!widget.questionModel.answered) {
///correct
if (widget.questionModel.option2 ==
widget.questionModel.correctOption) {
setState(() {
optionSelected = widget.questionModel.option2;
widget.questionModel.answered = true;
_correct = _correct + 1;
_notAttempted = _notAttempted - 1;
});
} else {
setState(() {
optionSelected = widget.questionModel.option2;
widget.questionModel.answered = true;
_incorrect = _incorrect + 1;
_notAttempted = _notAttempted - 1;
});
}
}
},
child: OptionTile(
option: "B",
description: "${widget.questionModel.option2}",
correctAnswer: widget.questionModel.correctOption,
optionSelected: optionSelected,
),
),
SizedBox(
height: 4,
),
GestureDetector(
onTap: () {
if (!widget.questionModel.answered) {
///correct
if (widget.questionModel.option3 ==
widget.questionModel.correctOption) {
setState(() {
optionSelected = widget.questionModel.option3;
widget.questionModel.answered = true;
_correct = _correct + 1;
_notAttempted = _notAttempted - 1;
});
} else {
setState(() {
optionSelected = widget.questionModel.option3;
widget.questionModel.answered = true;
_incorrect = _incorrect + 1;
_notAttempted = _notAttempted - 1;
});
}
}
},
child: OptionTile(
option: "C",
description: "${widget.questionModel.option3}",
correctAnswer: widget.questionModel.correctOption,
optionSelected: optionSelected,
),
),
SizedBox(
height: 4,
),
GestureDetector(
onTap: () {
if (!widget.questionModel.answered) {
///correct
if (widget.questionModel.option4 ==
widget.questionModel.correctOption) {
setState(() {
optionSelected = widget.questionModel.option4;
widget.questionModel.answered = true;
_correct = _correct + 1;
_notAttempted = _notAttempted - 1;
});
} else {
setState(() {
optionSelected = widget.questionModel.option4;
widget.questionModel.answered = true;
_incorrect = _incorrect + 1;
_notAttempted = _notAttempted - 1;
});
}
}
},
child: OptionTile(
option: "D",
description: "${widget.questionModel.option4}",
correctAnswer: widget.questionModel.correctOption,
optionSelected: optionSelected,
),
),
SizedBox(
height: 20,
),
],
),
);
}
}
Is there any way to do it? In case you want to see the code please let me know I will update more.
Is there any way to do it? In case you want to see the code please let me know I will update more.
It all comes back to how you save your data.
Can questions be shared between quizzes
here are sample db diagram
If your Questions can be shared between quizzes you might want to have a collection with all the quizzes and another with questions and tag each question with a quizID, and its no/ occurrence.
How To query for this, get a quiz ID and fetch all questions for where quizID match and order by no/ occurrence
so your db would look something like this
collectionQUiz-|_quiz0
|_quiz1
|_quiz2-|-quizId,
|-timestamp //always good to use for querying
collectionQuestions-|q1d-|quizId
|-other prams
|q2d-|quizId
|-other prams
querying questions you can use timestamps, We cant see how you create the questions, my guess, you are either creating one question at a time, uploading using a for loop,sending a list, if you add a timestamp key to your question object, you can use it to query for the first added question for that quiz.
Else the one creating a quiz can number the questions ,or you number them before uploading , and save the value in the db and use it to sort
I think this would be a better approach, with firestore indexing , your database should handle this easily
I have a parent widget called createRoutineScreen and it has 7 similar children widget called RoutineFormCard. RoutineFormCard is a form and which has a state _isPostSuccesful of boolean type to tell whether the form is saved to database or not. Now, I have to move to the other screen from createRoutine only when all of it's 7 children has _isPostSuccesful true. How can I access all of children's state from createRoutineScreen widget?
My Code is:
class CreateRoutineScreen extends StatefulWidget {
final String userID;
CreateRoutineScreen({this.userID});
//TITLE TEXT
final Text titleSection = Text(
'Create a Routine',
style: TextStyle(
color: Colors.white,
fontSize: 25,
)
);
final List<Map> weekDays = [
{"name":"Sunday", "value":1},
{"name":"Monday", "value":2},
{"name":"Tuesday", "value":3},
{"name":"Wednesday", "value":4},
{"name":"Thursday", "value":5},
{"name":"Friday", "value":6},
{"name":"Saturday", "value":7},
];
#override
_CreateRoutineScreenState createState() => _CreateRoutineScreenState();
}
class _CreateRoutineScreenState extends State<CreateRoutineScreen> {
Routine routine;
Future<List<dynamic>> _exercises;
dynamic selectedDay;
int _noOfRoutineSaved;
List _keys = [];
Future<List<dynamic>>_loadExercisesData()async{
String url = BASE_URL+ "exercises";
var res = await http.get(url);
var exercisesList = Exercises.listFromJSON(res.body);
//var value = await Future.delayed(Duration(seconds: 5));
return exercisesList;
}
#override
void initState(){
super.initState();
_exercises = _loadExercisesData();
_noOfRoutineSaved = 0;
for (int i = 0; i< 7; i++){
_keys.add(UniqueKey());
}
}
void _changeNoOfRoutineSaved(int a){
setState(() {
_noOfRoutineSaved= _noOfRoutineSaved + a;
});
}
#override
Widget build(BuildContext context) {
print(_noOfRoutineSaved);
return Scaffold(
appBar: AppBar(
title:Text("Create a Routine"),
centerTitle: true,
actions: <Widget>[
FlatButton(
child: Text("Done"),
onPressed: (){
},
),
],
),
body: Container(
color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top:5.0,left: 10,right: 10,bottom: 10),
child: FutureBuilder(
future: _exercises,
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: widget.weekDays.length,
itemBuilder: (context,index){
return RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key:_keys[index]
);
},
);
}
else if(snapshot.hasError){
return SnackBar(
content: Text(snapshot.error),
);
}
else{
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
)
);
}
},
)
),
);
}
}
And my child widget is:
class RoutineFormCard extends StatefulWidget {
final Function createRoutineState;
final String weekDay;
final List<dynamic> exerciseList;
final String userID;
final Function changeNoOfRoutineSaved;
RoutineFormCard({this.createRoutineState,
this.weekDay, this.exerciseList, this.changeNoOfRoutineSaved,
this.userID, Key key}):super(key:key);
#override
_RoutineFormCardState createState() => _RoutineFormCardState();
}
class _RoutineFormCardState extends State<RoutineFormCard> {
bool _checkBoxValue= false;
List<int> _selectedExercises;
bool _inAsyncCall;
bool _successfulPost;
#override
void initState(){
super.initState();
_selectedExercises = [];
_inAsyncCall = false;
_successfulPost= false;
}
void onSaveClick()async{
setState(() {
_inAsyncCall = true;
});
String url = BASE_URL + "users/routine";
List selectedExercises = _selectedExercises.map((item){
return widget.exerciseList[item].value;
}).toList();
String dataToSubmit = jsonEncode({
"weekDay":widget.weekDay,
"userID": widget.userID==null?"5e9eb190b355c742c887b88d":widget.userID,
"exercises": selectedExercises
});
try{
var res =await http.post(url, body: dataToSubmit,
headers: {"Content-Type":"application/json"});
if(res.statusCode==200){
print("Succesful ${res.body}");
widget.changeNoOfRoutineSaved(1);
setState(() {
_inAsyncCall = false;
_successfulPost = true;
});
}
else{
print("Not succesful ${res.body}");
setState(() {
_inAsyncCall = false;
});
}
}catch(err){
setState(() {
_inAsyncCall = false;
});
print(err);
}
}
Widget saveAndEditButton(){
if(_inAsyncCall){
return CircularProgressIndicator();
}
else if(_successfulPost)
{
return IconButton(
icon: Icon(Icons.edit, color: Colors.black,),
onPressed: (){
widget.changeNoOfRoutineSaved(-1);
setState(() {
_successfulPost = false;
});
},
);
}
else{
return FlatButton(child: Text("Save"),
onPressed: !_checkBoxValue&&_selectedExercises.length==0?null:onSaveClick,);
}
}
//Card Header
Widget cardHeader(){
return AppBar(
title: Text(widget.weekDay, style: TextStyle(
fontFamily: "Raleway",
fontSize: 20,
color: Colors.black,),
),
actions: <Widget>[
saveAndEditButton()
],
backgroundColor: Colors.lime[400],
);
}
Widget cardBody(){
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Rest Day"),
Checkbox(
value: _checkBoxValue,
onChanged: (value){
setState(() {
_checkBoxValue = value;
});
},
)
],
),
),
_checkBoxValue?Container():
SearchableDropdown.multiple(
hint: "Select Exercise",
style: TextStyle(color: Colors.black),
items: widget.exerciseList.map<DropdownMenuItem>((item){
return DropdownMenuItem(
child: Text(item.name), value: item
);
}).toList(),
selectedItems: _selectedExercises,
onChanged: (values){
setState(() {
_selectedExercises = values;
});
},
isExpanded: true,
dialogBox: true,
),
],
);
}
#override
Widget build(BuildContext context) {
print("<><><><><><><><><><><>${widget.weekDay} called");
return Card(
elevation: 8.0,
child: Form(
key: GlobalKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
cardHeader(),
_successfulPost?Container():cardBody()
],
),
),
);
}
}
As you can see, I've tried callBack from parent widget which increases or decrease no of form saved from each of the child widget. It does the work but, when one form is saved, parent state is modified and all other children got rebuild which is unnecessary in my opionion. What's the best way to do it?
Try to use GlobalKey instead of UniqueKey for each RoutineFormCard. It will help you to access the state of each RoutineFormCard. You can do it like this :
// 1. In the top of your CreateRoutineScreen file, add this line (make your RoutineFormCardState class public before)
final List<GlobalKey<RoutineFormCardState>> routineFormCardKeys = <GlobalKey<RoutineFormCardState>>[
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
];
// 2. Then construct your RoutineFormCard using the right key
RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key: routineFormCardKeys[index]
);
// 3. Now you can create a method in CreateRoutineScreen which will check the state of all RoutineFormCard
bool _allRoutineFormCardsCompleted() {
bool result = true;
for (int i = 0; i < 7; i++)
result = result && routineFormCardKeys[i].currentState.isPostSuccessful;
return result;
}
// 4. Finally use the result of the previous method where you want to move on another page
I'm sharing a quick idea to solve your problem, I've not tested it, but I'm ready to improve the answer if needed
Hope this will help!