Load data asynchronously into ChangeNotifier model in Flutter - flutter

in my Flutter application I have a widget
class HomeScreen extends StatelessWidget
that uses a model
class HomeScreenModel extends ChangeNotifier
These two objects are tied together using a ChangeNotifierProvider.
When the application loads the HomeScreen widget I would like to call a custom init() function of HomeScreenModel to load asynchronously some data from the disk into the model and then notify the listener with notifyListeners() function. This should be done once.
What's the right place to call this init() function?
As far as I know, for a stateless widget there are no lifecycle functions called only once. I'm pretty sure, though, that the constructor of HomeScreenModel is called only once.
Is it safe to call the async HomeScreenModel.init() function from its own constructor?
Is there any best practice on how to asynchronously load data into a model implemented as a ChangeNotifier?
Thanks to all!

After a bit of searching and tests I choose to call the async init function from the HomeScreenModel constructor. So I have
HomeScreenModel(BuildContext context) {
var initFuture = init(context);
initFuture.then((voidValue) {
_log.d('init finished');
state = HomeScreenModelState.initialized;
notifyListeners();
});
}
and the init function prototype is
Future<void> init(BuildContext context) async
I have found that another way to do this is to use a StatefulWidget and call the async init from the
initState()
function. This function is called only once so is like the ChangeNotifier constructor.
As of now I'm not using StatefulWidgets because it seems to me that they create a sort of strong coupling between ui and business logic. So as of now the above solution seems fine to me.
I hope it can help someone

Another way to call this function would be to put the init() call into the create function of the provider.
runApp(ChangeNotifierProvider(
create: (context) {
var model = TestModel();
model.init();
return model;
},
child: TestApp()));

Related

why use initState() in flutter, when we can just initailize a variable in the very first line of code

is there a difference in these 3 codes:
First: when i call my function inside onInit().
#override
void onInit() {
super.onInit();
fetchProductsFromAPI();
}
Second: when i call my function inside of build method, in stateless widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
fetchProductsFromAPI();
return GetMaterialApp(
home: ShoppingPage(),
);
}
}
Third: when i call my function outside of build method, in stateless widget.
class MyApp extends StatelessWidget {
fetchProductsFromAPI();
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: ShoppingPage(),
);
}
}
Yes, there is a difference. You can read about flutter widget life cycle to have more details:
Life cycle in flutter
https://medium.flutterdevs.com/app-lifecycle-in-flutter-c248d894b830
In summary
When you call your method outside of build method (your 3rd example).
This is what is usually recommended when you can do it.
See is there any difference between assigning value to the variable inside of initState or not in Flutter StatefulWidget?
This will be run only once, when the class is created.
Inside the initState (your 1st example)
At this moment, your widget is being created. Some getters are already available, like the context. This method is called only once.
Inside the build method (your 2nd example)
This is usually the worst approach. Your method will be called for each and every build (you can consider 1 build = 1 frame) which can lead to poor performances. It is recommended to move those calls out of the build method when possible (and if it makes sense)
See How to deal with unwanted widget build?
First:
Put it on initState then the function fetchProductsFromAPI will only call first time your widget create
Second:
I highly recommend you do not use this approach, because build method will be trigger many time when widget need to rebuild, if you put it there, your app will be fetchProductsFromAPI at a lot of unexpected times.
Example when you need to call setState() for some changes, you don't want to call fetch API
Third:
This way will cause compile error, I don't think you can put it there like your code above

Flutter GetX: Where Does Get.put Go in a Widget?

