How to make individual GetxControllers for reusable widget - flutter

I am trying to replace the use of Stateful widget with Getx. Most cases, I do not need it, however, when I am trying to create a list of cards widget, I find it hard to not to use the Stateful widget.
Obx(() {
getxA.updateA(name);
return Text(getxA.a());
})
RxString a = 'aaa'.obs;
updateA(String name) {
a(name);
}
When I have this Obx() - Text Widget inside the List Widget, it causes the error because each Text widget triggers updateA(String name) function.
It looks like every card is sharing one GetxController (getxA). Is there any way that to have each Text widget have its own GetxController? Or is it not possible in this case and I have to use the Stateful widget?

Related

How to find a child widget of certain type by context in Flutter?

I have a Statefull/Stateless widget I need to check if the widget contains certain type of widget as its child.
Example:
Statefull Widget -> Container() => Column() => [Text(), Expanded() -> ListView()]
Here I need to check if the Statefull widget consists ListView() through its context...
I think what you are looking for is byType method.
This will find widgets by searching for widgets with a particular type.
So in your case, you'll have something like this:
expect(find.byType(ListView), findsOneWidget);

how flutter's find() finds widget on widget testing?

I'm learning flutter's widget testing
and I'm reading flutter's official documentation of widget testing.
I wonder how find() method finds the widget.
find.byKey() may find by look around widget tree that which widget has specific key,
but like find.byWidget() how does it finds specific widget?
The find.byWidget() method finds a widget by checking if it is equal to the widget passed as an argument to the method. This is typically done by checking if the runtimeType and key of the two widgets match.
For example, let's say you have a Text widget with a specific key and you want to find it using the find.byWidget() method. You would first create the Text widget and assign it a key, like so:
final myText = Text(
'Hello World',
key: Key('my_text'),
);
Then, you can use the find.byWidget() method to find this widget in the widget tree, like this:
final foundWidget = find.byWidget(myText);
This will return the Text widget if it is found in the widget tree, or null if it is not found. You can then use this widget reference to perform assertions or interact with the widget in your tests.
Keep in mind that the find.byWidget() method only checks for strict equality between the two widgets. This means that if you have multiple widgets with the same runtimeType and key, the find.byWidget() method will only return the first widget it encounters that matches the criteria. To find all widgets that match the criteria, you can use the find.descendant() method instead.
going inside Flutter's source code, we will find that the find.byWidget() is implemented like this:
Finder byWidget(Widget widget, { bool skipOffstage = true }) => _WidgetFinder(widget, skipOffstage: skipOffstage);
going more inside _WidgetFinder:
class _WidgetFinder extends MatchFinder {
_WidgetFinder(this.widget, { super.skipOffstage });
final Widget widget;
#override
String get description => 'the given widget ($widget)';
#override
bool matches(Element candidate) {
return candidate.widget == widget;
}
}
so for flutter to find a specific widget, it compares it with the == operator with the one you provided.

When using GetX observables, which UI widgets need to be wrapped in Obx?

