I'm getting serious about communication between the widgets in my app.
I have a bar that has three states: Information, copy mode and options. When in info, only the date is displayed with DateFormat(). When in options, the information is changed by two buttons: copy and paste. When the copy button is used then the content of the bar is changed to a single button (paste).
To switch between information and options states there is an animated button that alters the value of a provider.
The button that allows to change from one state to another is in a StatefulWidget. The bar is a StatelessWidget that has a Consumer that makes it rebuild every time the button changes value.
So far so good. However, I would like that when clicking the paste button (which is in the Stateless) the setState() method of the button is called so that inside it uses the animation controller and calls reverse()...
The button starts with a menu icon and then turns into a cross to close. The problem is that when using the paste button, the button stays on the cross, it does not return to show the menu icon.
This is the code...
The bar:
class OptionsBar extends StatelessWidget {
const OptionsBar({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Row(
children: [
Expanded(
child: Consumer<OptionsBarProvider>(
builder: (_, optionsController, __) => AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: optionsController.state == EditorState.copyMode
? TextButton.icon(
onPressed: () {
optionsController.setState(EditorState.information);
},
icon: const Icon(Icons.paste),
label: const Text('Pegar'))
: optionsController.state == EditorState.options
? const ButtonsBar()
: Consumer<ShiftSystem>(
builder: (_, backend, __) => Text(
backend.getFormattedCurrentDay(),
style: Theme.of(context).textTheme.subtitle1,
textAlign: TextAlign.center,
),
),
),
),
),
const OptionsBarButton(), // This is the button widget!
],
),
);
}
}
The button:
class OptionsBarButton extends StatefulWidget {
const OptionsBarButton({Key? key}) : super(key: key);
@override
State<OptionsBarButton> createState() => _OptionsBarButtonState();
}
class _OptionsBarButtonState extends State<OptionsBarButton>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
bool _isPlaying = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
}
@override
Widget build(BuildContext context) {
final OptionsBarProvider optionsController =
context.read<OptionsBarProvider>();
return IconButton(
onPressed: () {
if (optionsController.state == EditorState.options ||
optionsController.state == EditorState.copyMode) {
optionsController.setState(EditorState.information);
} else {
optionsController.setState(EditorState.options);
}
setState(() {
_isPlaying = !_isPlaying;
if (_isPlaying) {
_controller.forward();
} else {
_controller.reverse();
}
});
},
icon: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,
));
}
bool returnToMenuIcon() {
setState(() => _controller.reverse());
return true;
}
}
The buttonBar:
class ButtonsBar extends StatelessWidget {
const ButtonsBar({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final optionsController = context.read<OptionsBarProvider>();
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton.icon(
onPressed: () {
optionsController.setState(EditorState.copyMode);
},
icon: const Icon(Icons.copy),
label: const Text('Copiar'),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.delete),
label: const Text('Eliminar'),
),
],
);
}
}
The provider:
class OptionsBarProvider extends ChangeNotifier {
late EditorState state;
OptionsBarProvider() {
state = EditorState.information;
}
bool get copyMode => state == EditorState.copyMode;
void setState (EditorState value) {
state = value;
notifyListeners();
}
}
CodePudding user response:
To fix the code you should remove the setState
in the build of OptionsBarButton
and reverse or forward the animation depending on the EditorState
. To listen for dependency changes, like the provider EditorState
, override the didChangeDependencies and only then call forward/reverse.
It should be this:
class OptionsBarButton extends StatefulWidget {
const OptionsBarButton({Key? key}) : super(key: key);
@override
State<OptionsBarButton> createState() => _OptionsBarButtonState();
}
class _OptionsBarButtonState extends State<OptionsBarButton>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
bool _isPlaying = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
}
@override
void didChangeDependencies() {
final OptionsBarProvider optionsController =
context.read<OptionsBarProvider>();
if (optionsController.state != EditorState.information) {
_controller.forward();
} else {
_controller.reverse();
}
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final OptionsBarProvider optionsController =
context.watch<OptionsBarProvider>();
return IconButton(
onPressed: () {
if (optionsController.state == EditorState.options ||
optionsController.state == EditorState.copyMode) {
optionsController.setState(EditorState.information);
} else {
optionsController.setState(EditorState.options);
}
},
icon: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,
));
}
bool returnToMenuIcon() {
setState(() => _controller.reverse());
return true;
}
}