Best practice: Update Model from View flutter - flutter

My question is small and is basically a request for advice on how to follow best practice in flutter. I have years of programming experience, but only a few weeks with flutter or dart.
Scenario:
To keep things simple, I have the following:
Model ClassA which has a List of ClassB. ClassB contains some String properties.
A StatefulWidget view that has a ClassA instance with a PageView builder based on the List of ClassB
Every page inside the PageView shows some String properties of ClassB in a TextField
Problem description:
Now, I want to update the properties in ClassB in realtime as the user types (or if the focus drops). There's an event for that and I created a method which accepts the ClassB instance. If I change the attribute in that instance, I see no changes to the original list. If I understand it correctly, dart passed the object by value to the event handler and not a reference to the original object. The original object remains unmodified.
In code this would be roughly the following:
Widget createTextField(ClassB classB) {
return EditableText(
onSubmitted: (value) {
setState(() {
classB.myStringValue = value;
});
}
);
}
PageView.builder(
itemBuilder: (BuildContext context, int index) {
var classB = widget.classA.myList[index];
return createTextField(classB);
},
}
)
My approach:
Instead of passing the instance of ClassB to the event handler, I just pass the index coming from the builder to the event handler instead. The handler will then directly modify the list inside ClassA using the index.
Now, having a strong Java background this somehow feels weird. My first instinct was to pass the instance of the ClassB to the event handler and modify the instance there. My assumption was that this would be a reference to the original Class B instance.
How would some of you approach this problem? It sounds easy enough, but I just want to follow the best practice here.

You should wrap the code that changes the value of a property inside
setState((){
// Put your codes here.
});

Related

Why does Flutter riverpod direct assignment does not work but methods do

Please check the two samples below.
The first sample does not rebuild the Widgets [Possibly 'listeners'
are not being 'notified']
The second sample works as
expected
To my understanding, i think all these two should work. Can someone brief me on the comprehension I'm lacking?
Thanks in advance.
Sample one (Does not rebuild) [ui changes do not take effect ]
onTap: (String? newValue) {
ref.watch(UserProvider).selectedMaritalStatusValue = newValue!;
UserModel().notifyAllListeners(); //triggers notifyListeners
},
Sample Two (Does rebuild)[working fine]
onTap: (String? newValue) {
ref.watch(UserProvider).setMaritalStatus(newValue!); // 'setMaritalStatus' has notifyListeners trigger within
},
Firstly, you should not use ref.watch inside any onTap callbacks. Use ref.read here instead. Read this for clarity on why this is the case.
Secondly, in your first code block where you write:
UserModel().notifyAllListeners();
UserModel() creates a new object altogether, and notifyAllListeners() is being called for this new object. This new object is not being watched inside the build method of this widget. This is why the first code block you posted fails to rebuild the widget.
Thirdly, as a best practice, methods like notifyListeners() and direct assignments of fields in any class should be done inside the class's code. Use your second code block as a reference in future. This is the correct and safest way.
You can use a private variable setter, then call notifyListeners in the setter after updating the variable like so:
class UserProvider extends ChangeNotifierProvider{
String? _selectedMaritalStatusValue;
String? get selectedMaritalStatusValue => _selectedMaritalStatusValue;
set selectedMaritalStatusValue(String? newValue){
_selectedMaritalStatusValue = newValue;
notifyListeners();
}
}
Now, this should work:
ref.watch(UserProvider).selectedMaritalStatusValue = newValue!;

What's the correct way to use methods from a sibling widget?

