How to make button change color and icon in the ListTile - flutter

I have ListTile in the ListView with RaisedButton as trailing, I want to change color and icon on btn clicked, trouble is if I change it on setstate method all listTile buttons change. So how to determine each one?
Widget _getList(BuildContext context,int index,) {
return Card(
elevation: 3,
child: Column(
children: <Widget>[
ListTile(
leading: Image.asset(
"assets/" + _allDevices[index].image,
fit: BoxFit.cover,
),
title: Text(_allDevices[index].name),
subtitle: Text(_allDevices[index].desc),
trailing: SizedBox.fromSize(
size: Size(56, 56), // button width and height
child: ClipOval(
child: RaisedButton(
elevation: 2,
splashColor: Colors.red,
color: Colors.blue,
onPressed: () {
setState(() {
//pro should do something here... switch index or something....
});
},
child: Icon(Icons.lock_open),
),
)),
onTap: () {},
)
],
),
);
}

Find this sample, All needed is bool flag in the model class which maintains the click status. On click set it true, if it's already true then set it as false.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class Devices {
String name = '';
bool isSelected = false;
Devices(this.name, this.isSelected);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
var _allDevices = [
Devices('Text', false),
Devices('Text', false),
Devices('Text', false),
Devices('Text', false)
];
Widget _getList(BuildContext context, int index) {
return Card(
elevation: 3,
child: Column(
children: <Widget>[
ListTile(
leading: Text('Text'),
title: Text(_allDevices[index].name),
subtitle: Text(_allDevices[index].name),
trailing: SizedBox.fromSize(
size: Size(56, 56), // button width and height
child: ClipOval(
child: RaisedButton(
elevation: 2,
color: _allDevices[index].isSelected
? Colors.green
: Colors.blue,
onPressed: () {
setState(() {
if (_allDevices[index].isSelected) {
_allDevices[index].isSelected = false;
} else{
_allDevices[index].isSelected = true;
}
});
},
child: Icon(Icons.lock_open),
),
)),
onTap: () {},
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return _getList(context, index);
})
]));
}
}

Related

Expansion tile trailing icon updates all in list on interaction with one tile. How can I only change the icon for the expanded tile?

How do I make it so that the icon will only update for the tile that was clicked? Right now, the behavior is that all icons update when clicking on one tile.
Here is the code (trimmed to only include relevant parts):
Column(children: List.generate(
filteredFAQ.length,
(index) => Column(
children: [
if(index > 0) {
Container(
child: Column(
children: <Widget>[
ExpansionTile(
trailing: SvgPicture.string(
isQuestionClicked
? addPayeeArrowUp
: rightArrow,
color: primary,
),
onExpansionChanged:
(bool expanded) {
setState(() {
isQuestionClicked = expanded;
});
},
),
],
)
)
}
]
)
),);
here are screenshots of the behavior:
[2
I used the in built onExpansionChange of the ExpansionTile.
To only change the icon of the expanded tile, you can use this approach:
create a Map:
Map<int, bool> state = {};
and use it accordingly in your ExpansionTile to check whether it's selected, if the value is true or false:
List.generate(6, (index) {
return ExpansionTile(
title: Text('Item $index'),
trailing: state[index] ?? false
? Icon(Icons.arrow_drop_up)
: Icon(Icons.arrow_drop_down),
onExpansionChanged: (value) {
setState(() {
state[index] = value;
});
},
children: [
Container(
height: 100,
color: Colors.red,
),
],
);
}),
Complete runnable example:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Map<int, bool> state = {};
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Column(
children:
// generate 6 ExpansionTiles
List.generate(6, (index) {
return ExpansionTile(
title: Text('Item $index'),
trailing: state[index] ?? false
? Icon(Icons.arrow_drop_up)
: Icon(Icons.arrow_drop_down),
onExpansionChanged: (value) {
setState(() {
state[index] = value;
});
},
children: [
Container(
height: 100,
color: Colors.red,
),
],
);
}),
),
);
}
}
You have to manage each childrens state separatory.
I think it's best to manage them in filteredFAQ by adding
bool isExpanded
property there. but you can achive by manage them as separated property like
final items = List<bool>.generate(filteredFAQ.length, (index) => false);
and change their state when they're expanded
items[index] = !items[index]
here's a sample complete code
Column(children: List.generate(
filteredFAQ.length,
(index) => Column(
children: [
if(index > 0) {
Container(
child: Column(
children: <Widget>[
ExpansionTile(
trailing: SvgPicture.string(
items[index]
? addPayeeArrowUp
: rightArrow,
color: primary,
),
onExpansionChanged:
(bool expanded) {
setState(() {
items[index] = !items[index];
});
},
),
],
)
)
}
]
)
),);
And don't forget to initalize items at where you initialize filteredFAQ
If you provide a whole code in the widget I can complete it if you need more information

