I am saving toggle button data and adding the toggle button
data list through provider in the list of Flatdetail1. the code
is
class FormDetail1 extends StatefulWidget {
const FormDetail1({Key? key, required this.onNext}) : super(key: key);
final VoidCallback onNext;
#override
State<FormDetail1> createState() => _FormDetail1State();
}
class _FormDetail1State extends State<FormDetail1> {
List<bool> isSelected = [false, false, false];
List<bool> isSelected1 = [false, false, false, false];
List<bool> isSelected2 = [false, false, false];
String city = '';
String city1 = '';
String city2 = '';
var saveFlatDetail = FlatDetail1(
id1: '',
id2: '',
id3: '',
);
void saveData() {
Provider.of<BasicInfo>(context, listen: false).addDetails1(saveFlatDetail);
print(saveFlatDetail.id1);
print(saveFlatDetail.id2);
print(saveFlatDetail.id3);
}
#override
Widget build(BuildContext context) {
//final flatDetail = Provider.of<FlatDetail1>(context);
print('hello sir ');
return Scaffold(
appBar: AppBar(
title: Text('Flutter App'),
),
body: Column(
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
children: [
ToggleButtons(
isSelected: isSelected,
children: [
Text('India'),
Text('Nepal'),
Text('China'),
],
onPressed: (int newIndex) {
setState(() {
for (int i = 0; i < isSelected.length; i++) {
isSelected[i] = i == newIndex;
}
if (newIndex == 0) {
city = 'India';
} else if (newIndex == 1) {
city = 'Nepal';
} else if (newIndex == 2) {
city = 'china';
}
saveFlatDetail = FlatDetail1(
id1: city,
id2: saveFlatDetail.id2,
id3: saveFlatDetail.id3,
);
// flatDetail.updateValue(city);
// print(city);
});
},
),
ToggleButtons(
isSelected: isSelected1,
children: [
Text('UP'),
Text('MP'),
Text('Bihar'),
Text('Odisha'),
],
onPressed: (int newIndex1) {
setState(() {
for (int i = 0; i < isSelected1.length; i++) {
isSelected1[i] = i == newIndex1;
}
if (newIndex1 == 0) {
city1 = 'UP';
} else if (newIndex1 == 1) {
city1 = 'MP';
} else if (newIndex1 == 2) {
city1 = 'Bihar';
} else if (newIndex1 == 3) {
city1 = 'Odisha';
}
saveFlatDetail = FlatDetail1(
id1: saveFlatDetail.id1,
id2: city1,
id3: saveFlatDetail.id3,
);
// flatDetail.updateValue(city1);
// print(city1);
});
},
),
Container(
color: Colors.red[50],
padding: EdgeInsets.all(8),
child: ToggleButtons(
isSelected: isSelected2,
children: [
Text('Patna'),
Text('Delhi'),
Text('Lucknow'),
],
onPressed: (int newIndex2) {
setState(() {
for (int i = 0; i < isSelected2.length; i++) {
isSelected2[i] = i == newIndex2;
}
if (newIndex2 == 0) {
city2 = 'Patna';
} else if (newIndex2 == 1) {
city2 = 'Delhi';
} else if (newIndex2 == 2) {
city2 = 'Lucknow';
}
saveFlatDetail = FlatDetail1(
id1: saveFlatDetail.id1,
id2: saveFlatDetail.id2,
id3: city2,
);
});
},
),
),
SizedBox(height: 100),
GestureDetector(
onTap: () {
saveData();
if (city == '' || city1 == '' || city2 == '') {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Please select all',
),
duration: Duration(milliseconds: 400),
),
);
} else {
widget.onNext();
}
},
child: Container(
height: 40,
width: 125,
color: Colors.blue,
child: Text('Widget Playground!'),
),
),
SizedBox(
height: 40,
),
],
),
),
),
],
),
);
}
}
Now i want to access id1,id2 and id3 in a text field but not
able to access it, here is the code below
class BasicInfo extends ChangeNotifier {
List<FlatDetail1> _details1 = [
FlatDetail1(
id1: '',
id2: '',
id3: '',
),
];
List<FlatDetail1> get details1 {
return [..._details1];
}
void addDetails1(FlatDetail1 flatDetail) {
final newFlatDetail = FlatDetail1(
id1: flatDetail.id1,
id2: flatDetail.id2,
id3: flatDetail.id3,
);
_details1.add(newFlatDetail);
notifyListeners();
}
}
When i use Text(name.details1['id1']), it shows error i also used name.details[index].id1 but still no use.Please tell me how to access the id1,id2,id3.
class FormDetail2 extends StatefulWidget {
const FormDetail2({Key? key, required this.onNext}) : super(key: key);
final VoidCallback onNext;
#override
State<FormDetail2> createState() => _FormDetail2State();
}
class _FormDetail2State extends State<FormDetail2> {
#override
Widget build(BuildContext context) {
final name = Provider.of<BasicInfo>(context);
//print(name.id1);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(name.details1[].id1),
Text(''),
Text(''),
],
),
),
);
}
}
this is the code and i wwant to access id1,id2,id3 in
FormDetail2 page
Related
Hi i have a riveanimation that changes flower when the user has checkin, however the page of the animation would not change state once build as it is in a bottomnavigationbar. Is there anyway to reload the page to change the state of the animation. I only want to reload only one page and maintain the others.
This is the animation picture, it should change the leaf to green
here is my bottomnavigationbar page
class bottomnavbar extends StatefulWidget {
const bottomnavbar ({Key? key}) : super(key: key);
#override
_bottomnavbarState createState() => _bottomnavbarState();
}
class _bottomnavbarState extends State<bottomnavbar> {
double screenHeight = 0;
double screenWidth = 0;
String currentDate = DateFormat('yyyy-MM-dd').format(DateTime.now());
Color primary = const Color(0xffeef444c);
int currentIndex = 0;
List<IconData> navigationIcons = [
FontAwesomeIcons.personPraying,
FontAwesomeIcons.leaf,
FontAwesomeIcons.dice,
FontAwesomeIcons.person,
];
#override
Widget build(BuildContext context) {
screenHeight = MediaQuery.of(context).size.height;
screenWidth = MediaQuery.of(context).size.width;
print("Current save date is ${Provider.of<checkinlist>(context, listen: false).checkDate()}");
if( Provider.of<checkinlist>(context, listen: false).checkDate() == 'not set'){
Provider.of<checkinlist>(context, listen: false).saveDatenow();
}
else if (Provider.of<checkinlist>(context, listen: false).checkDate() != currentDate ){
Provider.of<checkinlist>(context, listen: false).overwriteSaveDate();
Provider.of<checkinlist>(context, listen: false).dailyreset();
}
else if (Provider.of<checkinlist>(context, listen: false).checkDate() == currentDate ){
}
return Scaffold(
backgroundColor: Colors.blueGrey[900],
body: IndexedStack(
index: currentIndex,
children: const [
mainmenu(),
FlowerGarden(),
Gamespage(),
ProfilePage(),
],
),
bottomNavigationBar: Container(
height: 70,
margin: const EdgeInsets.only(
left: 12,
right: 12,
bottom: 24,
),
decoration: const BoxDecoration(
color: Colors.lime,
borderRadius: BorderRadius.all(Radius.circular(40)),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(2, 2),
),
],
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(40)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
for(int i = 0; i < navigationIcons.length; i++)...<Expanded>{
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
currentIndex = i;
});
},
child: Container(
height: screenHeight,
width: screenWidth,
color: Colors.deepPurple[900],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
navigationIcons[i],
color: i == currentIndex ? primary : Colors.white60,
size: i == currentIndex ? 30 : 26,
),
i == currentIndex ? Container(
margin: const EdgeInsets.only(top: 6),
height: 3,
width: 22,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(40)),
color: primary,
),
) : const SizedBox(),
],
),
),
),
),
),
}
],
),
),
),
);
}
}
And this is my riveanimation page
class FlowerGarden extends StatefulWidget{
const FlowerGarden({Key? key,
}) : super(key: key);
#override
_SimpleFlowerGardenState createState() => _SimpleFlowerGardenState();
}
final myCoordinates = Coordinates(3.139003, 101.686855);
// Replace with your own location lat, lng.
final params = CalculationMethod.karachi.getParameters();
final prayerTimes = PrayerTimes.today(myCoordinates, params);
class _SimpleFlowerGardenState extends State<FlowerGarden>{
SMITrigger? _SubuhEarly;
SMITrigger? _SubuhLate;
SMITrigger? _ZohorEarly;
SMITrigger? _ZohorLate;
SMITrigger? _AsarEarly;
SMITrigger? _AsarLate;
SMITrigger? _MaghribEarly;
SMITrigger? _MaghribLate;
SMITrigger? _IsyakEarly;
SMITrigger? _IsyakLate;
SMITrigger? _solatPerfect;
SMITrigger? _solatImperfect;
Artboard? _riveArtboard;
String? message;
#override
void initState() {
super.initState();
// Load the animation file from the bundle, note that you could also
// download this. The RiveFile just expects a list of bytes.
rootBundle.load('android/assets/lotus.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'State Machine ',onStateChange: _onStateChange);
if (controller != null) {
artboard.addController(controller);
_SubuhEarly = controller.findInput<bool>('Subuh Early') as SMITrigger;
_SubuhLate = controller.findInput<bool>('Subuh Late') as SMITrigger;
_ZohorEarly = controller.findInput<bool>('Zohor Early') as SMITrigger;
_ZohorLate = controller.findInput<bool>('Zohor late') as SMITrigger;
_AsarEarly = controller.findInput<bool>('Asar Early') as SMITrigger;
_AsarLate = controller.findInput<bool>('Asar Late') as SMITrigger;
_MaghribEarly = controller.findInput<bool>('Maghrib early') as SMITrigger;
_MaghribLate = controller.findInput<bool>('Maghrib late') as SMITrigger;
_IsyakEarly = controller.findInput<bool>('Ishak early') as SMITrigger;
_IsyakLate = controller.findInput<bool>('Ishak late') as SMITrigger;
_solatPerfect = controller.findInput<bool>('Solat Perfect') as SMITrigger;
_solatImperfect = controller.findInput<bool>('Solat Imperfect') as SMITrigger;
}
setState(() => _riveArtboard = artboard);
},
);
}
void _onStateChange(
String stateMachineName,
String stateName,
) =>
setState(
() => print('State Changed in $stateMachineName to $stateName') ,
);
void flowercheck(){
if (Provider.of<checkinlist>(context, listen: false).getSubuh() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getZohor() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getAsar() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getMaghrib() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getIsyak() == 2){
_solatImperfect?.fire();
}
else {
_solatPerfect?.fire();
}
}
void leafcheck(){
int subuh = Provider.of<checkinlist>(context, listen: false).getSubuh();
int zohor = Provider.of<checkinlist>(context, listen: false).getZohor();
int asar = Provider.of<checkinlist>(context, listen: false).getAsar();
int maghrib = Provider.of<checkinlist>(context, listen: false).getMaghrib();
int isyak = Provider.of<checkinlist>(context, listen: false).getIsyak();
double health = Provider.of<solatPoints>(context, listen: false).getHealth();
switch(subuh){
case 1:
_SubuhEarly?.fire();
break;
case 2:
_SubuhLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(zohor){
case 1:
_ZohorEarly?.fire();
break;
case 2:
_ZohorLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(asar){
case 1:
_AsarEarly?.fire();
break;
case 2:
_AsarLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(maghrib){
case 1:
_MaghribEarly?.fire();
break;
case 2:
_MaghribLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(isyak){
case 1:
_IsyakEarly?.fire();
break;
case 2:
_IsyakLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
default:
break;
}
}
#override
Widget build(BuildContext context) {
setState(() {
leafcheck();
});
return Scaffold(
backgroundColor: Colors.blueGrey[900],
appBar: AppBar(
backgroundColor: Colors.deepPurple[900],
title: const Text('Bunga Solat'),
),
body: Center(
child: GestureDetector(
child: _riveArtboard == null
? const SizedBox()
: Stack(
children: [
Positioned.fill(
child: Rive(
artboard: _riveArtboard!,
),
),
]
),
),
)
);
}
}
class ClassProfile extends State<ClassProfile> with AutomaticKeepAliveClientMixin {
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => false;
#override
Widget build(BuildContext context) {
if(wantKeepAlive) {
super.build(context);
}
.
.
.
.
first set - with AutomaticKeepAliveClientMixin
then, override wantkeepalive variable
at last, defined super.build with if condition in your main widget builder function
In future if you want to persist state of your class then set wantKeepAlive => true
I have a project that I have been working on and have been stumped on this for the last month and can't seem to figure out how to solve this. I have a screen that shows a list of contacts and a search bar that is used to show specific contacts. I have been trying to add a filter feature where the use clicks on filters and chooses a couple options that will show. The way the button widget works I couldn't get it to be in the same class so it's in another file and being used. Here is the code, I am still learning Flutter so if you have an recommendations to fix other bits of the code I would love to hear it.
Here is my first file with the screen that include the search bar and the the major chunk of code.
import 'package:myapp/widgets/cards.dart';
import 'package:flutter/material.dart';
import 'package:myapp/widgets/icon_buttons.dart';
class ResourceScreen extends StatefulWidget {
const ResourceScreen({Key? key}) : super(key: key);
#override
_ResourceScreen createState() => _ResourceScreen();
}
class _ResourceScreen extends State<ResourceScreen> {
bool filtered = false;
final TextEditingController _filter = TextEditingController();
String _searchText = "";
Widget customSearchBar = const Text('Resources');
Icon customIcon = const Icon(Icons.search);
List filterCardInformation = [],
tempList = [],
filterButtons = [],
cardInformation = phoneList;
void _getCards() async {
setState(() {
filterCardInformation = cardInformation;
});
}
//TODO There seems to be some lag when the list goes from nothing to the whole list. Not sure what is impacting this or how hard it could be on the users experience as list grows
_ResourceScreen() {
_filter.addListener(() {
filterCardInformation =
cardInformation; // Might need in every else statement, not sure if there is a memory leak with it being here
//Had "tempList = []" only here and caused the program to run out of memory. This is why I believe this could be an issue
if (_filter.text.isEmpty) {
if (filtered) {
setState(() {
_searchText = "";
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if (filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
});
} else {
setState(() {
_searchText = "";
});
}
} else {
if (filtered) {
setState(() {
_searchText = _filter.text;
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if (filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
});
} else {
setState(() {
_searchText = _filter.text;
});
}
}
});
}
//This is where the list is built as input comes in from the keyboard
Widget _buildList(context) {
if (_searchText.isNotEmpty && !filtered) {
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if ((filterCardInformation[i].title.toString())
.toLowerCase()
.contains(_searchText.toLowerCase())) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
} else if (_searchText.isNotEmpty && filtered) {
tempList = [];
for (int i = 0; i < filterCardInformation.length; i++) {
if ((filterCardInformation[i].title.toString())
.toLowerCase()
.contains(_searchText.toLowerCase()) &&
filterPhrase.contains(filterCardInformation[i].metadata)) {
tempList.add(filterCardInformation[i]);
}
}
filterCardInformation = tempList;
}
return ListView.builder(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: filterCardInformation.length,
itemBuilder: (BuildContext context, int index) {
return MyExpandingCard(
title: filterCardInformation[index].title,
information: filterCardInformation[index].information,
phoneList: filterCardInformation[index].phoneList,
website: filterCardInformation[index].website,
contactList: filterCardInformation[index].contactList,
);
},
);
}
#override
void initState() {
_getCards();
super.initState();
}
//Trigger filter list to show from the bottom
Widget filterButton(double height) {
return ElevatedButton.icon(
onPressed: () => {filterBottomSheet(height)},
//TODO Change out this icon for something different
icon: const Icon(Icons.filter),
label: const Text("Filter"),
);
}
//Row with buttons for bottomSheet
Widget doubleBottomButtonRow(String dataOne, dataTwo) {
Icon firstPersonalIcon = const Icon(Icons.add),
secondPersonalIcon = const Icon(Icons.add);
void _changeFirstIcon() {
setState(() {
if (firstPersonalIcon.icon == Icons.add) {
firstPersonalIcon = const Icon(Icons.check);
} else {
firstPersonalIcon = const Icon(Icons.add);
}
});
}
void _changeSecondIcon() {
setState(() {
if (secondPersonalIcon.icon == Icons.add) {
secondPersonalIcon = const Icon(Icons.check);
} else {
secondPersonalIcon = const Icon(Icons.add);
}
});
}
return Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
//TODO issue here with icon not changing on click. I am not sure exactly why, I just need to figure out how to trigger feedback from when button in different file is used
onPressed: () {
_changeFirstIcon();
},
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(dataOne),
icon: firstPersonalIcon),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
onPressed: () {
_changeSecondIcon();
},
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(dataTwo),
icon: secondPersonalIcon),
),
],
);
}
Widget singleBottomButtonRow(String dataOne) {
return Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IButton(firstName: dataOne)),
],
);
}
//BottomSheet filter
Future filterBottomSheet(double height) {
return showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return SizedBox(
height: height,
child: SingleChildScrollView(
child: Column(
children: [
doubleBottomButtonRow(
"filter 1", "filter 2"),
],
),
));
});
}
//TODO Design this
//Widget that keeps track of how many visible options are shown
#override
Widget build(BuildContext context) {
//Grab size of screan for the bottom sheet
Size size = MediaQuery.of(context).size;
double height = size.height;
return Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xff1c1d4b),
title: customSearchBar,
automaticallyImplyLeading: true,
actions: [
IconButton(
onPressed: () {
setState(() {
if (customIcon.icon == Icons.search) {
customIcon = const Icon(Icons.cancel);
//search bar
customSearchBar = ListTile(
leading: const Icon(
Icons.search,
color: Colors.white,
size: 28,
),
title: TextField(
controller: _filter,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Type in search...',
hintStyle:
TextStyle(color: Colors.white, fontSize: 18),
border: InputBorder.none,
),
style: const TextStyle(color: Colors.white),
),
);
//Reset the variables
} else {
customIcon = const Icon(Icons.search);
customSearchBar = const Text('Resources');
}
});
},
icon: customIcon)
],
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Row(
children: [
filterButton(height * .30),
],
),
Expanded(
child: SingleChildScrollView(
physics: const ScrollPhysics(),
child: _buildList(context),
),
),
],
),
),
);
}
}
//TODO This is not working but what I was thinking of how the function would look
List filterPhrase = [];
void _addPhrase(String phrase) {
filterPhrase.add(phrase);
}
void _deletePhrase(String phrase) {
filterPhrase.remove(phrase);
}
List phoneList = [
const MyExpandingCard(
title: 'example 1',
phoneList: ['123456789'],
metadata: "filter 1",
),
const MyExpandingCard(
title: 'example 2',
phoneList: ['123456789'],
metadata: "filter 2",
),
];
And here is the other file that I am using for the button in the filter
import 'package:flutter/material.dart';
import 'package:myapp/secondary_screens/resource_screen.dart';
class IButton extends StatefulWidget {
final String firstName;
const IButton({Key? key, required this.firstName}) : super(key: key);
#override
State<IButton> createState() => _IButtonState();
}
class _IButtonState extends State<IButton> {
Icon personalIcon = const Icon(Icons.add);
void _changeIcon() {
setState(() {
if (personalIcon.icon == Icons.add) {
personalIcon = const Icon(Icons.check);
//add to filter list
} else {
personalIcon = const Icon(Icons.add);
//remove from filter list
}
});
}
#override
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: _changeIcon,
icon: personalIcon,
style: ElevatedButton.styleFrom(shape: const StadiumBorder()),
label: Text(widget.firstName),
);
}
}
If you have any resources to improve skills in flutter I would love to hear them. I am always looking to improve.
Thank you all
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'm rather new to development, and started off with web dev, though now work with Flutter for about 2 months now, which means I still learn tons - so please, bear with me.
I'm currently working on a drag & drop periodic table.
I already got a working version where the player can drop an element at the correct position (only one DragTarget accepts the Draggable). However, I now want to make an advanced version, where each DragTarget accepts each Draggable and shows some information of the dropped element.
My problem is:
I can drop the DraggableElementTile on each "empty" DragTarget (as I want to), but when I hover over one of the DragTargets that already "have data", it changes the text to the one that was added last (to a different DragTarget). So the data of the Draggable is not "tied" to the DragTarget, but I cannot find out how to solve it.
I know that further, in this code, the data of the next element in line is shown in the DragTarget upon onAccept. It doesn't happen with my full code, maybe I deleted something here. Or it points someone to the solution?
As a side note: Eventually, there'll be a check, if the element has the correct position in the table or not, so the DragTarget needs to carry the information of the correct setup (as in my initial version).
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable & DragTarget',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List elementData = [
{
"key": "1x1",
"atomicNumber": 1,
"element": "Wasserstoff",
"symbol": "H",
"group": 1,
"period": 1,
"accepting": false,
"successfulDrop": false,
"correctDrop": false
},
{
"key": "1x2",
"atomicNumber": 3,
"element": "Lithium",
"symbol": "Li",
"group": 1,
"period": 2,
"accepting": false,
"successfulDrop": false,
"correctDrop": false
},
{
"key": "1x3",
"atomicNumber": 11,
"element": "Natrium",
"symbol": "Na",
"group": 1,
"period": 3,
"accepting": false,
"successfulDrop": false,
"correctDrop": false
},
{
"key": "1x4",
"atomicNumber": 19,
"element": "Kalium",
"symbol": "K",
"group": 1,
"period": 4,
"accepting": false,
"successfulDrop": false,
"correctDrop": false
}
];
int j = 0;
List<Widget> _elements;
List shuffledElements;
int tableRows = 4;
int tableCols = 1;
String key;
int index;
var tmpElement;
bool accepting = false;
bool successfulDrop = false;
bool correctDrop = false;
List shuffleElements() {
var random = Random();
shuffledElements = List.from(elementData);
for (var i = shuffledElements.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = shuffledElements[i];
shuffledElements[i] = shuffledElements[n];
shuffledElements[n] = temp;
}
return shuffledElements;
}
void nextElement() {
setState(() {
if (j < shuffledElements.length - 1) {
j++;
} else {}
});
}
List<Widget> getElements() {
if (_elements != null) {
return _elements;
}
_elements = [];
for (var j = 0; j < tableCols; j++) {
for (var i = 0; i < tableRows; i++) {
key = '${j + 1}x${i + 1}';
index = elementData.indexWhere((e) => e.containsValue(key));
if (!index.isNegative) {
tmpElement = elementData[index];
_elements.add(elementDragTarget(tmpElement));
} else {}
}
}
return _elements;
}
#override
void initState() {
shuffleElements();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white38,
appBar: AppBar(title: Text('Drag and Drop')),
body: Column(
children: [
Container(
color: Colors.white38,
height: MediaQuery.of(context).size.height * 0.7,
child: GridView.count(
crossAxisCount: tableRows,
scrollDirection: Axis.horizontal,
children: getElements(),
),
),
Draggable(
data: shuffledElements[j],
child: DraggableElementTile(
shuffledElements: shuffledElements,
j: j,
),
feedback: DraggableElementTile(
shuffledElements: shuffledElements,
j: j,
),
),
],
),
);
}
//problem: if I hover over tiles that already show data
// it changes to last data
Widget elementDragTarget(tmpElement) {
return DragTarget(
onWillAccept: (data) {
if (tmpElement['successfulDrop'] == true) {
tmpElement['accepting'] = false;
return false;
} else {
setState(() {
tmpElement['accepting'] = true;
});
return true;
}
},
onAccept: (data) {
setState(() {
tmpElement['successfulDrop'] = true;
if (shuffledElements[j]["atomicNumber"] ==
tmpElement['atomicNumber']) {
tmpElement['correctDrop'] = true;
tmpElement['accepting'] = false;
} else {
tmpElement['correctDrop'] = false;
tmpElement['accepting'] = false;
}
});
nextElement();
},
onLeave: (data) {
setState(() {
tmpElement['accepting'] = false;
});
return false;
},
builder: (context, acceptedData, rejectedData) {
return buildElementTileInGrid(tmpElement);
},
);
}
//show in grid onAccept
Container buildElementTileInGrid(tmpElement) {
accepting = tmpElement['accepting'];
successfulDrop = tmpElement['successfulDrop'];
correctDrop = tmpElement['correctDrop'];
return Container(
padding: EdgeInsets.all(4),
margin: EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
width: 4,
color: accepting == true ? Colors.teal : Colors.transparent,
),
color: Colors.white38,
),
child: successfulDrop == true
? Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(shuffledElements[j]['atomicNumber'].toString()),
Text(shuffledElements[j]['symbol']),
],
)
: Container(),
);
}
}
//draggable
class DraggableElementTile extends StatelessWidget {
const DraggableElementTile({
Key key,
#required this.shuffledElements,
#required this.j,
}) : super(key: key);
final List shuffledElements;
final int j;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.teal,
padding: EdgeInsets.all(12),
margin: EdgeInsets.all(8),
height: 100,
width: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
shuffledElements[j]['symbol'],
style: TextStyle(fontSize: 14),
),
Text(
shuffledElements[j]['element'],
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14),
),
],
),
);
}
}
Thankful for any helpful ideas.
edit: I think I'd need to save the data I want to show in a new list or so, but still hit a wall when I try to implement it.
I managed to make it work by creating a deep copy (called it elementDataCopy) of the initial elementData using a model in getElements(). I could then overwrite the data with the data of the dropped element from the Draggable in onAccept of the DragTarget, leading to the expected behaviour:
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable & DragTarget',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class ElementModel {
ElementModel({
this.key,
this.atomicNumber,
this.element,
this.symbol,
this.group,
this.period,
this.droppedKey,
this.accepting,
this.successfulDrop,
this.correctDrop,
this.answer,
});
String key;
int atomicNumber;
String element;
String symbol;
int group;
int period;
String droppedKey = '';
bool accepting = false;
bool successfulDrop = false;
bool correctDrop = false;
String answer = '';
}
class _MyHomePageState extends State<MyHomePage> {
List elementData = [
{
"key": "1x1",
"atomicNumber": 1,
"element": "Wasserstoff",
"symbol": "H",
"group": 1,
"period": 1
},
{
"key": "1x2",
"atomicNumber": 3,
"element": "Lithium",
"symbol": "Li",
"group": 1,
"period": 2
},
{
"key": "1x3",
"atomicNumber": 11,
"element": "Natrium",
"symbol": "Na",
"group": 1,
"period": 3
},
{
"key": "1x4",
"atomicNumber": 19,
"element": "Kalium",
"symbol": "K",
"group": 1,
"period": 4
}
];
int j = 0;
List<Widget> _elements;
List shuffledElements;
int tableRows = 4;
int tableCols = 1;
String key;
int index;
var tmpElement;
bool accepting = false;
bool successfulDrop = false;
bool correctDrop = false;
var droppedItem;
List droppedItems = [];
int droppedItemIndex;
List shuffledElementsCopy;
List elementDataCopy;
List shuffleElements() {
var random = Random();
shuffledElements = List.from(elementData);
for (var i = shuffledElements.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = shuffledElements[i];
shuffledElements[i] = shuffledElements[n];
shuffledElements[n] = temp;
}
return shuffledElements;
}
void nextElement() {
setState(() {
if (j < shuffledElements.length - 1) {
j++;
} else {}
});
}
List<Widget> getElements() {
if (_elements != null) {
return _elements;
}
elementDataCopy = elementData
.map((element) => ElementModel(
key: element['key'],
atomicNumber: element['atomicNumber'],
element: element['element'],
symbol: element['symbol'],
group: element['group'],
period: element['period'],
accepting: element['accepting'],
successfulDrop: element['successfulDrop'],
correctDrop: element['correctDrop'],
droppedKey: element['droppedKey'],
answer: element['answer'],
))
.toList();
_elements = [];
for (var c = 0; c < tableCols; c++) {
for (var r = 0; r < tableRows; r++) {
key = '${c + 1}x${r + 1}';
index = elementDataCopy.indexWhere((e) => e.key.contains(key));
if (!index.isNegative) {
tmpElement = elementDataCopy[index];
_elements.add(elementDragTarget(tmpElement));
} else {}
}
}
return _elements;
}
#override
void initState() {
shuffleElements();
shuffledElementsCopy = List.from(shuffledElements);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white38,
appBar: AppBar(title: Text('Drag and Drop')),
body: Column(
children: [
Container(
color: Colors.white38,
height: MediaQuery.of(context).size.height * 0.7,
child: GridView.count(
crossAxisCount: tableRows,
scrollDirection: Axis.horizontal,
children: getElements(),
),
),
Draggable(
data: shuffledElements[j],
child: DraggableElementTile(
shuffledElements: shuffledElements,
j: j,
),
feedback: DraggableElementTile(
shuffledElements: shuffledElements,
j: j,
),
childWhenDragging: Container(
margin: EdgeInsets.all(8),
height: 100,
width: 80,
color: Colors.blueGrey,
),
),
],
),
);
}
Widget elementDragTarget(tmpElement) {
return DragTarget(
onWillAccept: (data) {
if (tmpElement.successfulDrop == true) {
tmpElement.accepting = false;
return false;
} else {
setState(() {
tmpElement.accepting = true;
});
return true;
}
},
onAccept: (data) {
setState(() {
tmpElement.successfulDrop = true;
if (shuffledElements[j]["atomicNumber"] == tmpElement.atomicNumber) {
tmpElement.correctDrop = true;
tmpElement.accepting = false;
shuffledElementsCopy[j]['answer'] = 'correct';
} else {
tmpElement.correctDrop = false;
tmpElement.accepting = false;
shuffledElementsCopy[j]['answer'] = 'wrong';
}
tmpElement.droppedKey = shuffledElements[j]['key'] + 'dropped';
shuffledElementsCopy[j]['droppedKey'] = tmpElement.droppedKey;
droppedItems.add(shuffledElements[j]);
droppedItemIndex = droppedItems.indexWhere(
(e) => e['droppedKey'] == shuffledElements[j]['key'] + 'dropped');
droppedItem = droppedItems[droppedItemIndex];
tmpElement.symbol = droppedItem['symbol'];
tmpElement.atomicNumber = droppedItem['atomicNumber'];
tmpElement.element = droppedItem['element'];
tmpElement.group = droppedItem['group'];
tmpElement.period = droppedItem['period'];
tmpElement.answer = droppedItem['answer'];
});
nextElement();
},
onLeave: (data) {
setState(() {
tmpElement.accepting = false;
});
return false;
},
builder: (context, acceptedData, rejectedData) {
return buildElementTileInGrid(tmpElement);
},
);
}
//show in grid onAccept
Container buildElementTileInGrid(tmpElement) {
accepting = tmpElement.accepting;
successfulDrop = tmpElement.successfulDrop;
correctDrop = tmpElement.correctDrop;
return Container(
padding: EdgeInsets.all(4),
margin: EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
width: 4,
color: accepting == true ? Colors.teal : Colors.transparent,
),
color: Colors.white38,
),
child: successfulDrop == true
? Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(tmpElement.atomicNumber.toString()),
Text(tmpElement.symbol),
],
)
: Container(),
);
}
}
//draggable
class DraggableElementTile extends StatelessWidget {
const DraggableElementTile({
Key key,
#required this.shuffledElements,
#required this.j,
}) : super(key: key);
final List shuffledElements;
final int j;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.teal,
padding: EdgeInsets.all(12),
margin: EdgeInsets.all(8),
height: 100,
width: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
shuffledElements[j]['symbol'],
style: TextStyle(fontSize: 14),
),
Text(
shuffledElements[j]['element'],
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14),
),
],
),
);
}
}
Here I've one service page where I've displayed all the services, and there I can select multiple services. but I want to select only one service. I've used the checkbox Listitle for the service selection. I want that user can select only one service not multiple services at a time.
Here is code i've tried :
class _AddWalkinServiceScreenState extends State<AddWalkinServiceScreen>
with TickerProviderStateMixin {
List<int> servicesIds = [];
int selected = 0;
Map<String, bool> _selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
List<dynamic> finalList = new List();
List<dynamic> searchList = new List();
bool isLoading = false;
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeInQuint);
controller.forward();
}
Widget _renderServices(AddWalkinServiceViewModel awsVm) {
List lovCountryServices = searchList.length != 0 ? searchList : finalList;
if (lovCountryServices == null || lovCountryServices.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
}
// print(lovCountryServices);
return Container(
child: finalList.length < 1
? ListTile(
leading: CircularProgressIndicator(),
)
: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item = lovCountryServices[
index]; // should be outside build function
List items = item['services'];
return ExpansionTile(
title: Text(item['name']),
children: List.generate(items.length, (i) {
_selection[items[i]['name']] =
_selection[items[i]['name']] ?? items[i]['isSelected'];
return CheckboxListTile(
title: Text(items[i]['name']),
value: _selection[items[i]['name']] == null
? false
: _selection[items[i]['name']],
onChanged: (val) {
setState(() {
_selection[items[i]['name']] = val;
if (val) {
servicesIds.add(items[i]['id']);
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
SubCategory subService = new SubCategory(
id: items[i]['id'],
name: items[i]['name'],
);
List<SubCategory> subCategories = [];
if (services.length < 1) {
subCategories.add(subService);
selectedServices.add(
new BspServices.Service(
mainCategory: item['name'],
mainCategoryId: item['id'],
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
servicesIds.removeWhere((service) {
return service == items[i]['id'];
});
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == items[i]['id'];
});
}
});
print('servicesIds after set state');
print(servicesIds);
},
);
}),
);
},
),
);
}
Widget content(BuildContext context, AddWalkinServiceViewModel awsVm) {
Orientation orientation = MediaQuery.of(context).orientation;
var colorStyles = Theming.colorstyle(context);
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
_searchFilter();
},
defaultBar: AppBar(
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.pop(context);
// NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
return new Scaffold(
backgroundColor: colorStyles['primary'],
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: FadeTransition(
opacity: animation,
child: new Container(
margin: EdgeInsets.only(top: 10),
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(50.0),
topRight: const Radius.circular(50.0),
),
),
child: isLoading ? FadeInUi() : _renderServices(awsVm),
),
),
);
}
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, AddWalkinServiceViewModel>(
converter: (Store<AppState> store) =>
AddWalkinServiceViewModel.fromStore(store),
onInit: (Store<AppState> store) {
print('store.state.servicesState.servicesByCountry');
print(store
.state.servicesState.servicesByCountry.servicesByCountry[0].name);
Map<String, dynamic> services =
store.state.servicesState.servicesByCountry.toJson();
finalList = services['servicesByCountry'];
print('finalList = $finalList');
},
builder: (BuildContext context, AddWalkinServiceViewModel awsVm) =>
content(context, awsVm),
);
}
}
UPDATED
List<Map> services = [];
List<int> selections = [];
#override
void initState() {
super.initState();
getList();
}
void getList() async {
//get data from internet/api
//for ex. I m using offline data
setState(() {
services = List.generate(
10,
(ind) => {
'name': 'Service Category $ind',
'services': ['Service 1', 'Service 2']
}).toList();
selections = List.generate(10, (ind) => -1).toList();
});
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: services.length < 1
? ListTile(
leading: CircularProgressIndicator(), title: Text('Loading...'))
: ListView.builder(
itemCount: services.length,
itemBuilder: (con, ind) {
return ExpansionTile(
title: Text('${services[ind]['name']}',
style: TextStyle(color: Colors.black)),
children:
List.generate(services[ind]['services'].length, (ii) {
return CheckboxListTile(
title: Text('${services[ind]['services'][ii]}',
style: TextStyle(color: Colors.green[900])),
value: selections[ind] == ii,
onChanged: (b) {
setState(() {
selections[ind] = ii;
});
});
}).toList());
}));
}