Map not updating rendered stateful child Widgets - flutter

I am using the following to spread Map values inside a stack. All widgets are positioned.
...widgets.values,
This works fine, however when I used the Map.update function to update the position of the Widgets at a certain key, there is no change to the widgets position.
if (widgets.containsKey(uniqueValue)) {
setState(() {
widgets.update(
uniqueValue,
(value) => Box(
uuid: uniqueValue,
startPosLeft: newPos,
startPostop: newPos,
onMoved: (uniqueValue) => _boxMoved(uniqueValue),
),
);
});
I can see the widget is being updated if I print out the Map Widget position, but it wont move.
Any idea what might be going wrong here?

The problem here was the state not being updated inside the child widget.
My solution was to create a behavioral subject with RXDart and bind the stream listen function in the widgets initState function.
Bloc Class
final boxMovementController = BehaviorSubject<String>();
Function(List<String>) get inputMovingBoxId =>
boxMovementController.sink.add;
Child Widget
streamSubscription = bloc.boxMovementController.listen((value) {
setState(() {});
});
The widget would then call setState whenever it detected an addition to the behavioral Subject stream.
The parent would then sink.add to the stream whenever positioned changed therefore triggering a state update on the child.
Parent Widget
bloc.boxMovementController(value);
Hopefully this is useful to others facing a similar issue.

Related

Obx widget throws an error: ''the improper use of a GetX has been detected..."

I am using only Text widget inside an Obx widget but I'm getting this issue
-
[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.
please solve this error.
This error is thrown when you use an Obx widget without using really any Rx<T> variables inside of it, so the library notifies you that there is no point wrapping some widget that will not update based on some Rx<T> with the Obx.
Example of what causes this:
Controller:
class ControllerExample extends GetxController {
Rx<String> exampleText = "example".obs;
}
View:
// Right
Obx(() => Text(Get.find<ControllerExample>().exampleText.value)), // Text widget takes an Rx<T> variable as value, so it will work fine
// Wrong
Obx(() => Text("anotherExample")), // this is just showing a Text widget which do not have any relation to an Rx<T> so it will throw the error you're facing
using Obx(()=>) without the Observable variable in view will throw this error.
you have to use observable variables like i.e.
RxString a = "".obs;
RxInt b = 0.obs;
RxBool c = false.obs;
you have to use .value when accessing value in the view.
Obx(
() => Text(controller.a.value),
),

Dart Widget created based on a list always uses initState of other Widget in List

The children of my column are created with the data of a List of Maps. `
children: [for (var alarm in alarmDataList) AlarmWidget(alarmData: alarm)],
`I do some changes to my alarms on another Screen. Before that, I delete the according alarmData.
To do that, I have a deleteAlarmDataList function which I call to delete an element of the List. To update my Screen, I call Setstate.`
void deleteAlarmDataList (Map alarm) {
alarmDataList.removeWhere((element) => element.hashCode == alarm.hashCode);
setState(() { alarmDataList; });
}
`Afterwards I call my addAlarmDataList function, which adds a Map to the List alarmData and sorts the Data. Afterwards the alarmDataList gets updated.`
void addAlarmDataList (Map alarm) {
if(alarm['time'] != 'noAdd') setState(() alarmDataList.add(alarm);});
setState(() {alarmDataList.sort((a, b) => a['time'].compareTo(b['time']));});
}
`My problem is, that I have a function which gets called in the initState of the class AlarmWidget which needs to be executed so all information is shown correctly. Unfortunately, in the process of the addAlarmDataList function, the initState of the last index of the alarmDataList is called, instead of the one I added. Also the initState is called after the whole addAlarmDataList function and not after the if clause.
I want the initState of the Widget with the latest added AlarmData to be called. I have tried to insert the insert the new Map at index 0, but this didn't help.
if(alarm['time'] != 'noAdd') setState(() {alarmDataList.insert(0, alarm);});
im not sure this will solve all your issue. let me help to bring you on track.
when you call setState, the build method will be re-executed.
#override
Widget build(BuildContext context) {
....
}
thats why, everytime you call the setState all widget inside the build method will be executed.
and thats caused the last index of the alarmDataList is called on your widget.

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.