Check / Uncheck ListTiles in ListView.builder() - Flutter - flutter

I have a ListView.builder(); in showModalBottomSheet();
Need to select / deselect multiple items on tap everything is well but need to close the modal and show it again to apply changes, another thing is the ListTiles sometimes duplicated more than once, function emptyList doesn't work well.
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'book_details.dart' show BookDetails;
class Explore extends StatefulWidget {
#override
_ExploreState createState() => _ExploreState();
}
var _books,
_categories,
_arranges,
_currentCategory,
_selected,
_primeColor,
_currentFilter,
_isThereIsFilters,
_booksContainer,
_booksWithFilters,
_isLoading,
_noBooks,
_itemIcon;
final GlobalKey<ScaffoldState> _scaffoldKeyExplore =
new GlobalKey<ScaffoldState>();
List<String> _getCats = new List();
List<String> _getArrs = new List();
void _insertCategories() {
for (int i = 0; i < _categories.length; i++) {
_getCats.add(_categories[i]);
}
_getCats.sort();
}
void _insertArranges() {
for (int i = 0; i < _arranges.length; i++) {
_getArrs.add(_arranges[i]);
}
}
class _ExploreState extends State<Explore> with TickerProviderStateMixin {
onCatChange(String category) {
setState(() {
_currentCategory = category;
});
}
#override
void initState() {
super.initState();
_primeColor = Color.fromRGBO(239, 89, 39, 1.0);
_categories = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_arranges = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_currentFilter = _arranges[0];
_selected = [];
_isThereIsFilters = false;
}
void emptyList(List list) {
for (var i = 0; i < list.length; i++) {
list.remove(list[i]);
}
}
_showSheet(String type) {
switch (type) {
case "filters":
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Expanded(
child: new ListView.builder(
itemCount: _getArrs[0] != null ? _getArrs.length : 0,
itemBuilder: (BuildContext context, int i) {
return new RadioListTile(
title: Text(_getArrs[i]),
value: _getArrs[i],
groupValue: _currentFilter,
onChanged: (val) {
setState(() {
_currentFilter = val;
});
});
}),
)
])),
);
});
break;
case "categories":
default:
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Container(
color: _primeColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () {
emptyList(_selected);
//Navigator.pop(context);
//_showSheet(type);
}),
IconButton(
icon:
Icon(Icons.done_all, color: Colors.white),
onPressed: () {
if (_selected.length > 0) {
_getFilteredBooks(_selected);
setState(() {
_isThereIsFilters = true;
});
} else {
setState(() {
_isThereIsFilters = false;
});
}
Navigator.pop(context);
})
]),
),
Expanded(
child: new ListView.builder(
itemCount: _getCats != null ? _getCats.length : 0,
itemBuilder: (BuildContext context, int i) {
final _isSelected = _selected.contains(_getCats[i]);
return new ListTile(
leading: Icon(Icons.category),
trailing: _isSelected ? Icon(Icons.done) : null,
title: Text(_getCats[i]),
onTap: () {
setState(() {
_isSelected
? _selected.remove(_getCats[i])
: _selected.add(_getCats[i]);
});
//Navigator.pop(context);
//_showSheet(type);
});
}),
)
])),
);
});
break;
}
}
#override
Widget build(BuildContext context) {
return new Directionality(
textDirection: TextDirection.rtl,
child: new Scaffold(
key: _scaffoldKeyExplore,
appBar:
AppBar(title: Text("استكشاف"), elevation: 0.0, actions: <Widget>[
IconButton(
icon: Icon(Icons.category, color: _primeColor),
onPressed: () => _showSheet("categories")),
IconButton(
icon: Icon(Icons.filter_list, color: _primeColor),
onPressed: () => _showSheet("filters"))
]),
body: Center(child: Text("Nothing..."));
));
}
}
Thank you