FilterChip inside ModalBottomSheet

Hi I'm a beginner flutter developer, I have a StatefulWidget widget and a ListView here is a button to display ModalBottomSheet
The ModalBottomSheet has a FilterChip widget that allows the user to apply some filters to the ListView, but I would like to keep the FilterChip state even after the user pop the ModalBottomSheet.
class AvailableMeals extends StatefulWidget {
static const routeName = 'available-meals';
#override
_AvailableMealsState createState() => _DietAvailableMealsState();
}
class _DietAvailableMealsState extends State<DietAvailableMeals> {
bool status = false;
#override
Widget build(BuildContext context) {
buildFilterBox() {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
'SelectFilter',
style: TextStyle(fontSize: 10.sp),
),
),
Container(
child: Wrap(
spacing: 25,
children: [
FilterChip(
selected: status,
label: Text('Vegan'),
onSelected: (value) {
setState(() {
status = value;
});
})
],
),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: Text('Meals'),
actions: [
IconButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return buildFilterBox();
});
},
icon: Icon(Icons.search))
],
),
body: Container(child : Column(children: [
Expanded(
child: ListView.builder(
itemBuilder: (ctx, index) => ChangeNotifierProvider.value(
value: _customList[index], child: MealCard(_customList[index])),
itemCount: _customList.length,
));
] ))
}

Add new widget upon clicking floating action button in flutter

I am a beginner in Flutter. I am trying to add a new list item widget to screen when floating action button is pressed. How do I achieve this?
I am trying to create a list of items. When the floating action button is clicked, a dialog box is prompted and user is asked to enter details. I want to add a new list item with these user input details.
This is my input_page.dart file which I am calling in main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MedPage extends StatefulWidget {
#override
_MedPageState createState()=> _MedPageState();
}
class _MedPageState extends State<MedPage> {
Future<String>createAlertDialog(BuildContext context) async{
TextEditingController customController= new TextEditingController();
return await showDialog(context: context,builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: (){
Navigator.of(context).pop(customController.text.toString()); // to go back to screen after submitting
}
)
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
Expanded(
child: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
ReusableListItem(Color(0xFFd2fddf),"Name 1"),
ReusableListItem(Colors.orange,"Name 2"),
ReusableListItem(Color(0xFF57a1ab), "Name 3"),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
print("Clicked");
createAlertDialog(context).then((onValue){
print(onValue);
setState(() {
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour,this.pill);
Color colour;
String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colour,
borderRadius: BorderRadius.circular(15.0)
),
child: Center(
child: Text(pill)
),
);
}
}
You don't need to change much in your code, maintain a variable that stores the values entered to be able to show them in the list. You should use Listview.builder() in order to dynamically render the items.
Here's your code:
class MedPage extends StatefulWidget {
#override
_MedPageState createState() => _MedPageState();
}
class _MedPageState extends State<MedPage> {
List<String> items = [];
Future<String> createAlertDialog(BuildContext context) async {
TextEditingController customController = new TextEditingController();
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(customController.text
.toString()); // to go back to screen after submitting
})
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return ReusableListItem(Color(0xFFd2fddf), items[index]);
},
itemCount: items.length,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print("Clicked");
createAlertDialog(context).then((onValue) {
// print(onValue);
setState(() {
items.add(onValue);
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour, this.pill);
final Color colour;
final String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration:
BoxDecoration(color: colour, borderRadius: BorderRadius.circular(15.0)),
child: Center(child: Text(pill)),
);
}
}
Firstly you need to use ListView.builder() rather than ListView because you have dynamic content. Also you need to hold your items in a list.
// create a list before
ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text(list[index]);
}
)
When you click on FloatingActionButton() you will call AlertDialog() method.
FloatingActionButton(
onPressed: (){
AlertDialog(
content: Form(), // create your form here
actions: [
// add a button here
]
)
})
This method will show a dialog(you will add a form inside of the dialog). When the user completes the form(after clicking the button) you will add a new object to the list and update the state with setState({})
onPressed: (){
setState({
// add new object to the list here
});
Navigator.pop(context); // this will close the dialog
}