To learn GetX I created a simple controller class:
class MyDataController extends GetxController {
RxString aString = ''.obs;
void updateString(String s) {
aString.value = s;
}
}
aString's value is displayed in two classes: the AppBar (not discussed here) and another class in which aString is both set and displayed:
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyDataController controller = Get.find();
final textController = TextEditingController();
return Column(
children: [
TextField(
controller: textController,
onChanged: (_) {
controller.updateString(textController.text);
},
),
Text(controller.aString.value),
],
);
}
}
I'm confused about which widgets need to be wrapped in an instance of Obx().
If I wrap only the Text() (display widget) in an Obx instance, it's updated when the TextField() (input widget) changes. And if I wrap only the TextField() widget in an Obx instance, I get an error message:
The following message was thrown building Obx(has builder, dirty,
state: _ObxState#019a0):
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
Everything seems clear: widgets displaying state must be wrapped in Obx() instances to display updated variables. That makes perfect sense. And widgets that change state don't need to be wrapped in Obx() instances.
I'm confused, though, because if I wrap both widgets in separate Obx() instances, I get the error message. But if I wrap the entire Column() in an Obx() instance, the text is properly updated when the TextField() changes. ... What am I missing in my understanding?
You got it all up to the point when you wrapped the Text widget in an Obx. In actual fact it is best to wrap only the smallest widget that would need updating in Obx, the Text widget in this case.
Let me explain what happened the cases in which you tried testing it out:
Case 1: When you wrapped only the Text widget in Obx (The best thing to do)
This case happen to be the best and most encouraged approach. In this scenario when the value of aString changes in the controller (MyDataController) the Obx is notified to re-build only the affected Text widget from scratch, and this is exactly the aim of GetX.
Case 2: When you wrapped both the Text && the TextField widget in Obx (This will throw an error).
In this case you have wrapped both the Text and TextField widget in Obx, we can therefore let case 1 account for the Text widget.
Now, moving unto the TextField widget, an error will occur because the TexField widget is not in any way dependent on any obs-value (observable value).
It is important to note that in the onChanged callback provided to the textField, the method updateString called on the controller have no effect whatsoever on TextField's parameter and thus this leads GetX to throw an error since you are trying to forcibly update/re-build a widget that needs no rebuilding.
Case 3: When you wrapped the whole column in Obx (Will not throw an error but not the best practice).
In this case the widget will be built with no error whatsoever since the Text widget (which is inside the Column) is dependent on the value of aString. So, let's see what happens when the method updateString is called.
When the updateString is called the whole Column is re-built (along with the TextField and the Text widgets and this action will as well cause the value in the Text widget to be updated.
Now, you can see why this third case can be detrimental, if you try wrapping your whole app in Obx, your whole app will then have to get re-built (which can really affect your app's performance negatively. Of course GetX has a way of disallowing that and thus it throws an error when you try wrapping an HeavyWidget in Obx or GetX.

Flutter: display dynamic list

I've got a widget that displays a classes' list, but that list changes (the content changes) over time by other elements and interactions of the program. How can the DisplayListWidget detect this? Do the elements that change this list have to communicate with the displaylistwidget?
EDIT: I'm familiair with stateful widgets and setState () {}. It's just that the data changes in the background (e.g. by a timer) so there's no reference from bussiness logic classes to widgets to even call setState.
If you would like to notify (rebuild) widgets when data changes, check out provider package.
There are some other options:
BLoC (Business Logic Component) design pattern.
mobx
Redux
Good luck
Try wrapping the display widget in a state widget, and then whenever you update the list, call setState(() { //update list here })
ex.
// this is the widget that you'd nest directly into the rest of your tree
class FooList extends StatefulWidget {
FooListState createState() => FooListState();
}
// this is the state you will want to call setState on
class FooListState extends State<FooList> {
Widget build(BuildContext context) {
return DisplayListWidget [...]
}
}
I would recommend following this flutter tutorial where they dynamically update a list
And to learn more about StatefulWidgets and States check this out: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html

Understanding Flutter Render Engine

The docs here about how to update a ListView say:
In Flutter, if you were to update the list of widgets inside a
setState(), you would quickly see that your data did not change
visually. This is because when setState() is called, the Flutter
rendering engine looks at the widget tree to see if anything has
changed. When it gets to your ListView, it performs a == check, and
determines that the two ListViews are the same. Nothing has changed,
so no update is required.
For a simple way to update your ListView, create a new List inside of
setState(), and copy the data from the old list to the new list.
I don't get how the Render Engine determines if there are any changes in the Widget Tree in this case.
AFAICS, we care calling setState, which marks the State object as dirty and asks it to rebuild. Once it rebuilds there will be a new ListView, won't it? So how come the == check says it's the same object?
Also, the new List will be internal to the State object, does the Flutter engine compare all the objects inside the State object? I thought it only compared the Widget tree.
So, basically I don't understand how the Render Engine decides what it's going to update and what's going to ignore, since I can't see how creating a new List sends any information to the Render Engine, as the docs says the Render Engine just looks for a new ListView... And AFAIK a new List won't create a new ListView.
Flutter isn't made only of Widgets.
When you call setState, you mark the Widget as dirty. But this Widget isn't actually what you render on the screen.
Widgets exist to create/mutate RenderObjects; it's these RenderObjects that draw your content on the screen.
The link between RenderObjects and Widgets is done using a new kind of Widget: RenderObjectWidget (such as LeafRenderObjectWidget)
Most widgets provided by Flutter are to some extent a RenderObjectWidget, including ListView.
A typical RenderObjectWidget example would be this:
class MyWidget extends LeafRenderObjectWidget {
final String title;
MyWidget(this.title);
#override
MyRenderObject createRenderObject(BuildContext context) {
return new MyRenderObject()
..title = title;
}
#override
void updateRenderObject(BuildContext context, MyRenderObject renderObject) {
renderObject
..title = title;
}
}
This example uses a widget to create/update a RenderObject. It's not enough to notify the framework that there's something to repaint though.
To make a RenderObject repaint, one must call markNeedsPaint or markNeedsLayout on the desired renderObject.
This is usually done by the RenderObject itself using custom field setter this way:
class MyRenderObject extends RenderBox {
String _title;
String get title => _title;
set title(String value) {
if (value != _title) {
markNeedsLayout();
_title = value;
}
}
}
Notice the if (value != previous).
This check ensures that when a widget rebuilds without changing anything, Flutter doesn't relayout/repaint anything.
It's due to this exact condition that mutating List or Map doesn't make ListView rerender. It basically has the following:
List<Widget> _children;
List<Widget> get children => _children;
set children(List<Widget> value) {
if (value != _children) {
markNeedsLayout();
_children = value;
}
}
But it implies that if you mutate the list instead of creating a new one, the RenderObject will not be marked as needing a relayout/repaint. Therefore there won't be any visual update.