need to close the modal and show it again to apply changes
This happens because the showModalBottomSheet's builder needs to be called again to reflect the changes.
In Flutter, StatefulWidgets should be able to rebuild any time the state changes - which is not the case here, because of the bottom sheet being shown.
Why did I run into this issue (on a meta level)?
Storing the state in StatefulWidgets is useful for saving UI state, but you quickly outgrow this technique if you want to store some "app state" or "data state" that is independent of the screen it's on.
It is finally time to fundamentally rethink your state management and settle on a full-fledged state management pattern that decouples the state from the widgets. Luckily, there are a few to choose from:
Making everything global, like you did above. This is generally not a good idea, as you break the contract of setState (state can be modified without the widgets being notified). Also, you break hot restart and stuff like that.
Using an InheritedWidget, where widgets below a root widget can access the same state.
Using a ScopedModel, which builds on top of that.
Using the infamous BLoC pattern, which also builds on top of the InheritedWidget, but adds some Stream-y stuff to make everything more reactive.
Probably many more.
Here is a great Youtube video about state management from Google I/O, where several patterns are being presented.
Anyways, are bottom sheets the right widget for the task ahead?
According to the Material Design spec, the modal bottom sheet is "an alternative to inline menus or simple dialogs on mobile, providing room for additional items, longer descriptions, and iconography".
More concrete, the showModalBottomSheet function is designed to show a widget that doesn't affect the parent over time, but rather - if at all - at a single point in time. That's why it returns a Future<T>, not a Stream<T>.
Be aware that you are trying to use the bottom sheet in a way that it's not intended to be used.
In your case, I'd recommend just using a new screen.

Related

Flutter - getx controller not updated when data changed

I am developing an app that has a bottomnavitaionbar with five pages. I use getx. In first page, i am listing data. My problem is that, when i changed data(first page in bottomnavigationbar) manually from database and thn i pass over pages, came back to first page i could not see changes.
Controller;
class ExploreController extends GetxController {
var isLoading = true.obs;
var articleList = List<ExploreModel>().obs;
#override
void onInit() {
fetchArticles();
super.onInit();
}
void fetchArticles() async {
try {
isLoading(true);
var articles = await ApiService.fetchArticles();
if (articles != null) {
//articleList.clear();
articleList.assignAll(articles);
}
} finally {
isLoading(false);
}
update();
}
}
and my UI;
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
thanks to #Baker for the right answer. However, if you have a list and in viewModel and want to update that list, just use the list.refresh() when the list updated
RxList<Models> myList = <Models>[].obs;
when add or insert data act like this:
myList.add(newItem);
myList.refresh();
GetX doesn't know / can't see when database data has changed / been updated.
You need to tell GetX to rebuild when appropriate.
If you use GetX observables with GetX or Obx widgets, then you just assign a new value to your observable field. Rebuilds will happen when the obs value changes.
If you use GetX with GetBuilder<MyController>, then you need to call update() method inside MyController, to rebuild GetBuilder<MyController> widgets.
The solution below uses a GetX Controller (i.e. TabX) to:
hold application state:
list of all tabs (tabPages)
which Tab is active (selectedIndex)
expose a method to change the active/visible tab (onItemTapped())
OnItemTapped()
This method is inside TabX, the GetXController.
When called, it will:
set which tab is visible
save the viewed tab to the database (FakeDB)
rebuild any GetBuilder widgets using update()
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
Complete Example
Copy/paste this entire code into a dart page in your app to see a working BottomNavigationBar page.
This tabbed / BottomNavigationBar example is taken from
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
but edited to use GetX.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabHomePage(),
);
}
}
class FakeDB {
List<int> viewedPages = [0];
void insertViewedPage(int page) {
viewedPages.add(page);
}
}
/// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
/// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
class TabX extends GetxController {
TabX({this.db});
final FakeDB db;
int selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> tabPages;
#override
void onInit() {
super.onInit();
tabPages = <Widget>[
ListViewTab(db),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
}
/// INTERESTING PART HERE ↓ ************************************
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
// ↑ update() is like setState() to anything inside a GetBuilder using *this*
// controller, i.e. GetBuilder<TabX>
// Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
// by this update()
// Use async/await above if data writes are slow & must complete before updating widget.
// This example does not.
}
}
/// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
class ListViewTab extends StatelessWidget {
final FakeDB db;
ListViewTab(this.db);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: db.viewedPages.length,
itemBuilder: (context, index) =>
ListTile(
title: Text('Page Viewed: ${db.viewedPages[index]}'),
),
);
}
}
class MyTabHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
Get.put(TabX(db: FakeDB()));
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
/// ↓ Tab Page currently visible - rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
child: GetBuilder<TabX>(
builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
),
),
/// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
bottomNavigationBar: GetBuilder<TabX>(
builder: (tx) => BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: tx.selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: tx.onItemTapped,
),
),
);
}
}
You don't need GetBuilder here, as its not meant for observable variables. Nor do you need to call update() in the fetchArticles function as that's only for use with GetBuilder and non observable variables.
So you had 2 widgets meant to update UI (GetBuilder and Obx) both following the same controller and all you need is just the OBX. So Rahuls answer works, or you can leave the Obx in place, get rid of of the GetBuilder and declare and initialize a controller in the beginning of your build method.
final exploreController = Get.put(ExploreController());
Then use that initialized controller in your OBX widget as the child of your Expanded.
Obx(() => exploreController.isLoading.value
? Center(
child:
SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
)
: ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {},
),
)
GetX< ExploreController >(builder: (controller) {
if (controller.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: controller.articleList.length,
separatorBuilder: (BuildContext context, int index) {});
});
If you change the value in the database 'manually', you need a STREAM to listen to the change on the database.
You can't do:
var articles = await ApiService.fetchArticles();
You need to do something like this:
var articles = await ApiService.listenToArticlesSnapshot();
The way you explained is like if you need the data to refresh after navigating to another page and clicking on a button, then navigating to first page (GetBuilder) OR automatically adds data from the within the first page (Obx). But your case is simple, just retrieve the articles SNAPSHOT, then in the controller onInit, subscribe to the snapshot with the bindStream method, and eventually use the function ever() to react to any change in the observable articleList.
Something like this:
create
final exploreController = Get.put(ExploreController());
Add
init: ExploreController();
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
*** here ***
init: ExploreController();
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
using GetxBuilder approch on ui side and where you want update simple called built in function update();
The simplest way I could.
In the controller create an obs (var indexClick = 1.obs;)
On each Tile test the selected==index...;
On the click of each item change the indexClick sequentially
return Obx(() {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.dns),
title: const Text('Menu1'),
selected: controller.indexClick.value==1?true:false,
onTap: () {
controller.indexClick.value=1;
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.search),
title: const Text('Menu2'),
selected: controller.indexClick.value==2?true:false,
onTap: () {
controller.indexClick.value=2;
Navigator.pop(context);
},
),

