How to Change Arrow Icon in ExpansionPanelList - flutter

I am new to Flutter, and I am currently working on creating an ExpansionPanelList. I am trying to replace the icon next to the ExpansionPanel from a carrot (^) to a dropdown icon. Additionally, I would like the dropdown arrow to spin up when the ExpansionPanel is expanded. However, I am not sure how to do this.
Below is how I initialize my ExpansionPanelList. I attempted to solve this problem initializing the arrow I want to use in my ListTitle (where I initialize the header). However, the Icon appears next to the carrot icon that the ExpansionPanel uses.
I am very new to Flutter (and sort-of new to Stack Overflow in general), so a detained explanation would be very much appreciated.
ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
// I believe this is where we want to edit the arrow.
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text("yoink"), //Text(item.headerValue),
trailing: Icon(Icons.arrow_drop_down), //solid arrow
);
},
body: ListTile(
title: Text(item.expandedValue),
subtitle: Text(
'To delete this panel, tap the trash can icon'),
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere(
(currentItem) => item == currentItem);
});
}),
isExpanded: item.isExpanded,
);
}).toList(),
),
For my output, I would like the arrow on the left of each ExpansionPanel to show + respond to expanding/unexpanding the panel. I don't want to see the arrow on the right.

The answer was to make an implementation of a ListView instead. ExpansionPanels are like ListViews with ExpansionTiles, however they are more strict and you cannot customize ExpansionPanels as much as ExpansionTiles, which can only be used in a ListView.

Related

Flutter Slidable - How to remove the slidable item from the list when i hit the delete button?

I have a simple list of ListTile s that I nested in Slidable widget using the flutter_slidable package. However there is one issue that when I use the delete button, the item stays on the screen, even though it is successfully removed from the list (If i make a hot reload, it will actually get removed from screen). I tried to add a setState method inside the onPressed function but I cannot add it because the SlidableAction widget is a stateless widget. How can I make the item disappear when I tap on this button?
Here is a small video demonstration. I delete two lists with the delete button. They stay on the screen. I go home screen and come back to see they got deleted.
https://streamable.com/td7blf
Here is my code:
Expanded(
child: ListView.builder(
itemCount: likedNames.length,
itemBuilder: (context, index) {
return Slidable(
key: const ValueKey(0),
endActionPane: ActionPane(
motion: ScrollMotion(),
dismissible: DismissiblePane(onDismissed: () {
likedNames.remove(likedNames[index]);
}),
children: [
SlidableAction(
onPressed: (context) {
likedNames.remove(likedNames[index]); // <- this is the part where I want to do the removing of the item
},
label: 'Delete',
backgroundColor: AppColors.error,
),
],
),
child: ListTile(
onTap: () {},
title: Text(likedNames[index].name),
trailing: Icon(Icons.chevron_right),
),
);
}),
),
onPressed: (context) {
setState() => likedNames.remove(likedNames[index])
},
I've used this solution:
onPressed: (BuildContext context) async {
await Provider.of<MyList>(context, listen: false)
.remove(MyList[index]);
}
MyList is a separate class:
class MyList extends ChangeNotifier
Whit this methods:
final List<String> _myList = [];
List<String> get items => _myList;
.
.
.
Future<bool> remove(String item) async {
_myList.remove(item);
notifyListeners();
return true;
}

Flutter ExpansionTile trailing icon: how to change depending on whether tile is expanded or collapsed

