Is there something like #annotation to remove the boilerplate when creating a model class that extend ChangeNotifier?
Boilerplate
class MyModel extends ChangeNotifier{
bool _isLoading = true;
bool get isLoading => _isLoading;
set isLoading(bool value) {
_isLoading = value;
notifyListeners();
}
}
Using something like #annotation
class MyModel extends ChangeNotifier{
#Notify
bool _isLoading = true;
}
You can try Get for your state management: https://pub.dev/packages/get
Reactive programming with Get is as easy as using setState.
First you can create a new controller:
import 'package:get/get.dart';
class FormController extends GetxController {
FormController();
Rx<bool> _isLoading = false.obs;
set setLoading(value) => this._isLoading.value = value;
bool get loading => this._isLoading.value;
}
And in the UI, when you want to show that value and update the screen whenever the values changes, simply do this:
Obx(
() => formController.loading
? Center(
child: CircularProgressIndicator(),
)
: ElevatedButton(
onPressed: formController.googleLoading ||
formController.anonymousLoading
? null
: () async {
formController.setLoading = true;
if (_formKey.currentState!.validate()) {
await userController
.loginWithEmailAndPassword(
formController.email,
formController.password);
}
formController.setLoading = false;
},
child: Text('login'.tr),
),
),
See an more in-depth explanation of state management here. There you will see more examples and also the difference between the simple state manager and the reactive state manager
You will get a good idea of GetX power.
Related
import 'dart:convert';
import 'package:get/get.dart';
import '../../../functions/functions.dart';
import '../models/user_details_modal.dart';
class UserListController extends GetxController {
var data = [];
List<UserDetails> userDetails = <UserDetails>[].obs;
// var userDetails = <UserDetails>[].obs;
// RxList<UserDetails> userDetails = <UserDetails>[].obs;
// RxList<UserDetails> userDetails = RxList();
Future<void> fetchData() async {
String apipage = "getData.php";
Map mappeddata = {};
final response = await sendServerData(apipage, mappeddata);
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body.toString());
userDetails = userDetailsFromJson(json.encode(responseBody));
print(userDetails);
update();
} else {
Get.snackbar(
'Server Connection Failed', 'Please try again after some time');
}
}
#override
void onInit() {
fetchData();
super.onInit();
}
}
Above code is my controller,
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/user_list_controller.dart';
import '../models/user_details_modal.dart';
class UserListView extends GetView<UserListController> {
#override
final UserListController controller = Get.put(UserListController());
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView'),
centerTitle: true,
),
body: Obx(
() => controller.userDetails.isEmpty
? Center(
child: Text("Loading data..."),
)
: ListView.builder(
itemCount: controller.userDetails.length,
itemBuilder: (context, index) {
final UserDetails item = controller.userDetails[index];
return buildListTile(item);
},
),
),
);
}
Widget buildListTile(UserDetails item) {
print("userName: ${item.userName}");
print("userEmail: ${item.userEmail}");
return ListTile(
title: Text(item.userName),
subtitle: Text(item.userEmail),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {},
),
);
}
}
This is my view,but it's throwing this error -
"[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."
i'm a beginner in flutter,so please help me to find the issue here.
I have updated the code,i made some changes in controller part declaring that List,now that prev issue is solved!
But the problem is no data showing up on first load (using Get.to() to navigate to this from another page) but when i hot reload/reload this page it shows those data.
What's the problem?
if you want to use reactive approach using Obx() or Getx(), you need to make your variables observable. You can do this simply by adding .obs to the variable. This makes your variables as Rx type.
There are other ways to make your variables observable, but this is the simplest one.
in the controller:
var data = [].obs;
List<UserDetails> userDetails = [].obs;
Also, you don't need to use update() for this approach.
And whenever you want to change the value or use it in your view
you should access the value like this.
in the view inside the Obx: ( in the code above, no need to use .value for the list)
E.x)
bool isLoading = controller.loading.value
Hope this helped :)
Firstly, I have a login screen and I want to change button with CircularProgressIndicator when button is clicked. I am using Provider to handle state management. Here is my Provider class.
class UserProvider with ChangeNotifier {
bool _loading = false;
bool get loading => _loading;
UserViewModel? user;
bool isLoggedOut = false;
TokenRequestModel tokenRequestModel = TokenRequestModel(
username: '', password: '', environment: '', deviceToken: '');
login(TokenRequestModel requestModel) async {
_loading = true;
user = await UserService().login(requestModel);
_loading = false;
notifyListeners();
}
}
I change loading value between my Api call. Then I want to catch it in my login screen to change widget. With that way I prevent button clicked multiple times and my login screen looks like this.
Column(
children: [
//textfield widgets
context.watch<UserProvider>().loading == false
? button(context, userProvider)
: Container(
height: 35.h,
width: 280.w,
decoration: BoxDecoration(color: blueLogo),
child: const CircularProgressIndicator(
color: Colors.white,
),
)
],
),
I have tried multiple methods like Consumer, userProvider = Provider.of<UserProvider>(context); but nothing happens. What am I missing or what is the mistake of my method to handle state management? Would you help me to find right way to do? Thanks
Seems to me that you are notifying listeners when _loading has gone back to being false again, but you never notified them when it turned true!
Try this, in your provider file:
class UserProvider extends ChangeNotifier {
bool _loading = false;
bool get loading => _loading;
UserViewModel? user;
bool isLoggedOut = false;
TokenRequestModel tokenRequestModel = TokenRequestModel(
username: '', password: '', environment: '', deviceToken: '');
login(TokenRequestModel requestModel) async {
_loading = true;
notifyListeners(); // <- The crucial line, right here!
user = await UserService().login(requestModel);
_loading = false;
notifyListeners();
}
}
I noticed you had used with ChangeNotifier instead of extends ChangeNotifier... but I don't know what that does! I always use extends ChangeNotifier and that works for me.
I'm trying to use a GetX Builder in combination with a bool in the controller to display a loading spinner while fetching data and then the data afterwards.
Printing the update to the bool shows it finishes loading and changes the variable but the UI is never updated to reflect this.
Controller
class AuthController extends GetxController {
//final RxBool isLoading = true.obs;
//var isLoading = true.obs;
final Rx<bool> isLoading = Rx<bool>(true);
setLoading(bool status) {
isLoading.value = status;
print(isLoading.value);
update();
}
fetchUserData() async {
setLoading(true);
_firebaseUser.bindStream(_auth.authStateChanges());
if (_auth.currentUser != null) {
bool profileRetrieved =
await FirestoreDB().getUserProfile(_auth.currentUser!.uid).then((_) {
return true;
});
setLoading(!profileRetrieved);
}
}
}
Profile Page
class ProfileCard extends StatelessWidget {
const ProfileCard({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder<AuthController>(
init: AuthController(),
initState: (_) => AuthController().fetchUserData(),
builder: (controller) {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Container(...);
},
);
}
}
That widget is called within another as part of the wider UI. Let me know if you'd need to see that and/or anything else.
As you can see I've tried different ways of setting up the bool and none of them seem to trigger a change of UI in the builder.
Probably doing something stupid but I've tried a few different approaches and looked around for people having similar problems but not been able to crack it.
Thanks in advance.
You are using isLoading as a Rx<bool>. In that case you need to change GetBuilder into GetX. And no need to call update().
GetBuilder is for non-reactive, non-Rx, simple state management
GetX is for reactive, Rx state management
I have a switch which toggle the app theme but there is some errors on the code
Switch(value: AppStyleMode.isSwitched, onChanged: (value)=> AppStyleMode.switchMode())
My Theme file
import 'package:flutter/material.dart';
class AppStyleMode extends ChangeNotifier {
bool isSwitched = true;
Color primaryBG = Colors.white;
Color appBarBG = Colors.yellow;
switchMode() {
if (isSwitched == true) {
primaryBG = Colors.black];
appBarBG = Colors.grey[400];
isSwitched = false;
} else {
//if it is dark mode currently switch to light
primaryBG = Colors.white;
appBarBG = Colors.yellow;
isSwitched = true;
}
notifyListeners();
}
}
Errors on Switch :
If you are not using any particular state management packages,
First, you would have to create an instance of your AppStyleMode class, so that you can use values from it and also listen to it's changes.
Assuming you have a StatefulWidget,
first define your AppStyleMode in it's State,
class MyState extends State<MyStatefulWidget> {
late AppStyleMode appStyleMode;
Then in your initState, initialise it and add a listener to it.
void initState () {
super.initState();
appStyleMode = AppStyleMode();
// Also add a listener to it and call setState to rebuild your StatefulWidget
appStleMode.addListener(() => {
setState(() {});
});
}
Then you can use it in your build by using your variable appStyleMode like this,
Switch(value: appStyleMode.isSwitched, onChanged: (value) => appStyleMode.switchMode())
I would rather suggest you look into a State management solution like provider or Getx. But it is not compulsory.
I want to change body of the widget depending on if the pressedBool is false or true.
Here is GetxController I wrote.
import 'package:get/state_manager.dart';
class PressedState extends GetxController{
var pressedBool = true;
changeStatus() {
if(pressedBool){
pressedBool = false;
}
else {
pressedBool = true;
}
update();
}
}
Here is where GetX update and listen should work out to change body of the page:
final PressedState pressController = Get.put(PressedState());
return MaterialButton(
onPressed: () {
pressController.changeStatus();
},
child:
pressController.pressedBool
? Container(...) : Container(...)), ...
How can I make GetX to listen pressedBool variable ?
Sometimes, it is useful just listen changes and change the value of some others controllers or variables. For example, if we want to change the tab from another screen.
We can create controller with Rx variable
class TabStateController extends GetxController {
RxInt tabIndex = 0.obs;
}
and after, on the screen with tabs listen to changes
_tabStateController.tabIndex.listen((value) {
_tabController.index = value;
});
So, we can listen for changes in the observable variables.
return MaterialButton(
onPressed: () {
pressController.changeStatus();
},
child:
GetBuilder<PressedState>(
init: PressedState()
builder: (pressController) {
return pressController.pressedBool
? Container(...) : Container(...))
}
),
you can use ever method on the controller
for example
final Controller c = Get.put(Controller());
ever(c.counter, (value) => print("$value has been changed"));
or in controller, you declare this
import 'package:get/get.dart';
class Controller extends GetxController {
var counter = 50.obs;
void increment() {
counter = counter + 1;
}
#override
void onInit() {
ever(counter, (value) => print("$value has been changed!!!"));
super.onInit();
}
}
YourController.dart
import 'package:get/state_manager.dart';
class PressedState extends GetxController{
RxBool pressedBool = true.obs;
void changeStatus() {
pressedBool.toggle();
}
}
YourView.dart
final Controller pressController = Get.put(PressedState());
return MaterialButton(
onPressed: () {
pressController.changeStatus();
},
child: Obx(() {
return pressController.pressedBool.isTrue
? Container(...)
: Container(...),
})
There are 3 ways to listen to change to a property in your controller. You are using update() method in your setter method in your controller so the first one will work for you, also it is the lightest method.
If you didn't initialize a controller in your widget, then:
final Controller _controller = Get.put(Controller());
If you have initialized your controller already and it's in memory, then:
final Controller _controller = Get.find<Controller>();
GetBuilder<Controller> (
init: _controller,
builder: (_) {
return Text('${_controller.property}');
}
)
Here is your controller
import 'package:get/get.dart';
class YourController extende GetxController{
var yourBoolVar = false.obs;
toggleBoolValue(){
if(yourBoolVar.isTrue){
yourBoolVar.toggle();
}
yourBoolVar.value = true;
}
}
and after that listen to changes in your screen
GetX<YourController>(builder: (controller){
if(controller.yourBoolVar.value){
// return your true statement view from here
}else{
// return your false statement view from here
}
)
use this to toggle its value in your View Class
YourView.dart
final YourController controller = Get.put(YourController());
return MaterialButton(
onPressed:() {
controller.toggleBoolValue();
}
);