I am trying to clean my code by putting different widgets in different files/classes so it's not all inside the same main widget.
So right now I have this:
MainWidget {
Scaffold {
body: MyWebView(),
bottomNavigationBar: MyNavBar(),
}
}
Both are stateful widgets.
So now I'm wondering: how can I use methods from MyWebView from inside MyNavBar?
I've tried MyWebView.of(context).method() and even do a middle method inside MainWidget to try and call the methods from the parent widget always, but it's always "null". I've seen people mentioning PrivateKeys but many say this is an old way of doing things and I shouldn't rely on that.
So how can I do this?
I would need more details to provide an exact solution but based on your question this is the answer:
You have two options to do what you need
Lift Up The state:
This means In some situations, there are some states that are needed and shared among two or more sibling widgets, what we can do about it is to move those states to the parent widget, and provide it to the children, either by passing the variables and functions or using inherited-widget-based methods like using provider framework. so in your case, you can keep the function and state in the main widget, and pass it to both children.
Use a Key to access the MyWebView widget's state: as you mentioned in the comments, this is exactly done as you said :
you'd have to do it on the MainWidget, pass it onto the MyWebView(key: key), and then pass it to MyNavBar(webviewKey: key) to use it there
if you want to pass variable instead use typedef,
first declare outside MyWebview class :
let me guess, you need some int value from webview to pass it into navbar class.
typedef OndataProcessed = void Function(int valueIneed);
class MyWebView {
--------
final OndataProcessed ondataprocess;
and in methods in my webview
int someMethod (){
int result = blabla;
ondataprocess(result);
}
and finally :
MainWidget {
int resultfromwebview;
Scaffold {
body: MyWebView(ondataprocess: (int result){
setstate((){resultfromwebview = result})
}),
bottomNavigationBar: MyNavBar(index: result),
}
}

How best to make my scroll controllers available throughout the app?

Context: I'll be having a couple of scrollable lists in my app and I always want to scroll them to the latest item whenever an item is added.
Problem: My ListView.builders and the places where items are added are going to be quite far apart in my widget tree. Passing around all those scroll controllers via constructors seems to be super awkward.
My Solution:As I'm practising with Provider at the moment, I came up with a working solution using Provider:
class ScrollControllerProvider with ChangeNotifier {
ScrollController _paneController = ScrollController();
//setting up all other controllers here later
get paneController {
return _paneController;
}
void scrollHistory() {
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (_paneController.hasClients) {
_paneController.jumpTo(_paneController.position.maxScrollExtent);
}
});
}
}
I'll add all scroll controllers to that provider and grab what I need, where I need it. It already works with one, but someone on reddit told me it's not a good idea, as scroll controllers should be disposed. Im not super knowledgeable on the topic of life cycle yet and find it difficult to assess this.
Questions: Is it really a bad idea to use Provider here? Can you help me to understand why? If yes, what is the best approach to solve this issue?
Provider is not the problem, using a disposable item inside a provider is. ScrollController is a disposable item related to its main Widget, or better to say its State.
If you want to notify your widgets about newly added items, create a variable inside the provider and listen to that variable in your widgets, then use your ScrollController to change the position.
To find out more about your question take a look at ScrollController class and Disposable class
For posterity, Payam Asefi pointed me in the right direction.
How I'm doing it now.
tldr; Provider contains a value that can be toggled and a method to toggle it. I provide the value where I can also access the scroll controler. If it is toggled, the scroll conroler is used. I provide the method to toggle the value where I add new items to the list.
item is added > value in provider is triggered > listeners realized the value has changed calling the build method > scroll controller is used to go to maxscrollextend.
Long answer with code:
Provider with a) a bool that can be toggled b) a method to toggle the bool c) a getter for the bool
Code:
class ScrollControllerToggles with ChangeNotifier {
bool _historyPaneSwitch = true;
get getTogglePaneSwitch {
return _historyPaneSwitch;
}
void toggleHistoryPane() {
_historyPaneSwitch = !_historyPaneSwitch;
notifyListeners();
}
}
In the widget I'm using the Listview.builder: a) I define a scroll controller, b) I use a function dependent on the _historyPaneSwitch inside that Provider. That funtion also uses the scroll controller to scroll the list to the end.
void triggerScrollController() {
bool scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).getTogglePaneSwitch;
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (paneController.hasClients) {
paneController.jumpTo(paneController.position.maxScrollExtent);
}
});
}
In the widget adding new items to the list, I access the Provider again and grab the method to toggle "_historyPaneSwitch".
Function scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).toggleHistoryPane;
void dayChange(Function scrollHistoryPane) {
mainElementList.insert(0, MainElement(false, DateTime.now().toString()));
scrollHistoryPane;
}

is is ok if show progress indicator inside the viewmodel pattern?( Flutter question)

I'm using MVVM pattern in my project and i show the spinner inside the viewmodel by passing the build context to the view model method
for example :
void getSomeData(BuildContext context,int someDataID){
showSpinner(context);
}
the mentioned method getSomeData(....) is called from screen file
it works fine but i just wanna know will that make any problem?
Thanks
I think it is an ok pattern to use but it may cause problems in the future when your app is much bigger. You may either go to debug your viewmodel and view or you want to extend some functionality based on or closely related to the spinner and find it hard to navigate through your code to find what you want as the viewmodel and view are very intertwined.
Strictly speaking the viewmodel should direct the view via state variables held within the viewmodel, rather than allowing the viewmodel to make direct changes to the view via context. This helps keep all the build code in the view and all the logic that controls that in the viewmodel. Below is an example of what I mean using a ChangeNotifier as a viewmodel (using the provider package) e.g.
class ViewModel extends ChangeNotifier {
bool _showSpinner = false;
bool get shouldShowSpinner => _showSpinner;
void showSpinner() {
_showSpinner = true;
notifyListeners() // (Method for ChangeNotifiers) Or equivalent call to rebuild the view
}
void hideSpinner() {
_showSpinner = false;
notifyListeners() // (Method for ChangeNotifiers) Or equivalent call to rebuild the view
}
}
class View extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ViewModel>(
create: (context) => ViewModel(),
child: Consumer<ViewModel>(
build: (context, model, child) {
return Stack(
children: [
SomePageContents(),
if (model.shouldShowSpinner) Spinner(),
],
);
}
)
);
}
}
Just to reiterate, I think this helps maintainability in the long run but if your method works for you, i.e. the scope of your project is relatively small and your original way doesn't cause too many dependency issues then I'd say do it your original way as the MVVM method should help not hinder development.

Observable Lists in flutter with mobx

I am trying to make an observable list (i.e. when an element of the list is changed, removed, or added, I would like the UI to updated). I know that mobx has something called an "observableList", and so this seems like it should be possible. However, I am having issues implementing it. I currently have the following code in my mobx store file:
var metrics = Observable([.5,.5,.5]);
Then later, I try to change one of the elements like so:
metrics[index] = data;
I get the error:
The method '[]=' isn't defined for the class 'Observable>'.
Is there a way to create an observable list (or better yet, an observable dictionary) in flutter, or is that not implemented yet?
Thanks!
With MobX you need to create methods annotated with #action to be notified about changes over an Observable.
In your Store you must have something like
#observable
var metrics = ObservableList<int>([.5,.5,.5]);
// This is action method. You need to use this method to react
// your UI properly when something changes in your observable list.
#action
void addItem(float data) => metrics.add(data);
// the same for this method but with a different operation.
#action
void removeItem(float data) => metrics.remove(data);
#In your UI layer
Observer(
builder: (context) =>
ListView.builder(
itemCount: yourStore.metrics.length,
builder: (_, index) => Text('${yourStore.metrics[index]}'),
),
);
In this case after some change in yourStore.metrics observable list the Observer builder callback will be fired and the list view will be updated.
You can use my code. You need to add ".of" to initialize the list
ObservableList<int> values = ObservableList<int>.of([1,2,3]);
Later you can use it like this.
values[index] = data;