Basically, I'm trying to make an app, that on one of the screens - the content of a listview will be updated by choosing one of the options listed in the toggleButtons (One shows only upcoming events and the second option is events that have been passed). But when I try to set the new state, it doesn't reload the listview and doesn't colour the selected option in the ToggleButtons. How can I refresh both?
For reference:
List filteredCands = []; //Starts empty, gets filled with events when clicking one of the buttons
List isSelected = [true, false];
ToggleButtons and setState(():
child: ToggleButtons(
isSelected: isSelected,
selectedColor: Colors.white,
color: Color(0xFF33CCCC),
fillColor: Color(0xFF33CCCC),
renderBorder: true,
borderWidth: 1.5,
borderRadius: BorderRadius.circular(10),
children: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Icon(FontAwesomeIcons.calendarXmark, size: 25),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Icon(FontAwesomeIcons.calendarDay, size: 25),
),
],
onPressed: (int newIndex) {
final data = listViewGetCandListByManagerResponse
.jsonBody['candList'] as List;
List<CandModel> cands = data.map((e) => CandModel.fromJson(e))
.toList();
DateTime now = new DateTime.now();
DateTime currentDate = new DateTime(now.year, now.month, now.day);
setState(() {
//looping through the list of bool values
for (int index = 0; index < isSelected.length; index++)
{
//Checking for the index value
if (index == newIndex)
{
isSelected[index] = true;
if (index == 0)
{
for (var i = 0; i < cands.length; i++) {
DateTime expirationDate = DateTime.parse(cands[i].dateEvent);
final bool isExpired = expirationDate.isBefore(currentDate);
if (isExpired == true) {
filteredCands.add(cands[i]);
}
}
}
if (index == 1)
{
for (var i = 0; i < cands.length; i++) {
DateTime expirationDate = DateTime.parse(cands[i].dateEvent);
final bool isFuture = currentDate.isBefore(expirationDate);
if (isFuture == true) {
filteredCands.add(cands[i]);
}
}
}
}
else
{isSelected[index] = false;}
}
});
},
),
That's the ListView:
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 8, 0, 0),
child: FutureBuilder<ApiCallResponse>(
future: GetCandListByManagerCall.call(
entityId: widget.entityId,
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color:
FlutterFlowTheme.of(context).primaryColor,
),
),
);
}
return Builder(
builder: (context) {
if (filteredCands.isEmpty) {
return Center(
child: Text('Error - Looks like there are no events available'
),
);
}
return ListView.builder(
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical,
itemCount: filteredCands.length,
itemBuilder: (context, dataIndex) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: FlutterFlowTheme.of(context).primaryColor,
),
),
);
}
int count = 0;
String date = (filteredCands[dataIndex].dateEvent).toString();
DateTime tempDate = DateTime.parse(date);
String dmy = DateFormat.yMMMd().format(tempDate);
String exDate = dmy; //Date Formatting
return InkWell(
child: SworkerContainerWidget(
desc: filteredCands[dataIndex].descEvent,
fname: filteredCands[dataIndex].wfirstName,
lname: filteredCands[dataIndex].wlastName,
dateEvent: exDate,
),
);
},
);
},
);
And the results:
As you can see, I'm clicking the 2nd option and it doesn't colour it nor refreshing the listview
Check where you first declared on your list. this might be the cause of the problem (found out I misplaced it) it need to go under class __widgetstate and not under builder.
Also would recommend to clear the listview everytime the button gets clicked, unless you want infinite events on your list haha
Related
I believe I am having issues with using setState in a futurebuilder - but Im not sure how to change this? Below you can see I am building a listView using a futureBuilder (http request to api) and connecting it to my ProjectModel model. From here I filter the values I need into List<ProjectSearch> searchList. What I am finding is when I try to implement a search box to filter through the objects I believe the setState is causing the futurebuilder to rebuild the page each time causing duplications. How can I separate the futurebuilder from the listView so that it only displays the one list object as the user types?
class _HomePageState extends State<HomePage> {
TextEditingController controller = TextEditingController();
late final Future<ProjectModel> futureProjects;
List<ProjectSearch> searchList = [];
List<ProjectSearch> finder = [];
var jobNames = [];
var jobNumbers = [];
var techs = [];
var pms = [];
var address = [];
var majors = [];
var budget = [];
#override
void initState() {
super.initState();
futureProjects = fetchProjects();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Upcoming/Live Projects',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
backgroundColor: ColorConstants.darkScaffoldBackgroundColor,
),
drawer: const CustomDrawer(),
backgroundColor: ColorConstants.lightScaffoldBackgroundColor,
body: Center(
child: FutureBuilder<ProjectModel>(
future: futureProjects,
builder: (context, snapshot) {
// finder.clear();
if (snapshot.hasData) {
var data = snapshot.data!;
var columns = data.columns;
var rows = data.rows;
for (var item in rows!) {
var cells = item.cells;
for (var elements in cells!) {
if (elements.columnId != null) {
if (elements.columnId == 2057691532158852) {
var displayValues = elements.displayValue;
if (displayValues != null) {
jobNames.add(displayValues);
}
if (displayValues == null) {
pms.removeLast();
techs.removeLast();
address.removeLast();
majors.removeLast();
budget.removeLast();
}
}
if (elements.columnId == 697505454286724) {
var projectNumber = elements.displayValue;
if (projectNumber != null) {
jobNumbers.add(projectNumber);
}
}
if (elements.columnId == 7452904895342468) {
var techAssigned = elements.displayValue;
if (techAssigned != null) {
if (techAssigned == 'ts#ag.com.au') {
techAssigned = 'Ts';
techs.add(techAssigned);
} else {
techs.add(techAssigned);
}
}
if (techAssigned == null) {
techAssigned = 'No tech assigned as yet';
techs.add(techAssigned);
}
}
if (elements.columnId == 2949305267971972) {
var pmName = elements.displayValue;
if (pmName != null) {
pms.add(pmName);
}
if (pmName == null) {
pmName = 'No project manager allocated';
pms.add(pmName);
}
}
if (elements.columnId == 5201105081657220) {
var addressValue = elements.displayValue;
if (addressValue != null) {
address.add(addressValue);
}
if (addressValue == null) {
addressValue = '';
address.add(addressValue);
}
}
if (elements.columnId == 52961559766916) {
var majorValue = elements.displayValue;
if (majorValue != null) {
majors.add(majorValue);
}
if (majorValue == null) {
majorValue = 'No';
majors.add(majorValue);
}
}
if (elements.columnId == 4226807856686980) {
var budgetHours = elements.displayValue;
if (budgetHours != null) {
budget.add(budgetHours);
}
if (budgetHours == null) {
budgetHours = 'TBA';
budget.add(budgetHours);
}
}
}
}
}
int index = 0;
for (int i = 0; i < jobNames.length; i++) {
// List<ProjectSearch> innerMap = [];
ProjectSearch myProjects = ProjectSearch(
address: jobNames[index],
budget: budget[index],
jobNumber: jobNumbers[index],
major: majors[index],
name: jobNames[index],
pM: pms[index],
tech: techs[index]);
index++;
searchList.add(myProjects);
}
return Container(
child: Column(
children: <Widget>[
Padding(
padding:
const EdgeInsets.only(left: 20, top: 20, right: 20),
child: TextField(
textAlign: TextAlign.center,
controller: controller,
onChanged: search,
keyboardType: const TextInputType.numberWithOptions(
signed: true),
// keeps going....
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: finder.length,
itemBuilder: (context, index) {
// print(finder.length);
final projectData = finder[index];
return MaterialButton(
onPressed: () => showModalBottomSheet<void>(
backgroundColor: Colors.transparent,
context: context,
builder: (BuildContext context) {
return ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(27.0),
topLeft: Radius.circular(27.0)),
child: Container(
height: 1000,
color: ColorConstants
.secondaryDarkAppColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: Container(
height: 400,
margin: const EdgeInsets.only(
top: 20,
left: 20,
right: 20),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment
.spaceAround,
children: [
const Padding(
padding:
EdgeInsets.only(
top: 10.0,
bottom: 5.0)),
const Center(
child: Text(
'Project Details',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight:
FontWeight
.w700),
),
),
Row(
children: [
const Text(
'Project: ',
style: TextStyle(
color: Colors
.white70,
fontWeight:
FontWeight
.w600,
fontSize: 17),
),
const SizedBox(
width: 20),
Flexible(
child: Padding(
padding:
const EdgeInsets
.symmetric(
horizontal:
8.0),
child: Text(
projectData.name,
overflow:
TextOverflow
.ellipsis,
maxLines: 2,
style: const TextStyle(
color: Colors
.white,
fontWeight:
FontWeight
.w600,
fontSize: 17),
),
),
),
],
),
],
),
),
);
},
),
child: Container(
height: 50,
margin: const EdgeInsets.only(
top: 30, left: 20, right: 20),
decoration: const BoxDecoration(
color: ColorConstants
.darkScaffoldBackgroundColor,
borderRadius:
BorderRadius.all(Radius.circular(8)),
),
padding: const EdgeInsets.all(15),
child: Center(
child: Text(
projectData.name,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 17),
),
),
),
);
}),
),
],
),
);
} else if (snapshot.hasError) {
print(snapshot);
return Text(
'${snapshot.error}',
style: const TextStyle(color: Colors.white),
);
} else {
return const CircularProgressIndicator();
}
}),
),
);
}
void search(String query) {
final suggestions = searchList.where((search) {
final projectName = search.name.toLowerCase();
final input = query.toLowerCase();
return projectName.contains(input);
}).toList();
setState(() {
finder.clear();
finder = suggestions;
});
}
}
Here is what my UI looks like after searching....
Inside your FutureBuilder's builder, try this:
searchList = [];// <---- add this
int index = 0;
for (int i = 0; i < jobNames.length; i++) {
// List<ProjectSearch> innerMap = [];
ProjectSearch myProjects = ProjectSearch(
address: jobNames[index],
budget: budget[index],
jobNumber: jobNumbers[index],
major: majors[index],
name: jobNames[index],
pM: pms[index],
tech: techs[index]);
index++;
searchList.add(myProjects);
}
and also because when every time keyboard status change your FutureBuilder rebuild again do this:
var data = snapshot.data!;
var columns = data.columns;
var rows = data.rows;
jobNames = [];
jobNumbers = [];
techs = [];
pms = [];
address = [];
majors = [];
budget = [];
for (var item in rows!) {
var cells = item.cells;
for (var elements in cells!) {
...
}
}
define the future in the build and use it once like this:
..
Widget build(BuildContext context) {
Future<ProjectModel> futureProjects = fetchProjects() ;
...
Then remove it in the initState() and use it in the future builder like you've done. Refer to this and read more on when to use initSate() and when to use FutureBuilder()
About your Query search: Try removing the setState() in the search method
void search(String query) {
final suggestions = searchList.where((search) {
final projectName = search.name.toLowerCase();
final input = query.toLowerCase();
return projectName.contains(input);
}).toList();
}
}
I've been working on a project in part of it I needed to bring a list of data and do some filtering on it, some of those filters are just working fine but I've been facing that problem where the part of getting all the data when I press the button all retrieve all the items of the list and show them into listview.builder() with different Card shapes based on grouping similar data i.e a card designed for data.type[tests] & another card designed for data.type[offers] ..etc.
So when I press all button it shows only the first 4 items inside the listview + it doesn't show data in the card design that supposed to have base on it's group filtering.
here I'm getting the data from firestore
import 'package:cloud_firestore/cloud_firestore.dart';
class Test{
final String details;
final String name;
final String price;
final String type;
Test({
this.details,
this.name,
this.price,
this.type,
});
factory Test.fromDocument(DocumentSnapshot doc){
return Test(
details: doc.data()['details'],
name: doc.data()['name'],
price: doc.data()['price'],
type: doc.data()['type'],
);
}
}
..........................
import 'package:ilab/services/User.dart';
class Services {
final _db = FirebaseFirestore.instance.collection('tests');
// test list from snapshot
List<Test> _testsListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return Test(
details: doc.data()['details'] ?? '',
name: doc.data()['name'] ?? '',
price: doc.data()['price'] ?? '',
type: doc.data()['type'] ?? '');
}).toList();
}
// Get tests stream
Stream<List<Test>> get Tests {
return _db.snapshots().map(_testsListFromSnapshot);
}
my component starts here
List<String> alphabets = [
'all',
'a',
'b',
'c',
'd',
... etc
]
List<Test> filteredTests = List();
List<Test> tests = List();
Color color = KWhiteColor;
int Index;
#override
void initState() {
super.initState();
filteredTests = tests;
}
here is the code of giving a card desgin based on the type of data
// return different cardshape for different group of data
Widget _card(int index) {
if (filteredTests
.where((user) => user.type.toLowerCase().contains('باقة'))
.toList()
.isNotEmpty) {
return PackageCardDesign(
packageName: filteredTests[index].name,
price: '${filteredTests[index].price} YR',
details: filteredTests[index].details.toLowerCase(),
colour: Packgecolors[index],
icon: Icons.ac_unit_outlined,
type: filteredTests[index].type,
);
} else if (filteredTests
.where((user) => user.type.toLowerCase().contains('تحليل'))
.toList()
.isNotEmpty) {
return TestCardDesign(
colour: TestOffercolors[index],
testName: filteredTests[index].name,
details: filteredTests[index].details.toLowerCase(),
price: '${filteredTests[index].price} YR',
type: filteredTests[index].type,
);
} else if (filteredTests
.where((user) => user.type.toLowerCase().contains('عرض'))
.toList()
.isNotEmpty) {
return OfferCardDesign(
colour: TestOffercolors[index],
testName: filteredTests[index].name,
// details: filteredUsers[index].details.toLowerCase(),
price: '${filteredTests[index].price} %',
// type: filteredUsers[index].type,
);
}
}
here is the code of creating and printing the top three buttons
ReusableTestChip mainThreeButtonChip(
{#required String text, String buttonName, Function onTap}) {
return ReusableTestChip(
ontap: onTap,
cardChild: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: selectedButton == buttonName ? KWhiteColor : KInActiveColor,
fontSize: 18.0, //25.0,
fontFamily: 'Cairo-Italic',
fontWeight: FontWeight.w600,
),
),
colour: selectedButton == buttonName ? KInActiveColor : KWhiteColor,
);
}
// print Main Three Top Button method using for loop to iterate through loop of strings
List<ReusableTestChip> printMainThreeButtonMethod() {
List<ReusableTestChip> allButtons = [];
for (int i = 0; i < buttons.length; i++) {
String button = buttons[i];
var newItem = mainThreeButtonChip(
text: button,
onTap: () {
setState(() {
selectedButton = buttons[i];
if (buttons[i] == 'تحاليل') {
// setState(() {
// _card = offerList();
// });
filteredTests = tests
.where((u) => (u.type.toLowerCase().contains('تحليل')))
.toList();
} else if (buttons[i] == 'عروض') {
filteredTests = tests
.where((u) => (u.type.toLowerCase().contains('عرض')))
.toList();
} else if (buttons[i] == 'باقات') {
filteredTests = tests
.where((u) => (u.type.toLowerCase().contains('باقة')))
.toList();
}
});
},
buttonName: buttons[i],
);
allButtons.add(newItem);
}
return allButtons;
}
here is the code of creating and printing the all button
ReusableAlphabetChip alphabetChip(
{#required String text, String char, Function onTap}) {
return ReusableAlphabetChip(
ontap: onTap,
cardChild: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: selectedAlphabet == char ? KInActiveColor : KSecondaryColor,
fontSize: 18.0, //25.0,
fontFamily: 'Cairo-Italic',
fontWeight: FontWeight.w600,
),
),
colour: selectedAlphabet == char ? KWhiteColor : KInActiveColor,
);
}
// print all button
List<ReusableAlphabetChip> printAlphabetMethod() {
List<ReusableAlphabetChip> chars = [];
for (int i = 0; i < alphabets.length; i++) {
String char = alphabets[i];
var newItem = alphabetChip(
text: char,
onTap: () {
setState(() {
selectedAlphabet = alphabets[i];
if (alphabets[i] == 'الكل') {
filteredTests = tests;
// _foundUsers = _allUsers;
} else {
filteredTests = tests
.where((u) => (u.name.toLowerCase().startsWith(alphabets[i])))
.toList(); //json filter first filter && firebase second filter
// _foundUsers = _allUsers.where((u) => (u["name"].toLowerCase().startsWith(alphabets[i]))).toList();
}
});
},
char: alphabets[i],
);
chars.add(newItem);
}
return chars;
}
#override
Widget build(BuildContext context) {
tests = Provider.of<List<Test>>(context);
ScrollController scrollController = ScrollController(
initialScrollOffset: 10, // or whatever offset you wish
keepScrollOffset: true,
);
return SafeArea(
child: Scaffold(
appBar: AppBar(
toolbarHeight: 100,
title: Image.asset('images/logo.jpeg',
height: 100.0, alignment: Alignment.center),
),
drawer: AppDrawer(),
body: ListView(
shrinkWrap: true,
children: [
// applogo(),
Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.0),
Row(
// top filters
mainAxisAlignment: MainAxisAlignment.center,
children: printMainThreeButtonMethod(),
),
Container(
// get all list items
margin: EdgeInsets.symmetric(vertical: 4.0),
height: 50.0,
child: ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
children: printAlphabetMethod()),
),
SizedBox(
height: 390,
child: Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(10.0),
controller: scrollController,
scrollDirection: Axis.vertical,
itemCount: filteredUsers.length,
itemBuilder: (BuildContext context, int index) {
Index = index;
if (index < filteredTests.length) {
return Card(
child: Padding(
padding: EdgeInsets.all(10.0),
child:_card(Index)
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
// itemCount: filteredUsers.length + 1,
),
),
],
),
),
],
),
],
),
bottomNavigationBar: MyBottomBar(),
),
);
}
I hope I explained what I'm facing clearly, any help will be appreciated and thanks in advance.
I found out that the problem was in color's list. where I made a list of colors to allow ListView.builder prints Cards of data that will retrieve with different colors, it turns out that due to color's list is finite and when the ListView.builder reaches the end of it, it returns that error [RangeError (index): Invalid value: Not in inclusive range 0..3: 4], So I've made a change on my color's list so when it reaches the end of the list it start over from the beginning and printing new data using the specified colors, like this
Color selectedColour(index) {
Color c;
if (index % 4 == 0) c = Colors.cyan;
if (index % 4 == 1) c = Colors.blueGrey;
if (index % 4 == 2) c = Colors.blue;
if (index % 4 == 3) c = Color(0xFFea9999);
return c;
}
this is my previous color's list before changing it
var Paccolors = [
Colors.blue,
Colors.cyan,
Colors.blueGrey,
Colors.pink,
Colors.black45,
Colors.lightGreen,
Colors.green,
Colors.red
];
I have simple function which is calling data from firestore and filtering data. But issue is my futurebuilder keeps on loader situation (Data is called successfully i can see in console but now showing in future) I think its because my fucntion is calling in loop or something i have try to print something in my function which indicates me that my function is not stopping and thats why i think my futureBuilder keeps on loading.
My code
Future<List> getCustomerList() async {
print('calling');
String uUid1 = await storage.read(key: "uUid");
String uName1 = await storage.read(key: "uName");
String uNumber1 = await storage.read(key: "uNumber");
setState(() {
uUid = uUid1;
uName = uName1;
uNumber = uNumber1;
});
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Customers');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs
.where((element) => element['sellerUID'] == uUid)
.map((doc) => doc.data())
.toList();
double gGive = 0;
double gTake = 0;
double gCal = 0;
for (int i = 0; i < allData.length; i++) {
// print(allData[i]);
// print('give ${double.parse(allData[i]['give'].toString()) }');
// print('take ${double.parse(allData[i]['take'].toString()) }');
double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString()) >
0
? gGive += double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString())
: gTake += double.parse(allData[i]['give'].toString()) -
double.parse(allData[i]['take'].toString());
}
// print(gGive);
// print(gTake);
setState(() {
Gtake = gGive.toString().replaceAll("-", "");
Ggive = gTake.toString().replaceAll("-", "");
});
if (greenBox) {
var check = allData.where((i) => i['take'] > i['give']).toList();
return check;
} else if (redBox) {
var check = allData.where((i) => i['give'] > 1).toList();
return check;
} else {
return allData;
}
}
And my futureBuilder look like this
Expanded(
child: Container(
height: Height * 0.5,
child: FutureBuilder(
future: getCustomerList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
list = snapshot.data;
return SingleChildScrollView(
child: Column(
children: [
Container(
height: Height * 0.5,
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder:
(BuildContext context,
int index) {
var showThis = list[index]
['give'] -
list[index]['take'];
return list[index]
['customerName']
.toString()
.contains(searchString)
? GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CustomerData(
data: list[
index])),
);
},
child: Padding(
padding:
const EdgeInsets
.only(
left: 13,
right: 13),
child: Container(
decoration:
BoxDecoration(
border: Border(
top: BorderSide(
color: Colors
.grey,
width:
.5)),
),
child: Padding(
padding:
const EdgeInsets
.all(
13.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
child:
Text(
list[index]['customerName'][0]
.toString(),
style:
TextStyle(fontFamily: 'PoppinsBold'),
),
backgroundColor:
Color(0xffF7F9F9),
),
SizedBox(
width:
20,
),
Text(
list[index]['customerName']
.toString(),
style: TextStyle(
fontFamily:
'PoppinsMedium'),
),
],
),
Text(
'RS ${showThis.toString().replaceAll("-", "")}',
style: TextStyle(
fontFamily:
'PoppinsMedium',
color: list[index]['give'] - list[index]['take'] <
0
? Colors.green
: Colors.red),
),
],
),
),
),
),
)
: Container();
},
),
)
],
),
);
} else
return Center(
heightFactor: 1,
widthFactor: 1,
child: SizedBox(
height: 70,
width: 70,
child: CircularProgressIndicator(
strokeWidth: 2.5,
),
),
);
}),
),
),
I am damn sure its because futurebuilder keeps calling function which is returning data but because of keeps calling functions my Futurebuilder keeps showing loading.
You should not call setState inside the future that you are giving to the FutureBuilder.
The state actualization will cause the FutureBuilder to re-build. Meaning triggering the future again, and ... infinite loop !
I'm trying to make a word game. First of all, the index will be white. if user Click the correct answer then index will be to green color and go to next screen, also index will be white in next screen.. again if user click incorrect answer then index will be to red color and don't let go to the next page until user put correct answer...
i set a GridView in Swiper (Swiper took by importing,
import 'package:flutter_swiper/flutter_swiper.dart';).
and i want to show next index of Swiper after completing a logic in GridView. suppose, i have a long string list(array) and took four value string from this list(array) by random, this four values string set in GridView index.
Again i make a new string list(array) by this four value string and took a value from this new string list(array) by random, this single string set in Swiper. finally if user can select the Swiper index value to the GridView four index value correctly, then user can see next screen in Swiper. but output is not working properly. problem is - at first i set white color in GridView index, if it is correct answer should be green color in GridView index and incorrect will be red color in GridView index. here is my logic made it messy.
import 'package:flutter_swiper/flutter_swiper.dart';
import 'dart:math';
class GameWordRoute extends StatefulWidget {
#override
_GameWordRouteState createState() => _GameWordRouteState(); }
class _GameWordRouteState extends State<GameWordRoute> {
SwiperController _controller = SwiperController();
SwiperControl _control = SwiperControl(color: Colors.white);
double get _width => MediaQuery.of(context).size.width;
double get _height => MediaQuery.of(context).size.height;
bool inLastPage = true;
bool _answer = false;
List<Color> colorList = <Color>[Colors.white, Colors.white, Colors.white, Colors.white,];
List<String> gameButtonList = <String>[];
FlutterTts flutterTts = FlutterTts();
#override
Widget build(BuildContext context) {
var sizeDevice = MediaQuery.of(context).size;
final orientation = MediaQuery.of(context).orientation;
final double itemHeight = sizeDevice.width / 6;
final double itemWidth = sizeDevice.width / 2;
return Scaffold(
backgroundColor: Colors.purple, // white
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
color: Colors.lightBlueAccent,
)),
Expanded(
flex: 8,
child: Container(
color: Colors.cyan,
child: Swiper(
controller: _controller,
loop: false,
scrollDirection: Axis.horizontal,
itemCount: word_data.drink.length,
onIndexChanged: (value) {
if (value == word_data.drink.length - 1)
setState(() {
inLastPage = true;
});
else {
setState(() {
inLastPage = true; // false
});
}
},
itemBuilder: (BuildContext context, int index) {
gameButtonList.clear();
var fourValueRandom = new Random();
for (var i = 0; i < 4; i++) {
final fourGameBtnRandom = word_data.drink[fourValueRandom.nextInt(word_data.drink.length)];
gameButtonList.add(fourGameBtnRandom);
}
var oneValueRandom = new Random();
var oneValueRandomGet = gameButtonList[oneValueRandom.nextInt(gameButtonList.length)];
var wordDataReplace = oneValueRandomGet.replaceAll(" ", "_").toLowerCase();
return Container(
child: Column(
children: <Widget>[
Expanded(
flex: 8,
child: Container(
color: Colors.purple,
width: _width,
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Image.asset("asset/drink_images/" + wordDataReplace + ".png",
fit: BoxFit.contain,
),
),
)),
Expanded(
flex: 1,
child: Container(
color: Colors.yellow,
width: _width,
alignment: Alignment.center,
// "${categoryTitleArray[index]}"
child: Text("What is this?"),
)),
Expanded(
flex: 4,
child: Container(
color: Colors.yellow[200],
width: _width,
alignment: Alignment.center,
child: GridView.builder(
padding: EdgeInsets.all(8),
itemCount: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (orientation == Orientation.portrait) ? 2 : 4,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: (itemWidth / itemHeight),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
if (index == 0) {
if (gameButtonList[0] == oneValueRandomGet){
_answer = true;
inLastPage = false;
colorList[0] = Colors.green;
setState((){});
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
}else{
colorList[0] = Colors.red;
inLastPage = true;
setState((){});
}
} else if (index == 1) {
if (gameButtonList[1] == oneValueRandomGet){
_answer = true;
colorList[1] = Colors.green;
setState((){});
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
inLastPage = false;
}else{
colorList[1] = Colors.red;
inLastPage = true;
setState((){});
}
} else if (index == 2) {
if (gameButtonList[2] == oneValueRandomGet){
_answer = true;
colorList[2] = Colors.green;
setState((){});
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
inLastPage = false;
}else{
colorList[2] = Colors.red;
inLastPage = true;
setState((){});
}
} else if (index == 3) {
if (gameButtonList[3] == oneValueRandomGet){
_answer = true;
colorList[3] = Colors.green;
inLastPage = false;
setState((){});
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
}else{
colorList[3] = Colors.red;
inLastPage = true;
setState((){});
}
}
},
child: Container(
child: new Card(
elevation: 0,
color: colorList[index], //Colors.transparent,
child: Center(
child: Text(
"${gameButtonList[index]}",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
),
),
),
),
);
}),
)),
],
),
);
},
),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.lightBlueAccent,
)),
],
),
),
);
}
void _showCorrectAndIncorrectDialog(String _title, String _image, String _subTitle, Color _color){
showDialog(
context: context,
builder: (BuildContext context){
Future.delayed(Duration(milliseconds: 500), () {
Navigator.of(context).pop(true);
});
return AlertDialog(
title: Text(_title, textAlign: TextAlign.center, style: TextStyle(color: _color),),
content: Container(
height: _width/1.1,
child: Column(
children: <Widget>[
Expanded(
flex: 4,
child: Container(
// color: Colors.cyan[100],
child: Image.asset(_image,
fit: BoxFit.cover,
),
),
),
SizedBox(height: 8),
Expanded(
flex: 1,
child: Container(
color: Colors.cyan,
child: Center(
child: Text(
_subTitle,
style: TextStyle(
// fontSize: 24,
),
),
),
),
),
],
),
),
);
}
);
}
}
so first thing you should do is changing your onTap function in your gesture detector into a simpler code You shouldn't verify every single number for the index because the index is already that number
To be more clear when you call list[index] the index here is an integer so if the index==1 you're calling list[1] and if the index==5 you're calling list[5] you don't have to test if index==1 or something like that
so your code should be something like this
onTap: () async{
if (gameButtonList[index] == oneValueRandomGet){
_answer = true;
colorList[index] = Colors.green;
inLastPage = false;
setState((){});
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
}else{
colorList[index] = Colors.red;
inLastPage = true;
setState((){});
}
},
And next for the problem of testing if the answer is correct or not and color changing and going to next screen
First thing please move these lines in your item builder function into a function you can call from anywhere like this for example
void newQuestion(){
gameButtonList.clear();
var fourValueRandom = new Random();
for (var i = 0; i < 4; i++) {
final fourGameBtnRandom = word_data.drink[fourValueRandom.nextInt(word_data.drink.length)];
gameButtonList.add(fourGameBtnRandom);
}
oneValueRandomGet = gameButtonList[fourValueRandom.nextInt(gameButtonList.length)];
wordDataReplace = oneValueRandomGet.replaceAll(" ", "_").toLowerCase();
}
and you can call this question after calling your dialogAlert if you change the line when you call _showCorrectAndIncorrectDialog(...) into
_showCorrectAndIncorrectDialog("Congratulation", "asset/icon_images/ok_emoji.png", "Correct answer", Colors.green);
newQuestion() //**Add this line also**
Notes :
-Remember to declare the variables you need in your class so they get changed in newQuestion function
-First time you lunch the app the variables like " oneValueRandomGet " are gonna be null so you can't show any data, call oneQuestion() in your initState so that when launching the app you get your first question ready directly.
I hope all this doesn't confuse you I tried my best to simplify and give you the simplest edit and answer possible, please if you're still unable to fix your problem I would really advice you to try to rewrite your code and try to make it as simple as possible.
Thank you.
I'm facing issue with scrolling bar, the lists of data load at bottom rather than showing at top.I have made code dynamic which is loading according to conditions. I'm new flutter I have written code as from different side and still learning, If you want to give suggest me to written in better way please go ahead let me know the change where I can do.
class EventsPageState extends State<EventsPage>
with SingleTickerProviderStateMixin {
int theirGroupValue = 0;
List data = List();
String url = 'https://e19f7c9d.ngrok.io/api/events/westernaf/packages';
Future<String> makeRequest() async {
var response = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var extractdata = json.decode(response.body);
data = extractdata["result"];
});
for (var i = 0; i < data.length; i++) {
var startTime = data[i]['start_date'].split('T')[0];
var endTime = data[i]['end_date'].split('T')[0];
final todayDate = DateTime.now();
final pkgStartDate = DateTime.parse(startTime);
final pkgEndDate = DateTime.parse(endTime);
final difDate = todayDate.isAfter(pkgStartDate);
final difDate2 = todayDate.isBefore(pkgEndDate);
if (difDate && difDate2 == true) {
if (data[i]['regTypeId'] == 1) {
this.theirGroupValue = 0;
} else if (data[i]['regTypeId'] == 2) {
this.theirGroupValue = 1;
} else if (data[i]['regTypeId'] == 3) {
this.theirGroupValue = 2;
} else {
this.theirGroupValue = 0;
}
}
}
}
final Map<int, Widget> logoWidgets = const <int, Widget>{
0: Text('Early Bird'),
1: Text('General'),
2: Text('Onsite'),
};
List<bool> inputs = new List<bool>();
#override
void initState() {
super.initState();
this.makeRequest();
setState(() {
for (int i = 0; i < 35; i++) {
inputs.add(false);
}
});
}
void ItemChange(bool val, int index) {
setState(() {
inputs[index] = val;
});
}
static Widget giveCenter(String ListView) {
return new Card(
// child Container(
child: Text(
"Text: $ListView",
style: TextStyle(color: Colors.blue, fontSize: 20.0),
// )
),
);
}
void _Register() {
Navigator.push(
context, new MaterialPageRoute(builder: (context) => LandingScreen()));
}
#override
Widget _listingShow(i) {
if (data[i]['regTypeId'] == 1) {
var startTime = data[i]['start_date'].split('T')[0];
var endTime = data[i]['end_date'].split('T')[0];
final todayDate = DateTime.now();
final pkgStartDate = DateTime.parse(startTime);
final pkgEndDate = DateTime.parse(endTime);
final difDate = todayDate.isAfter(pkgStartDate);
final difDate2 = todayDate.isBefore(pkgEndDate);
if (difDate && difDate2 == true) {
return new Row(
children: <Widget>[
Expanded(
child: Text(data[i]["packageName"]),
),
Expanded(
child: Text((data[i]["price"]).toString()),
),
Expanded(
child: Checkbox(
value: inputs[i],
onChanged: (bool val) {
ItemChange(val, i);
},
),
),
],
);
}
}
}
#override
Widget _listingShow2(i) {
if (data[i]['regTypeId'] == 2) {
var startTime = data[i]['start_date'].split('T')[0];
var endTime = data[i]['end_date'].split('T')[0];
final todayDate = DateTime.now();
final pkgStartDate = DateTime.parse(startTime);
final pkgEndDate = DateTime.parse(endTime);
final difDate = todayDate.isAfter(pkgStartDate);
final difDate2 = todayDate.isBefore(pkgEndDate);
if (difDate && difDate2 == true) {
return new Row(
children: <Widget>[
Expanded(
child: Text(data[i]["packageName"]),
),
Expanded(
child: Text((data[i]["price"]).toString()),
),
Expanded(
child: Checkbox(
value: inputs[i],
onChanged: (bool val) {
ItemChange(val, i);
},
),
),
],
);
}
}
}
#override
Widget _listingShow3(i) {
if (data[i]['regTypeId'] == 3) {
var startTime = data[i]['start_date'].split('T')[0];
var endTime = data[i]['end_date'].split('T')[0];
final todayDate = DateTime.now();
final pkgStartDate = DateTime.parse(startTime);
final pkgEndDate = DateTime.parse(endTime);
final difDate = todayDate.isAfter(pkgStartDate);
final difDate2 = todayDate.isBefore(pkgEndDate);
if (difDate && difDate2 == true) {
return new Row(
children: <Widget>[
Expanded(
child: Text(data[i]["packageName"]),
),
Expanded(
child: Text((data[i]["price"]).toString()),
),
Expanded(
child: Checkbox(
value: inputs[i],
onChanged: (bool val) {
ItemChange(val, i);
},
),
),
],
);
}
}
}
Widget build(BuildContext context) {
List<Widget> bodies = [
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, i) {
return ListTile(
title: _listingShow(i),
);
},
),
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, i) {
return ListTile(
title: _listingShow2(i),
);
},
),
new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, i) {
return ListTile(
title: _listingShow3(i),
);
},
),
];
return Scaffold(
body: bodies[this.theirGroupValue],
appBar: AppBar(
elevation: 2.0,
backgroundColor: Colors.white,
centerTitle: true,
title: Text(
'Select conference Package',
style: TextStyle(color: Colors.black),
),
bottom: PreferredSize(
preferredSize: Size(double.infinity, 45.0),
child: Padding(
padding: EdgeInsets.only(top: 5.0, bottom: 10.0),
child: Row(
children: <Widget>[
SizedBox(
width: 15.0,
),
Expanded(
child: CupertinoSegmentedControl(
groupValue: this.theirGroupValue,
onValueChanged: (changeFromGroupValue) {
setState(() {
theirGroupValue = this.theirGroupValue;
});
},
children: logoWidgets,
),
),
],
),
),
),
),
);
}
}
I have attached the pic above, If you want to c all code I will push all the code.If needed , all I want to c listing at the top
in bodies, you define the ListViews like this:
ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, i) {
return ListTile(
title: _listingShow(i),
);
},
),
And this it listingShow, which returns the widget you use as the title property of your ListView.
Widget _listingShow3(i) {
if (data[i]['regTypeId'] == 3) {
// construct and return the widget
}
}
The problem is, your ListView has always a fixed number of items which is data.length, but in your function, you only return a widget if the data matches a certain type (if (data[i]['regTypeId'] == 3)), else, you return nothing. But the ListTile is always there, it just doesn't show anything.
My suggestion is to divide data into 3 separate lists, you can see how I did it here, it's probably not optimised, you'll need to figure out a better way.