Can I use Dismissible without actually dismissing the widget?

I'm trying to make a widget that can be swiped to change the currently playing song in a playlist. I'm trying to mimic how other apps do it by letting the user swipe away the current track and the next one coming in. Dismissible is so close to what I actually want. It has a nice animation and I can easily use the onDismissed function to handle the logic. My issue is that Dismissible actually wants to remove the widget from the tree, which I don't want.
The widget I'm swiping gets updated with a StreamBuilder when the song changes, so being able to swipe away the widget to a new one would be perfect. Can I do this or is there a better widget for my needs?
Here's the widget I'm working on:
class NowPlayingBar extends StatelessWidget {
const NowPlayingBar({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<ScreenState>(
stream: _screenStateStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final screenState = snapshot.data;
final queue = screenState.queue;
final mediaItem = screenState.mediaItem;
final state = screenState.playbackState;
final processingState =
state?.processingState ?? AudioProcessingState.none;
final playing = state?.playing ?? false;
if (mediaItem != null) {
return Container(
width: MediaQuery.of(context).size.width,
child: Dismissible(
key: Key("NowPlayingBar"),
onDismissed: (direction) {
switch (direction) {
case DismissDirection.startToEnd:
AudioService.skipToNext();
break;
case DismissDirection.endToStart:
AudioService.skipToPrevious();
break;
default:
throw ("Unsupported swipe direction ${direction.toString()} on NowPlayingBar!");
}
},
child: ListTile(
leading: AlbumImage(itemId: mediaItem.id),
title: mediaItem == null ? null : Text(mediaItem.title),
subtitle: mediaItem == null ? null : Text(mediaItem.album),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (playing)
IconButton(
onPressed: () => AudioService.pause(),
icon: Icon(Icons.pause))
else
IconButton(
onPressed: () => AudioService.play(),
icon: Icon(Icons.play_arrow)),
],
),
),
),
);
} else {
return Container(
width: MediaQuery.of(context).size.width,
child: ListTile(
title: Text("Nothing playing..."),
));
}
} else {
return Container(
width: MediaQuery.of(context).size.width,
// The child below looks pretty stupid but it's actually genius.
// I wanted the NowPlayingBar to stay the same length when it doesn't have data
// but I didn't want to actually use a ListTile to tell the user that.
// I use a ListTile to create a box with the right height, and put whatever I want on top.
// I could just make a container with the length of a ListTile, but that value could change in the future.
child: Stack(
alignment: Alignment.center,
children: [
ListTile(),
Text(
"Nothing Playing...",
style: TextStyle(color: Colors.grey, fontSize: 18),
)
],
));
}
},
);
}
}
Here's the effect that I'm going for (although I want the whole ListTile to get swiped, not just the song name): https://i.imgur.com/ZapzpJS.mp4
This can be done by using the confirmDismiss callback instead of the onDismiss callback. To make sure that the widget never actually gets dismissed, you need to return false at the end of the function.
Dismissible(
confirmDismiss: (direction) {
...
return false;
}
)