Flutter TabBarView and BottomNavigationBar pages are not refreshing with SetState

I have the following Flutter BottomNavigationBar, I have 3 children page widgets. As soon as the user writes a review and press the submit button, I am calling the void _clear() which resets the values in the textfield widgets. This method is inside and called from the WriteReview widget but is not working, the screen is not refreshing. The data it self is being reset but the UI has not be refreshed. I tried with and without setState(){ _clear()}; but no results.
I have similar issue with the TabBarView. What I would expect as a result would be the selected widget page to be refreshed.
I assume is something different on how the widgets are being handled in the TabBarView and BottomNavigationBar that I am missing.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final String _writeReviewLabel = 'Write review';
final String _searchLabel = 'Search';
final String _accountLabel = 'Account';
var _currentIndex = 0;
final List<Widget> _bottomBarItems = [
WriteReviewView(),
SearchView(),
UserAccountView()
];
#override
Widget build(BuildContext context) {
final accentColor = Theme.of(context).accentColor;
final primaryColor = Theme.of(context).primaryColor;
return BaseView<HomePresenter>(
createPresenter: () => HomePresenter(),
onInitialize: (presenter) {
_currentIndex = ModalRoute.of(context).settings.arguments;
},
builder: (context, child, presenter) => Scaffold(
bottomNavigationBar: BottomNavigationBar(
backgroundColor: primaryColor,
selectedItemColor: accentColor,
unselectedItemColor: Colors.white,
elevation: 0,
currentIndex: _currentIndex,
onTap: (index) {
onTabTapped(index, presenter);
},
items: [
BottomNavigationBarItem(
activeIcon: Icon(Icons.rate_review),
icon: Icon(Icons.rate_review),
title: Text(_writeReviewLabel),
),
BottomNavigationBarItem(
activeIcon: Icon(Icons.search),
icon: Icon(Icons.search),
title: Text(_searchLabel),
),
BottomNavigationBarItem(
activeIcon: Icon(Icons.person),
icon: Icon(Icons.person),
title: Text(_accountLabel)),
],
),
body: _bottomBarItems[_currentIndex]),
);
}
void onTabTapped(int index, HomePresenter presenter) {
if (index == _currentIndex) {
return;
}
if (presenter.isAuthenticated) {
setState(() {
_currentIndex = index;
});
} else {
presenter.pushNamed(context, Routes.login, arguments: () {
if (presenter.isAuthenticated) {
Future.delayed(const Duration(microseconds: 500), () {
setState(() {
_currentIndex = index;
});
});
}
});
}
}
}
Write Review View
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'base/BaseView.dart';
class WriteReviewView extends StatefulWidget {
#override
_WriteReviewViewState createState() => _WriteReviewViewState();
}
class _WriteReviewViewState extends State<WriteReviewView> {
final String _locationLabel = 'Location';
final String _reviewLabel = 'Review';
#override
Widget build(BuildContext context) {
return BaseView<WriteReviewPresenter>(
createPresenter: () => WriteReviewPresenter(),
onInitialize: (presenter) {
presenter.init();
},
builder: (context, child, presenter) => Container(
color: Theme.of(context).backgroundColor,
child: _body(presenter),
));
}
Widget _body(WriteReviewPresenter presenter) {
final height = MediaQuery.of(context).size.height;
return LayoutBuilder(builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
margin: EdgeInsets.fromLTRB(4.0, 4.0, 4.0, 8.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10, bottom: 20),
child: Row(
children: [
Icon(Icons.location_on,
color: Theme.of(context).cursorColor),
SectionTitle(_locationLabel),
],
),
),
ChooseAddressButton(presenter.addressContainer.address, () {
presenter.navigateNewAddress(context);
}),
SizedBoxes.medium,
],
),
),
),
Card(
margin: EdgeInsets.fromLTRB(4.0, 4.0, 4.0, 8.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(top: 10),
child: Row(
children: [
Icon(Icons.star, color: Theme.of(context).indicatorColor),
SectionTitle(_reviewLabel),
],
),
),
SizedBoxes.medium,
CustomTextFormField(
data: presenter.reviewContainer.review,
showMinimum: true,
characterLimit: 50,
maxLines: 4,
height: 100),
ModifyRatingBar(50.0, presenter.reviewContainer.rating, false),
Padding(
padding: EdgeInsets.symmetric(vertical: 5),
child: Divider(indent: 40, endIndent: 40)),
],
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Card(
color: Theme.of(context).accentColor,
child: FlatButton(
onPressed: () {
_submit(context, presenter);
},
child: Text('Submit',style:TextStyle(fontWeight: FontWeight.bold,fontSize: 18,color: Colors.white)),
),
),
),
],
),
),
),
);
});
}
void _submit(BuildContext context, WriteReviewPresenter presenter) async {
LoadingPopup.show(context);
final status = await presenter.submit(context);
LoadingPopup.hide(context);
if (status == ReviewStatus.successful) {
PostCompletePopup.show(context);
setState(() {
presenter.clear();
});
} else {
presenter.showError(context, status);
}
}
}
setState() only refreshes the widget that called it, and all the widgets under it.
When calling setState()from WriteReview widget, it will not update the HomeView widget. Try moving the setState()call to the HomeView widget using some sort of callback.