I would like my ExpandedTile to have the default arrow when it's expanded but show something else when it's collapsed (namely a number that the user has chosen from a list of options shown when it's expanded; the number is stored in a variable called int _selectedNumber.
Is there a way to easily make the trailing property of the ExpandedTile dependent on the tile state (i.e. collapsed or expanded)?
Short example:
ExpansionTile(
title: Text('Some title'),
trailing: // if the tile is expanded -> null; if it's collapsed -> Text(_selectedNumber.toString())
);
P.S. This is my first ever question so please let me know if anything is unclear in my question!
You can use ternary operator with null value to use the default arrow.
Something like this:
var isExpanded = false;
...
ExpansionTile(
title: const Text('Some title'),
subtitle: const Text('Some subtitle'),
// null if expanded, will using default arrow
trailing: isExpanded? null: Text("Other"),
children: const <Widget>[
ListTile(title: Text('This is item')),
],
onExpansionChanged: (bool expanded) {
setState(() => isExpanded = expanded);
},
),

How can I update a list in flutter, after adding a new item to the list?

Okay guys, need your guidance, just started learning flutter (i am a java developer):
Imagine we have a simple list of ListTile widgets:
List testList = [
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(url
),
),
title: Text('Title text'),
subtitle: Text('Subtitle text'),
trailing: smth here also,
),
];
Imagine there are 1-2-3-n items in this array.
Now we want to display this list in a listview, seems quite simple:
children: <ListTile>[
for(ListTile e in testList)
e
],
Now we have all the elements.
Okay, now I want to add new list tile, and this is where I am stuck:
Let's add a dialog, which I am going to call every time the button is pressed:
TextEditingController txtController = TextEditingController();
Future<String> createTxtDialog(BuildContext context) {
return showDialog(context: context, builder: (context) {
return SimpleDialog(
title: Text("Input text"),
children: [
TextField(
controller: txtController,
),
MaterialButton(
elevation: 5.0,
child: Text('Submit'),
onPressed: () {
Navigator.of(context).pop(txtController.text.toString());
},
)
],
);
});
}
Okay, now let's call this function with our button:
// Here I want to add new item to my existing list and instantly get updated list:
// I will create a new list tile with only one row - this is going to be text received from our text controller:
floatingActionButton: FloatingActionButton(
onPressed: () {
createTxtDialog(context);
setState(() {
testList.add(
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(url
),
),
title: Text(txtController.text),
),
);
});
},),
So the problem is:
My list is updated normally, everything is okay, but I cannot see updated list until I reload my application.
I want to have my list updated "on the fly" - as soon as I add new item to the list - my new item is going to be displayed instantly
How can this be solved?
Thank you in advance! :)
P.S.
List updated, but with no text from the dialog
You can use initState and define your default testList in it.
initState({
// testList definition
});
After you add item to list, you can call setState.

Flutter ReorderableListView - how to add divider

I have a list created using ReorderableListView. I want to have a separate each list item with a Divider. I am looking for a clean solution, similar to ListView.separated(), however I can't find anything similar for ReorderableListView. In my code at the moment I am using a column widget to which I add a divider for every item but this is very "hacky". Do you know how this could be implemented in a better way?
I'm looking for divider like here:
My Code:
Main Page:
Widget _buildList(RoutinesState state) {
if (state is RoutinesFetched || state is RoutinesReordered) {
List<CustomCard> cards = [];
state.routines.forEach(
(e) {
cards.add(
CustomCard(
key: PageStorageKey(UniqueKey()),
title: e.name,
onTap: () {
Navigator.pushNamed(
context,
RoutineDetails.PAGE_ROUTE,
arguments: RoutineDetailsArgs(e),
);
},
includeDivider: cards.isNotEmpty,
),
);
},
);
return ReorderableListView(
children: cards,
onReorder: (int from, int to) => {
bloc.add(ReorderRoutine(fromIndex: from, toIndex: to)),
},
);
}
return Container(
child: Text("No routines found yet :( "),
);
}
Custom Card Widget:
#override
Widget build(BuildContext context) {
List<Widget> columnItems = [];
if (this.includeDivider) {
columnItems.add(Divider());
}
columnItems.add(ListTile(
leading: CircleAvatar(
child: Icon(Icons.fitness_center),
),
title: Text(this.title),
subtitle: Text("Weights"),
trailing: ActionChip(
label: Icon(Icons.play_arrow),
backgroundColor: Color(0xffECECEC),
onPressed: () => null,
),
onTap: this.onTap,
));
return Column(
mainAxisSize: MainAxisSize.min,
children: columnItems,
);
}
You're right with using a Divider as it's usually used in ListTiles to serve as its name implies: a divider. Using a Column to define the Widgets in your ListTile isn't "hacky", you're just defining the Widgets in the ListTile the way it can be used. Also, since the Divider is added in the ListTile, when the tile is dragged, the Divider will be moved along with the entire ListTile as expected.
Have you attempted to decorate the container you are using with?
This suggestion by #J. S. worked for me like a charm. No need for a devider when you can just paint the bottom border.
First I tried it with Divider() but that didn't look right because the ListTile is the "tappable" area and when you append a Divider below that, there is space that isn't considered tappable. So there is a white border above the divider that isn't painted with the splash effect. Also when you drag the Tile, it looked as if it would cut some of the lower tile with itself. This is what worked for me:
ReorderableListView.builder(
itemCount: _timeSegments!.length,
itemBuilder: (ctx, index) {
return Container(
key: Key(_timeSegments![index].id),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Theme.of(context).dividerColor, width: 0.5))),
child: TimeSegmentTile(
timeSegment: _timeSegments![index],
press: () => Navigator.pushNamed(context, '/EditTimeSegmentScreen', arguments: EditTimeSegmentScreenArguments(_timeSegments![index], _timeSegmentRepository)),
),
);
},
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final TimeSegment item = _timeSegments!.removeAt(oldIndex);
_timeSegments!.insert(newIndex, item);
});
},
),