Is there a way to update BottomNavigationBarItems?

I am new flutter and been trying to work with the BottomNavigationBar. Thing is, i made the bar as i required but i need it to update its items when a Switch is set to true but can't manage to find a work around.
I have two List<BottomNavigationBarItems> that have the two different navBar items which i assign to a third List that contain the active one depending on the switch state. This variable is the one setting the items in my navbarItem but a setState() doesn't seems to re build the navBar.
Is there a way to update the items or do i have to make my own kind of navBar with other widgets ?
Non Ready Items
Ready Items
List<BottomNavigationBarItem> nonReadyBottomItems = [
//some items
];
List<BottomNavigationBarItem> readyBottomItems = [
some other items
];
List<BottomNavigationBarItem> = nonReadyBottomItems;
Scaffold(
body: Center(
child: Switch(
value: switchConnect,
onChanged: (bool boolean) {
setState(() {
switchConnect = boolean;
});
}),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int i) {
setState(() {
pageIndex = i;
if (switchConnect) {
activeItems = readyBottomItems;
} else if (!switchConnect) {
activeItems = nonReadyBottomItems;
}
});
},
currentIndex: pageIndex,
type: BottomNavigationBarType.fixed,
items: activeItems,
),
);
Yes, You create state full Bottom Navigation Bar for change the state of widgets.
Open bottom sheet
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return ModalBottomSheet(
);
});
})
Stateful bottom sheet
class ModalBottomSheet extends StatefulWidget {
#override
_ModalBottomSheetState createState() => _ModalBottomSheetState();
}
class _ModalBottomSheetState extends State<ModalBottomSheet>
{
#override
Widget build(BuildContext context) {
// TODO: implement build
return Wrap(
children: <Widget>[
Container(
margin:
EdgeInsets.only(left: 10.0, right: 10.0, top: 15.0, bottom: 15.0),
child: Column(
Widgets(),
)
)
],
);
}
}
I actually found out why the setState() wasn't working. Seems like the there was some problem with the assignment of the activeItems variable so it wasn't changing the bar since there was nothing new.
So setState actually work on a BottomNavBar !

Question about Flutter State and retrieving variables from State vs StatefulWidget

