I would like to call a fonction when my ExpandablePanel is expanded, with ExpansionTile I do this with onExpansionChanged but here I don't want to use ExpansionTile,
Doesn't anyone have a solution ?
Thanks.
Use an ExpandableControllerand an ExpandableNotifier:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _MyWidget();
}
class _MyWidget extends State<MyWidget> {
final ExpandableController expandableController = ExpandableController();
void onExpandableControllerStateChanged() {
if (expandableController.expanded) {
// Do your stuff when panel got expanded here
} else {
// Do your stuff when panel got collapsed here
}
}
#override
void initState() {
super.initState();
expandableController.addListener(onExpandableControllerStateChanged);
}
#override
void dispose() {
expandableController.removeListener(onExpandableControllerStateChanged);
super.dispose();
}
#override
Widget build(BuildContext context) {
return ExpandableNotifier(
controller: expandableController,
child: ExpandablePanel(
header: HeaderWidget(),
collapsed: CollapsedWidget(),
expanded: ExpandedWidget(),
),
);
}
}
You can put the ExpansionPanel inside an ExpansionPanelList and inside it will have a property called expansionCallback
You can wrap ExpansionPanel with ExpansionPanelList, so that you can access to a callback function named expansionCallback. Take a look at the snippet below:
ExpansionPanelList(
animationDuration: const Duration(milliseconds:1000),
children: [
ExpansionPanel(), //..your expansionPanel here
],
expansionCallback: (int item, bool status) {
//status is what you're looking for
},
),
Related
I am trying to update the notifier value from parent widget whereas ValueListenableBuilder is defined in a child widget but the builder is not calling after changing the value.
Here is the parent widget code in which I have declared two child widgets as StatefulWidget and also declared a static object of Notifier class. I am calling the method updateMenuItemList from secondChild() widget like this HotKeysWidget.of(context)!.updateMenuItemList(currentCat!['items']); to update the list of firstChild() widget :
class HotKeysWidget extends StatefulWidget {
static HotKeysWidgetState? of(BuildContext context) =>
context.findAncestorStateOfType<HotKeysWidgetState>();
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HotKeysWidgetState();
}
}
class HotKeysWidgetState extends State<HotKeysWidget> {
static DealsNotifier appValueNotifier = DealsNotifier();
updateMenuItemList(List<Food> list) {
appValueNotifier.updateMenuList(list);
}
#override
Widget build(BuildContext context) {
return Container(child: Column(children: [
firstChild(),
secondChild(),
],
),
);
}
}
Here is my Notifier class:
class DealsNotifier {
ValueNotifier<List<Food>> dealList = ValueNotifier([]);
ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
ValueNotifier<List<Map<String,dynamic>>> categoryList = ValueNotifier([]);
void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
void updateCategoryList(List<Map<String,dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
List<Food> getDealList() {
return dealList.value;
}
List<Food> getMenuitemList() {
return menuitemList.value;
}
List<Map<String,dynamic>> getCategoryList() {
return categoryList.value;
}
}
And this is the child widget named as firstChild() in parent code. Here the ValueListenerBuilder is declared:
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: HotKeysWidgetState.appValueNotifier.menuitemList,
builder: (context, List<Food> value, widget)
{
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu:currentCat != null ? value : [],
);
},
);
}
}
class secondChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return secondChildState();
}
}
class secondChildState extends State<secondChild> {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
HotKeysWidget.of(context)!.updateMenuItemList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
Anyone help me with this issue please.
Thanks in advance
While there's still not enough code shared to fully reproduce your situation, I can offer some suggestions.
The state portion of StatefulWidgets are private by default for a reason. You shouldn't make them public just to access variables that are inside there are several other to access outside classes within widgets.
So anytime you're doing something like this
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
...
Just stick to the default syntax of a StatefulWidget and also classes should be in UpperCamelCase with the first letter capitalized.
class FirstChild extends StatefulWidget {
const FirstChild({Key? key}) : super(key: key);
#override
State<FirstChild> createState() => _FirstChildState();
}
class _FirstChildState extends State<FirstChild> {
#override
Widget build(BuildContext context) {
...
If you find yourself wanting to edit this default syntax its a clue that you need to find a better way to achieve whatever it is you're trying to do. If you're need to access a function that is declared in a Widget from outside that Widget, then that function should be declared somewhere else.
All that being said, unless you need setState, initState or another of the lifecycle functions, then you don't need a StatefulWidget to begin with. All those classes can be Stateless.
An easy way to make that DealsNotifier class globally accessible without a full on state management solution is to make it a static class.
class DealsNotifier {
static ValueNotifier<List<Food>> dealList = ValueNotifier([]);
static ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
static ValueNotifier<List<Map<String, dynamic>>> categoryList =
ValueNotifier([]);
static void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
static void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
static void updateCategoryList(List<Map<String, dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
static List<Food> getDealList() {
return dealList.value;
}
static List<Food> getMenuitemList() {
return menuitemList.value;
}
static List<Map<String, dynamic>> getCategoryList() {
return categoryList.value;
}
}
Then when you need to pass in the valueListenable you access via DealsNotifier.menuitemlist and its always the same instance.
return ValueListenableBuilder(
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
Here's the Stateless version of all those classes and wherever you need the UI update you can use ValueListenableBuilder and pass in DealsNotifier.whicheverVariableYouWantToListenTo in the valueListenable. Then call whichever relevant method from the DealsNotifier class ie. DealsNotifier.updateMenuList([]).
And you didn't share your HotkeysMenuItemsWidget but if that's where you're looking to see the change in the UI, then that is where the ValueListenableBuilder should be. Its currently too high up in the widget tree all it needs to do is re-render the list in that Widget, you don't need/want an entire re-build of the HotkeysMenuItemsWidget from a parent widget.
class FirstChild extends StatelessWidget {
const FirstChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder( // this should be inside HotkeysMenuItemsWidget
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
}
}
class SecondChild extends StatelessWidget {
const SecondChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
DealsNotifier.updateMenuList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
class HotKeysWidget extends StatelessWidget {
const HotKeysWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
FirstChild(),
SecondChild(),
],
),
);
}
}
In flutter,
How can a parent widget know if a child among many children widgets has received focus? For example, Can we know if a child in a Row widget's children has received focus?
Can I detect this focus before the child widget receives it?
It actually depends on your take and which architecture you wanna follow.
This snippet that I'm posting uses NotificationListener, a custom notification and a custom child widget. This might work for an application like a print or a callback, but you might need to change the architecture and use a state management tool to achieve greater things.
Parent Widget class:
class MyParentWidget extends StatelessWidget {
const MyParentWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return NotificationListener<FocusNotification>(
onNotification: (notification) {
print("New widget focused: ${notification.childKey.toString()}");
return true;
},
child: Row(
children: List.generate(
5,
(index) => MyChildWidget(
Key('widget-$index'),
),
),
),
);
}
}
Child Widget class:
class MyChildWidget extends StatefulWidget {
const MyChildWidget(Key key) : super(key: key);
#override
_MyChildWidgetState createState() => _MyChildWidgetState();
}
class _MyChildWidgetState extends State<MyChildWidget> {
final node = FocusNode();
#override
initState() {
node.addListener(() {
if (node.hasFocus) {
final notification = FocusNotification(widget.key!);
notification.dispatch(context);
}
});
super.initState();
}
#override
dispose() {
node.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
focusNode: node,
);
}
}
Custom Notification class:
class FocusNotification extends Notification {
const FocusNotification(this.childKey);
final Key childKey;
}
I'm trying to create a custom menu bar in my app. Right now, the biggest issue I'm having is passing a state for when it's expanded to it's children after a setState occurs.
I thought about inheritance, but from what I've tried all inheritance needs to be in-line. I can't create a widget where the children [] are fed into the constructor on an ad-hoc basis.
My current approach is to use a GlobalKey to update the State of the children widgets being inserted into the StateFul while updating them directly.
The children for my MenuBar are declared as:
List<MenuBarItem> menuItems;
MenuBarItem is an abstract interface class that I intend to use to limit the widgets that can be fed in as menuItems to my MenuBar.
abstract class iMenuItem extends Widget{}
class MenuBarItem extends StatefulWidget implements iMenuItem{
At some iterations of this script, I had a bool isExpanded as part of the iMenuItem, but determined it not necessary.
Here is my code at its current iteration:
My Main:
void main() {
// runApp(MainApp());
//runApp(InherApp());
runApp(MenuBarApp());
}
class MenuBarApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: MenuBar(
menuItems: [
// This one does NOT work and is where I'm trying to get the
// value to update after a setState
MenuBarItem(
myText: 'Outsider',
),
],
),
),
);
}
}
My Code:
import 'package:flutter/material.dart';
/// Primary widget to be used in the main()
class MenuBar extends StatefulWidget{
List<MenuBarItem> menuItems;
MenuBar({
required this.menuItems,
});
#override
State<MenuBar> createState() => MenuBarState();
}
class MenuBarState extends State<MenuBar>{
bool isExpanded = false;
late GlobalKey<MenuBarContainerState> menuBarContainerStateKey;
#override
void initState() {
super.initState();
menuBarContainerStateKey = GlobalKey();
}
#override
Widget build(BuildContext context){
return MenuBarContainer(
menuItems: widget.menuItems,
);
}
}
class MenuBarContainer extends StatefulWidget{
List<MenuBarItem> menuItems;
late Key key;
MenuBarContainer({
required this.menuItems,
key,
}):super(key: key);
#override
MenuBarContainerState createState() => MenuBarContainerState();
}
class MenuBarContainerState extends State<MenuBarContainer>{
bool isExpanded = false;
#override
void initState() {
super.initState();
isExpanded = false;
}
#override
Widget build(BuildContext context){
List<Widget> myChildren = [
ElevatedButton(
onPressed: (){
setState((){
this.isExpanded = !this.isExpanded;
});
},
child: Text('Push Me'),
),
// This one works. No surprise since it's in-line
MenuBarItem(isExpanded: this.isExpanded, myText: 'Built In'),
];
myChildren.addAll(widget.menuItems);
return Container(
child: Column(
children: myChildren,
),
);
}
}
/// The item that will appear as a child of MenuBar
/// Uses the iMenuItem to limit the children to those sharing
/// the iMenuItem abstract/interface
class MenuBarItem extends StatefulWidget implements iMenuItem{
bool isExpanded;
String myText;
MenuBarItem({
key,
this.isExpanded = false,
required this.myText,
}):super(key: key);
#override
State<MenuBarItem> createState() => MenuBarItemState();
}
class MenuBarItemState extends State<MenuBarItem>{
#override
Widget build(BuildContext context){
GlobalKey<MenuBarState> _menuBarState;
return Row(
children: <Widget> [
Text('Current Status:\t${widget.isExpanded}'),
Text('MenuBarState GlobalKey:\t${GlobalKey<MenuBarState>().currentState?.isExpanded ?? false}'),
Text(widget.myText),
],
);
}
}
/// To give a shared class to any children that might be used by MenuBar
abstract class iMenuItem extends Widget{
}
I've spent 3 days on this, so any help would be appreciated.
Thanks!!
I suggest using ChangeNotifier, ChangeNotifierProvider, Consumer and context.read to manage state. You have to add this package and this import: import 'package:provider/provider.dart';. The steps:
Set up a ChangeNotifier holding isExpanded value, with a setter that notifies listeners:
class MyNotifier with ChangeNotifier {
bool _isExpanded = false;
bool get isExpanded => _isExpanded;
set isExpanded(bool isExpanded) {
_isExpanded = isExpanded;
notifyListeners();
}
}
Insert the above as a ChangeNotifierProvider in your widget tree at MenuBar:
class MenuBarState extends State<MenuBar> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyNotifier(),
child: MenuBarContainer(
menuItems: widget.menuItems,
));
}
}
After this you can easily read and write the isExpanded value from anywhere in your widget tree under the ChangeNotifierProvider, for example:
ElevatedButton(
onPressed: () {
setState(() {
final myNotifier = context.read<MyNotifier>();
myNotifier.isExpanded = !myNotifier.isExpanded;
});
},
child: Text('Push Me'),
),
And if you want to use this state to automatically build something when isExpanded is changed, use Consumer, which will be notified automatically upon every change, for example:
class MenuBarItemState extends State<MenuBarItem> {
#override
Widget build(BuildContext context) {
return Consumer<MyNotifier>(builder: (context, myNotifier, child) {
return Row(
children: <Widget>[
Text('Current Status:\t${myNotifier.isExpanded}'),
Text(widget.myText),
],
);
});
}
}
Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"
class SpecialButton extends StatefulWidget {
bool active = false;
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration:
BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
child: null);
}
}
In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).
My second idea was to introduce this flag as a propper state of the SpecialButtonState class
class SpecialButton extends StatefulWidget {
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
bool active;
#override
void initState() {
super.initState();
this.active = false;
}
activate() {
this.setState(() {
active = true;
});
}
deactivate() {
this.setState(() {
active = false;
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
child: null);
}
}
As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.
So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?
There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.
EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.
It is not neŃessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:
class SomeParent extends StatefulWidget {
const SomeParent({Key key}) : super(key: key);
#override
State<SomeParent> createState() => SomeParentState();
}
class SomeParentState extends State<SomeParent> {
bool _button1IsActive = false;
bool _button2IsActive = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SpecialButton(
key: UniqueKey(),
active: _button1IsActive,
),
SizedBox(height: 8),
SpecialButton(
key: UniqueKey(),
active: _button2IsActive,
),
SizedBox(height: 16),
TextButton(
child: Text('Toggle button 1'),
onPressed: () {
setState(() {
_button1IsActive = !_button1IsActive;
});
},
),
SizedBox(height: 8),
TextButton(
child: Text('Toggle button 2'),
onPressed: () {
setState(() {
_button2IsActive = !_button2IsActive;
});
},
),
],
),
);
}
}
class SpecialButton extends StatelessWidget {
final bool active;
const SpecialButton({Key key, this.active = false}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
);
}
}
SomeParent is my fantasy, just for example. Don't know what your parent is.
Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.
Please try this approach, it should work.
As nvoigt says, your buttons could even be stateless widget , but their parent should be statefull and you should provide them with the corresponding value. e.g.:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
StateLessButton1(isEnabled: isEnabled),
StateLessButton1(isEnabled: !isEnabled),
FloatingActionButton(onPressed: (){
setState(() {
isEnabled = !isEnabled;
});
})
],
);
}
}
Now it just depends on when you want to change that value. If you want to change it inside your buttons, I would recommend you to use a class with ChangeNotifier and a function inside it that changes the value. Otherwise I would recommend not to separate your tree into multiple files
I have a list of choice widget and want to pass the selected choice to another widget.
Here is the list of choice widget
class ChoiceChipWidget extends StatefulWidget {
final List<String> reportList;
final Function(String item) onChoiceSelected;
ChoiceChipWidget(this.reportList, this.onChoiceSelected);
#override
_ChoiceChipWidgetState createState() => new _ChoiceChipWidgetState();
}
class _ChoiceChipWidgetState extends State<ChoiceChipWidget> {
String selectedChoice = "";
_buildChoiceList() {
List<Widget> choices = List();
widget.reportList.forEach((item) {
choices.add(Container(
child: ChoiceChip(
label: Text(item),
selected: selectedChoice == item,
onSelected: (selected) {
setState(() {
selectedChoice = item;
widget.onChoiceSelected(item);
print(selectedChoice); //DATA THAT NEEDS TO BE PASSED
});
},
),
));
});
return choices;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildChoiceList(),
);
}
}
I need to pass it to this widget
class AddCashPage extends StatefulWidget {
#override
_AddCashPageState createState() => _AddCashPageState();
}
class _AddCashPageState extends State<AddCashPage> {
void createTodo() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
String repetition = //DATA NEEDS TO GO HERE;
final addCash = AddCash(repetition);
setState(() {
id = addCash.id;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
],
),
RaisedButton(
child: Text("Update Cash Flow"),
onPressed: createTodo,
),
],
),
),
);
}
}
I tried making a constructor inside AddCashPage
like this
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState();
}
I think you just missed to call setState() in here:
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
Like this:
ChoiceChipWidget(chipList, (item) {
setState(() => selectedItem = item);
}),
Then you could do this:
AddCash(selectedItem)
Make sure to declare the selectedItem variable inside _AddCashPageState, I don't see it on your code.
Your choice widget passes the data to the AddCashPage via the constructor you created, but you're missing something. You need to pass the data that AddCashPage has to its state (_AddCashState) so that you can use it there. Basically, you need to create one more constructor.
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState(choiceChipWidget: choiceChipWidget);
}
And in _AddCashPageState:
class _AddCashPageState extends State<AddCashPage> {
final choiceChipWidget;
_AddCashPageState({Key key, #required this.choiceChipWidget});
}
To use your passed data inside _AddCashPageState class you can use widget property of the corresponding state of the related Stateful class.
For Ex : To use choice chip widget in your class you can use it like widget.ChoiceChipWidget
Any properties/methods provided in AddCashPage class can be accessed in its State class _AddCashPageState() using widget.ChoiceChipWidget property;
You can use this widget property inside methods only like, initState(), build(), dispose() etc.