I have an issue with the provider pattern, I have a solution to my problem i just wanted to check its a good method and if there a better alternates.
I am trying to update the text controllers text when they first load the widget to have the value stored in my provider state. Originally, I just created the text controller in the init and just loaded the state in the build method and set the controllers text.
This was working until i wanted to have some validation of the content that would prevent certain things being passed into the state ( for example preventing empty strings).
textController.addListener(() {
if( RULE ABOUT WHETHER TO SET STATE BASED ON TEXT){
state.textField = textController.text;
}
});
The problem arose here, as they user would delete their word and this would fail the rule. The rebuild would occur (as they had pressed a key) and the text.controller would then rebuild to what was in the state (a single letter as it failed the rule). Effectively this prevented them from deleting, which is something i want them to be able to do but for this not to update the state.
I found this library https://pub.dev/packages/after_layout which allows a method to be overridden which is called once immediately after the build.
My solution uses this by creating the text controller in the initstate.
Assigning it to the TextField in the build.
Then creating a local field that would maintain the textField widget string content.
Then in the new afterbuild method i would assign whats on the state to the variable and update the text controllers text to have this value.
Then in the text.controllers text changed listener I used my rule to only update this state if it met the rule. Therefore if they deleted content they shouldn't be able to and closed/reopened the widget the state would rebuild to still have a valid value.
Is using this library a good solution to this or is there a better technique?
---------------- EDIT
Sorry for my wordy first part. I think my main question is just how am i meant to init Controllers if the data i need has to come from the state in the build.
For example.
// This must go in the build as it requires state
myTabsController = TabController(length: myState.list.length, vsync: this);
I'm initialisng the controller every time it builds now... How am i meant to put this in the init but still access the state variables.
(I've tried using the afterFirstLayout() callback from the AfterLayoutMixin but that just causes more problems.
Feel free to ask me to clarify more.
Thanks for your help.
Related
I'm experimenting with MobX state management solution in Flutter. I really like its conciseness and its simplicity but I'm a bit concerned in the drawbacks its way of mutating the state can introduce.
Theoretically , we can force the state to be mutated only using actions and this is great because we can have a history of the changes using the spy feature the package provides.
However, actions are automatically created by the framework if we do not use a specific one. For example, in the code below, I created a counter store class and I used it in the main function to assign a new value for the counter without using a predeclared action. The stric-mode has been previously set to "always".
abstract class _Counter with Store {
#observable
int value = 0;
#action
void increment() {
value++;
}
}
final counter = Counter(); // Instantiate the store
void main() {
mainContext.config = mainContext.config
.clone(isSpyEnabled: true, writePolicy: ReactiveWritePolicy.always);
mainContext.spy((event) {
print("event name : " + event.name);
print("event type : " + event.type);
});
Counter counter = Counter();
counter.value = 10;
}
I was expecting an error to raise saying that it is not possible to change the state outside of action but this does not happen because an ad hoc action is created , called value_set, automatically. I'm wondering ,then, which is the point of forcing to use actions if it is possible to avoid using them. I mean, if we directly change the state, an action is created automatically and seen by the spy functionality making all the process more robust and predictable but, what if I want the state to be changed only by actions I previously implemented? Is there a way of doing so? For example, in the counter code I just provided , is there a way of making the counter incrementalbe only by 1? Because, in the way Mobx works, I could write everywhere in the code something like
counter.value = 10
and this will work just fine without any problems. Forcing people to use preset actions could increase the predicatability a lot and facilitate the teamwork too I think.
I tried to make the value variable private but it still remains accessible from the outside , also if annotated with #readonly.
Hm, that looks weird for me too, because it does work differently in MobX JS (exactly like you describing), but it seems that Dart MobX changed behaviour for single field values and they are automatically wrapped in actions now, yes.
Maybe there should be a optional rule to turn strict checking on again, it would make sense. I suggest you to create and issue or discussion on Dart MobX Github.
More info there: https://github.com/mobxjs/mobx.dart/issues/206
At the end, I found out that the behaviour I was looking for was obtainable with the #readonly annotation. Marking a variable with #readonly annotation avoids automatically creating its setter and getter methods. Moreover it requires the annotated variable to be private, denying the variable to be set directly.
Note: By the way, the way the Dart language is implemented, it is necessary to locate the Counter in a separate file to provide the desired behaviour, otherwise its private fields would be accessible anyway.
Maybe my question is a nooby one, but i really try to understand how to implement the following operations:
How to add item for a list without the need of rebuilding all list.
How to update a class instance property value.
Does BLoCProvider used only once in a class?
===> I'm asking those question because i see that BLoC working with 'Equatable' and that in a the state class there is always a need to bring default value to the state we provide to the view class.
Another Question (what change the view and operate the state):
Does i need to bring always a list in order for example to update an item from the list or only changing the state+emit(in cubit) will change the state of the application, maybe it realted to BLocListener?
I use MultiProvider and then create all my models. Lazy loading is enabled and as such when I open my page widget the constructor of my model is called when I call Provider.of<>(context).
This initialize my model and the model gets fresh data.
I have the following issue however, when I pop the view(widget) and revisit the view(widget) later, Provider.of<>(context) is called again, but since the model was already initialized I get the previous data from the model (This is useful because I do use this to preserve state between certain screens).
I need my model to reinitialize since I need to refresh my data and reset the page values, and since the constructor is never called again, I don't get any of these.
No matter what I do, if I call the initialize method from initState() / didChangeDependencies() it always error since I'm changing the data while the widget is building.
I'm looking for something like the following:
MyChangeNotifier variable = MyChangeNotifier();
ChangeNotifierProvider.value(
value: variable,
child: child()
)
To reinitialize my class, but from what I read this is bad and don't know where to call it.
I have no idea how to proceed and any help would be appreciated.
So I found what I was looking for in the Provider actual documentation here.
The key is to call your code that would update the UI or trigger a rebuild inside a Future.microTask(). This will only then trigger the rebuild once the future completes and not trigger the rebuild while the widget tree is still building.
#override
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).getMyData(); // Or in my situation initialize the page.
);
}
I would like my toggle buttons to be layout in a table, and not as a single row.
The number of toggle buttons is not static -
that is upon init I load a resource which contains a list of all the texts that should become the toggle buttons.
Looked at a number of approaches, each has its issues:
Create a list of ToggleButtons and a list of lists of bools to store the appropriate selected state as a data structure to divide the toggle buttons into a number of rows. The problem with this approach is in the implementation of the onPressed method - how to get a reference to the appropriate element in the list of lists of bools? Or in other words - how to get a reference to ToggleButtons object from within the onPressed method?
Use key property to pass the index of the current ToggleButtons. It is not intended for this purpose, so it is a bad practice, also again, there seems to be no straightforward way to access the key property from the onPressed method.
Extend the ToggleButtons class, and specifically override its build method. This is considered an anti-pattern in Flutter in general. Specifically In this approach, as we want all the functionality to remain the same, and change only the internal Row -> Table widget generation in the build method, it looks like we would have to duplicate all the code of this method, which is a bad idea as it might brake things as it changes in future versions of this widget
Create a table of checkbox / switch widgets as an alternative, which should work easily, but I want the look & feel of toggle buttons, not checkboxes or switches :)
I must be missing something simple!
After posting this I have a new idea :)
A table of FlatButtons! Will be probably possible to achieve similar UI to ToggleButtons. Will try it in a bit.
I would still love to hear other suggestions regarding ToggleButtons.
Try using SwitchListTile.
The entire list tile is interactive: tapping anywhere in the tile toggles the switch. Tapping and dragging the Switch also triggers the onChanged callback.
To ensure that onChanged correctly triggers, the state passed into value must be properly managed. This is typically done by invoking State.setState in onChanged to toggle the state value.
The value, onChanged, activeColor, activeThumbImage, and inactiveThumbImage properties of this widget are identical to the similarly-named properties on the Switch widget.
The title, subtitle, isThreeLine, and dense properties are like those of the same name on ListTile.
The selected property on this widget is similar to the ListTile.selected property, but the color used is that described by activeColor, if any, defaulting to the accent color of the current Theme. No effort is made to coordinate the selected state and the value state; to have the list tile appear selected when the switch is on, pass the same value to both.
The switch is shown on the right by default in left-to-right languages (i.e. in the ListTile.trailing slot) which can be changed using controlAffinity. The secondary widget is placed in the ListTile.leading slot.
To show the SwitchListTile as disabled, pass null as the onChanged callback.
TextEditingController.text has the following docs:
Setting this will notify all the listeners of this [TextEditingController] that they need to update (it calls [notifyListeners]). For this reason, this value should only be set between frames, e.g. in response to user actions, not during the build, layout, or paint phases.
This property can be set from a listener added to this [TextEditingController]; however, one should not also set [selection] in a separate statement. To change both the [text] and the [selection] change the controller's [value].
How am I supposed to update the TextEditingController.text inside a build method if I want then?
Also, is this limitation regarding performance issues, as to no lock up the build method with the listeners execution? Or is it something else?