onTap go to next list item (Flutter)

I have a ListView.builder showing a list, when i click on an item it shows details of that item on the next page (FlashcardDetailsPage).
I'd like to show the next list item when i tap the IconButton in the class FlashcardDetailsPage. So i'd like this button to skip to the next list item. Any ideas?
class FlashcardListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: allFlashcards.length,
itemBuilder: (context, int index) {
return ListTile(
title: Text(allFlashcards[index].actReg),
subtitle: Text(allFlashcards[index].question),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FlashcardDetailPage(
flashcardToShow: allFlashcards[index]),
),
);
},
);
});
}
}
class FlashcardDetailPage extends StatefulWidget {
final Flashcard flashcardToShow;
FlashcardDetailPage({Key key, #required this.flashcardToShow})
: super(key: key);
#override
_FlashcardDetailPageState createState() => _FlashcardDetailPageState();
}
class _FlashcardDetailPageState extends State<FlashcardDetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(242, 242, 242, 1),
appBar: AppBar(
centerTitle: true,
title: Text(widget.flashcardToShow.actReg),
),
body: Column(
children: <Widget>[
Container(
child: Card(
margin: EdgeInsetsDirectional.fromSTEB(20, 20, 20, 0),
child: Center(
child: Text(
widget.flashcardToShow.question,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30),
),
)),
),
Container(
height: 100.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.skip_next),
iconSize: 32.0,
),
],
),
),
],
),
);
}
}
You could just replace the screen with another one showing the next card:
IconButton(
icon: Icon(Icons.skip_next),
iconSiz: 32,
onTap: () {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
if (currentIndex >= allFlashcards.length) return;
var nextFlashcard = allFlashcards[currentIndex + 1];
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (ctx) => FlashDetailsPage(flashcardToShow: nextFlashcard)
));
},
)
Thanks Marcel for the direction! I used your logic for a method. To avoid opening a new page every time I pressed the button, i did this & it's working:
void _skipFlashcard () {
setState(() {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
var nextFlashcard = allFlashcards[currentIndex + 1];
widget.flashcardToShow = nextFlashcard;
});
}
IconButton(
icon: Icon(Icons.skip_next),
iconSize: 32.0,
onPressed: _skipFlashcard,