I am new to GetX and am trying to learn how to use it. I have read different tutorials that inject the controller outside of the widget's build method, and others that put it inside it.
class MyWidget extends StatelessWidget{
const MyWidget({Key? key}) : super(key:key);
//Outside...
final controller = Get.put(Controller()); //<---
#override
Widget build(BuildContext context) {
//Inside...
final controller = Get.put(Controller()); //<---
return Obx(
() => Text(controller.name)
);
}
}
Is there a difference between those two locations? If so, why?
Also, where should it go in a StatefulWidget? It seems it should not go inside the build method because it causes a stack overflow error for me.
Does the location of Get.put() matter inside a widget?
The normal way is to put the controller outside the widget, so it will be created once. if you but it inside the widget a new instance of the controller will be created each time you refresh (update) the widget.
Also, with GetX there is no need to use StatefulWidget.
When you put it inside the build method, it will create a new instance every time the widget is rebuilt, so it is better to but it outside and in most cases I think you do not need to use StatefulWidgetwith GetX even in animation.
The answers shared are correct. Thank you! I wanted to post another answer that I consider to be an even better way to do it as referenced in the documentation.
This method allows for controller access without ever putting Get.put in any of your widgets. This is also really helpful if you have a lot of controllers and need to reference them in widgets as well as from other controllers.
Here is an example:
//ControllerA
class ControllerA extends GetxController {
static ControllerA get to => Get.find();
final name = 'Bob'.obs;
someMethod(){
ControllerB.to.anotherMethod(); //'I am inside ControllerB!'
}
}
//ControllerB
class ControllerB extends GetxController {
//+++
static ControllerB get to => Get.find();
anotherMethod(){
print('I am inside ControllerB!');
}
}
And then inside Widgets:
class MyWidget extends StatelessWidget{
const MyWidget({Key? key}) : super(key:key);
#override
Widget build(BuildContext context) {
return Obx(
() => Text(ControllerA.to.name) //Bob
);
}
}
This requires that you add your Get.put declarations in main.dart so that you make sure they are all ready:
void main() {
Get.put(ControllerA());
Get.put(ControllerB());
runApp(
GetMaterialApp(...)
);
}
In my opinion, it's really clean like this--and very convenient!
If we want to keep a GetX controller in memory forever, then we should indeed instantiate it outside of a Widget's build() function (such as within main() as Clifton shows).
(Note that we can also use GetXService for persistent controllers, which allows manual disposal.)
Placing controllers inside build() functions, is the correct place when we want GetX to free memory & dispose the controller when the widget goes "out of scope". (e.g. when the user has "popped" the route from the stack.) There's also no danger in "recreating" the controller when its instantiated inside build(): Get checks for existence of a controller when Get.put() is called and skips it if the controller is already instantiated.
See this answer (and link to an explanation by one of Get's maintainers) for more info on why we should Get.put() inside build().

How do I call a function within my main page from another file?

I have the following problem:
My Stateful Widget "HomePage" has an Function:
void refresh() {
//not important for now
}
But, my whole save and load functions are located in an extra file (save_load.dart)
It's not a class or whatever, just pure functions that I can call from every other file.
void loadList(String key) {
//not important as well
//The following line is what I basically want to achieve
HomePage.refresh();
}
My Problem now is: I want to call the function refresh() after/within the loadList() function. Do you have an idea on how I can achieve this?
Thank you so much!
Summary
The easiest way to achieve what you're looking to do is to use a GlobalKey<HomePageState> to reference the currentState of your HomePage widget and then call the refresh method.
Solution
First: Create your Global Key
Create your global key and pass it into your HomePage when you create it.
final GlobalKey<HomePageState> homeKey = GlobalKey<HomePageState>();
...
// In one of your build methods where you're creating your `HomePage` widget.
HomePage(key: homeKey, ...)
Second: Get The Current State
Next, you'll need to get access to the current state of your HomePage widget.
final homePageState = homeKey.currentState;
Last: Call Refresh
Finally, you'll want to call the refresh method using the state class you retrieved from the GlobalKey.
// This is how you access it safely
homePageState?.refresh(); // will only call refresh if the homePageState is not null
// OR
homePageState!.refresh(); // will throw if homePageState is null

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

ChangeNotifier on non-widget classes

We've just started developing our first Flutter app and we are running into a small problem with ChangeNotifier applied to a non-widget class and the disposal of objects of this class. We get exceptions when a future returns after the class has been disposed, we call notifyListeners after the future returns.
I've seen questions suggesting to use the mounted property to check if the object is disposed, but this is only available in widgets. The ChangeNotifier class uses it's own check to see if it's been disposed, but we can't call those methods (and neither should we, I think)
My current idea is that we should use a widget class, and not apply ChangeNotifier on any non-widget class. But I can't find any similar problems, or some documentation telling me best practices/guidelines.
class ExampleClass extends ChangeNotifier {
ExampleClass() {
}
final ExampleService _service = ExampleService();
List<Stuff> stuff;
Future<void> DoStuff() async {
stuff = await _service.getStuff();
notifyListeners();
}
}
We're looking for a way to properly handle the callbacks/returning futures and only call notifyListeners if the object has not been disposed.