Flutter - popup menu isn't closed after selecting an item

I am experimenting with Flutter and having a problem with PopupMenuButton. After selecting an item in the menu I show a new screen without the menu, and after I navigate back the popup menu is still open - as shown in the following screenshots:
After navigating back from the edit profile screen (2nd screenshot) to the account screen (1st screenshot) the pop up menu is still open. I would like it to be closed. The relevant code where I create the popup menu inside an appbar:
actions: <Widget>[
new PopupMenuButton(
itemBuilder: (BuildContext context) {
return <PopupMenuEntry>[
new AppBarMenuItem("Edit profile", () => Navigator.pushNamed(context, Routes.editProfile)).build(context),
new AppBarMenuItem("Option 1", () => {}).build(context),
new AppBarMenuItem("Option 2", () => {}).build(context),
new AppBarMenuItem("Option 3", () => {}).build(context),
new AppBarMenuItem("Option 4", () => {}).build(context),
];
},
),
],
And the AppBarMenuItem:
new PopupMenuItem(
child: new InkWell(
child: new Text(_label),
onTap: _onTap,
)
How can I make sure that the popup menu gets closed after selecting an item? It looks like if I just use PopupMenuItem in my PopupMenuButton and navigate to the new screen in the onSelected function the menu closes properly. But when I use the onTap function of InkWell it doesn't close anymore.
Just use pop inside your onTap function Navigator.pop(context, "$popupValue");
PopupMenuItem<String>(
value: "Replay Game",
child: ListTile(
leading: Icon(Icons.replay,
color: theme.actionButtonColor),
title: Text("Replay Game"),
onTap: () {
Navigator.pop(context, "Replay Game");
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Clear input and replay game?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text("No"),
textColor: theme.alterDialogActionColor,
),
FlatButton(
onPressed: () {
store.dispatch(ReplayAction(timerBloc, varBloc.fireAnalytics));
Navigator.pop(context);
},
child: Text("Yes"),
textColor: theme.alterDialogActionColor,
),
],
);
});
},
),
)
as stated in the documentation the popup menu should automatically close when user selects an option from the popup menu item.
Using InkWell onTap wont automatically close the popup menu rather directly use the popup menu item to automatically close the popup menu when an item is selected from the popupMenuList
Make sure that the value property of the PopupMenuItem is not null otherwise the onSelected function will not be called when the PopupMenuItem is selected
I had this same issue and here is my solution. Your PopUpMenuButton() is not utilizing the onSelected property. The onSelected property will properly close your PopUpMenuButton. It currently is not because the onTap property of your AppBarMenuItem is taking over that job.
Also, I created a list of PopUpMenuItem, instead of PopUpMenuEntry, not sure if that makes a difference. But for each of my PopUpMenuItem, I also assigned the value property to each, so that the onSelected would communicate to the PopUpMenuButton() which item was tapped.
Something like this:
PopupMenuButton(
onSelected: (selection) {
switch (selection) {
case 1:
... do stuff...
break;
case 2:
... break stuff...
);
break;
}
},
Where case 1, case 2, etc refer to the value property I assigned to the PopUpMenuItem().