Here's the context:
In my app, users can create a question, and all questions will be displayed on a certain page. This is done with a ListView.builder whose itemBuilder property returns a QuestionTile.
The problem:
If I create a new question, the text of the new question is (usually) displayed as the text of the previous question.
Here's a picture of me adding three questions in order, "testqn123", "testqn456", "testqn789", but all are displayed as "testqn123".
Hot restarting the app will display the correct texts for each question, but hot reloading wont work.
In my _QuestionTileState class, if I change the line responsible for displaying the text of the question on the page, from
child: Text(text)
to
child: Text(widget.text)
the issue will be resolved for good. I'm not super familiar with how hot restart/reload and state works in flutter, but can someone explain all of this?
Here is the code for QuestionTile and its corresponding State class, and the line changed is the very last line with words in it:
class QuestionTile extends StatefulWidget {
final String text;
final String roomName;
final String roomID;
final String questionID; //
QuestionTile({this.questionID, this.text, this.roomName, this.roomID});
#override
_QuestionTileState createState() => _QuestionTileState(text);
}
class _QuestionTileState extends State<QuestionTile> {
final String text;
int netVotes = 0;
bool expand = false;
bool alreadyUpvoted = false;
bool alreadyDownvoted = false;
_QuestionTileState(this.text);
void toggleExpansion() {
setState(() => expand = !expand);
}
#override
Widget build(BuildContext context) {
RoomDbService dbService = RoomDbService(widget.roomName, widget.roomID);
final user = Provider.of<User>(context);
print(widget.text + " with questionID of " + widget.questionID);
return expand
? ExpandedQuestionTile(text, netVotes, toggleExpansion)
: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 7, 15, 7),
child: GestureDetector(
onTap: () => {
Navigator.pushNamed(context, "/ChatRoomPage", arguments: {
"question": widget.text,
"questionID": widget.questionID,
"roomName": widget.roomName,
"roomID": widget.roomID,
})
},
child: new Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
// the stack overflow functionality
children: <Widget>[
InkWell(
child: alreadyUpvoted
? Icon(Icons.arrow_drop_up,
color: Colors.blue[500])
: Icon(Icons.arrow_drop_up),
onTap: () {
dynamic result = dbService.upvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyUpvoted = !alreadyUpvoted;
if (alreadyDownvoted) {
alreadyDownvoted = false;
}
});
},
),
StreamBuilder<DocumentSnapshot>(
stream: dbService.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// print("Current Votes: " + "${snapshot.data.data["votes"]}");
// print("questionID: " + widget.questionID);
return Text("${snapshot.data.data["votes"]}");
}
},
),
InkWell(
child: alreadyDownvoted
? Icon(Icons.arrow_drop_down,
color: Colors.red[500])
: Icon(Icons.arrow_drop_down),
onTap: () {
dbService.downvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyDownvoted = !alreadyDownvoted;
if (alreadyUpvoted) {
alreadyUpvoted = false;
}
});
},
),
],
),
Container(
//color: Colors.red[100],
width: 290,
child: Align(
alignment: Alignment.centerLeft,
child: Text(text)), // problem solved if changed to Text(widget.text)
),
}
}
You can wrap your UI with a Stream Builder, this will allow the UI to update every time any value changes from Firestore.
Since you are using an item builder you can wrap the widget that is placed with the item builder.
That Should update the UI

Button text color change

I'm building simple quiz app as a learning project. I'd like to change only clicked button color. Now it changes colors of all buttons. I've tried to make a list of colors, so every button could take its color from the list.
class _AnwserButtonsState extends State<AnwserButtons> {
Color color = Colors.black;
#override
Widget build(BuildContext context) {
return Container(
height: 200,
child: ListView(
children: widget.anwsers
.map(
(anwser) => RaisedButton(
child: Text(anwser, style: TextStyle(color: color)),
onPressed: () => onPressed(anwser),
),
)
.toList(),
),
);
}
onPressed(anwser) {
setState(
() {
if (anwser == widget.properAnwser) {
color = Colors.green;
} else {
color = Colors.red;
}
},
);
}
}
You need an id value. You could use index when use ListView.builder.
ListView.builder(
itemCount: widget.anwsers.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget> [
RaisedButton(
child: Text(widget.anwsers[index], style: TextStyle(color: color)),
onPressed: () => onPressed(widget.anwsers[index], index),
),
)
onPressed(anwser, index) {
setState(
() {
if (anwser == widget.properAnwser[index]) {
color = Colors.green;
} else {
color = Colors.red;
}
},
);
}
}
This could give you an idea. But my opinion is make a class that has answer, correct answer, color and id. Could be much easier and readable when use a class for this example.
You'll be better off using ListView.builder, since you'll be able to control each RaisedButton through its index in the List
Useful Links:
https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html
https://medium.com/#DakshHub/flutter-displaying-dynamic-contents-using-listview-builder-f2cedb1a19fb