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;
}
Related
I need to update a widget whenever the widget that's nested to it is updated.
Let's say widget A is nested in widget B, by the way we can have GlobalKey of widget A (if it can help to detect if the widget A is updated). Here, I need to update widget B whenever the widget A is updated, and in order to do it I need to check if the widget A is updated, if it is then I'll update widget B too.
You could do this for example:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: B()));
}
class B extends StatefulWidget {
const B({super.key});
#override
State<B> createState() => BState();
}
class BState extends State<B> {
#override
Widget build(BuildContext context) {
print('B updates');
return Scaffold(
body: Column(
children: [A(updater: update)],
));
}
void update() {
setState(() {
});
}
}
class A extends StatefulWidget {
final Function? updater;
const A({Key? key, this.updater}) : super(key: key);
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
void setState(VoidCallback fn) {
if (widget.updater != null) widget.updater!();
super.setState(fn);
}
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
setState(() {
print('clicked button');
});
},
child: const Text('click'));
}
}
Whenever the button is clicked it prints both "clicked button" as "B updates". So B rebuilds on each click. If you leave out the setState override in A you will see only the "clicked button" prints.
I need to realise row of TextField widgets. I did it. But now I want to get actual value TextEditingController from State my TextField. How I can do this?
Its my Stateful widget:
class _UnRecognition extends StatefulWidget {
final String text;
const _UnRecognition({Key? key, required this.text}) : super(key: key);
#override
State<StatefulWidget> createState() => _UnRecognitionState();
}
class _UnRecognitionState extends State<_UnRecognition> {
final TextEditingController editWordController = TextEditingController();
#override
void initState() {
editWordController.text = widget.text;
super.initState();
}
#override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: TextField(
controller: editWordController,
),
);
}
Its, where i want to use my variable:
void _SomeMethodOutsideWidget() {
for (var obj in ListOfWidget) {
if (obj is _UnRecognition) {
**this I get access obj.editWordController.text**
}
}
}
you need to learn the providers: https://pub.dev/packages/provider
basically it is used to retrieve values in the contexts of Statefull Widgets
you can find many tutorial of state managment provider on youtube
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 Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}
I am trying to refresh the parent widget from sub children widget. Actually, there are a number of widgets in between like A uses B and B uses C. I would like to refresh A widget on an event in C widget.I researched a lot but couldn't find an exact answer. A code snipped will be really helpful. Thanks in advance
There are a few solutions:
A pass a callback that does a setState to B, which then pass it to C:
class A extends StatefulWidget {
#override
_AState createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return B(
onSomething: () => setState(() {}),
);
}
}
class B extends StatelessWidget {
final VoidCallback onSomething;
const B({Key key, this.onSomething}) : super(key: key);
#override
Widget build(BuildContext context) {
return C(onSomething: onSomething);
}
}
class C extends StatelessWidget {
final VoidCallback onSomething;
const C({Key key, this.onSomething}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: onSomething,
);
}
}
use NotificationListener in A, and dispatch a Notification from C:
class MyNotification extends Notification {}
class A extends StatefulWidget {
#override
_AState createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: (_) {
setState(() {});
},
child: B(),
);
}
}
class C extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
MyNotification().dispatch(context);
},
);
}
}
Why do you want to refresh? You updated any data and wanted the new ones to be displayed?
You could try to use Provider widget. With it you can modify any data and notify everyone interested in that data that it changed.
In your setup you could put the provider on the A widget, on the C widget you could get the value, updated and notify everyone. When doing that, A widget will automatically rebuild with the updated information.
The code would be something like this:
class AppState with ChangeNotifier {
AppState();
YourData _data;
void setData(YourData data) {
_data = data;
notifyListeners();
}
}
ChangeNotifierProvider.value(
value: AppState(),
child: WidgetA()
)
WidgetC() {
Provider.of<AppState>(context).setData(yourChangedDataHere);
}