is it possible to automatically refresh component when getx value changed flutter - flutter

I am using getx get: ^4.3.8 as the state management component in flutter, when I using this code to update the controller value:
var expand = controller.newTaskExpanded.value;
controller.newTaskExpanded(!expand);
I found the component did not refresh, then I have to invoke the getx refresh function like this:
controller.refresh();
this is the controller properties define:
class HomeController extends GetxController {
List<Widget> widgetsList = List.empty(growable: true);
List<Todo> tasks = List.empty(growable: true);
var newTaskExpanded = true.obs;
var completedTaskExpanded = false.obs;
}
is is possible to auto refresh the flutter component when controller properties value changed? should I invoke the refresh function every time change the value of getx controller?

Reactive programming with Get is as easy as using setState.
Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed.
This is your count variable:
var name = 'Jonatas Borges';
To make it observable, you just need to add ".obs" to the end of it:
var name = 'Jonatas Borges'.obs;
And in the UI, when you want to show that value and update the screen whenever the values changes, simply do this:
Obx(() => Text("${controller.name}"));
For more , refer: https://pub.dev/packages/get/example

wrap you UI with Obx builder like
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
it will auto refresh the UI on value change
if you want to refresh the UI manually then use GetBuilder and when you need to update the UI just call controller.update();

As #Muhammad Sherad explained, your widget code should be wrapped with Obx() so that you widget can look for variable change and re-render the widget.

Related

Open dialog box in Flutter Stateless widget using ChangeNotifierProvider

I have a Stateless flutter widget which shows a list of TODOs. The list is shown using a ChangeNotifierProvider (of TodoList object, which contains a list of todos). Now when I load the page, I want to show a dialog box asking user to enter a new TODO if and only if the existing todos is empty. Inside the builder of the ChangeNotifierProvider, i tried below logic
if (todoList.todos.length == 0) {
_showDialog(context);
return Column...;
} else {
return ListBuilder...;
}
But its showing 2 dialog box (probably due to the build method executing twice). I have to pass context to dialog box because I'm updating the todoList inside it, which should trigger a rebuild.
How do I handle this scenario. I've tried using flag (_isDialogOpen) but its not still working?
make the widget Stateful in order to use it's lifecycle methods, you can use then initState() for showing the dialog when the page widgets are inserted in the widget tree, but you will need to use an addPostFrameCallback() to schedule showing it 1 frame after the initState's code gets executed:
First, import:
import package:flutter/scheduler.dart.
Then use this:
#override
void initState() {
// ...
SchedulerBinding.addPostFrameCallback((_) => _showDialog(context),);
}

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),
),

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();
}
}

flutter refresh obs list of classes after changing data in it

I created a list and made it observable by using using getx obs
var msgChat = List.filled(1, UserMessagesModel(), growable: true).obs;
and if i append or add a new to it, it automatically updates in the UI but my problem is that if i using a single value inside the classes of the list it doesn't get updated until i do it for the second time before it updates
socket.on("msg_delivered", (del) {
OfflineDatabaseManager.instance.updateMsgReadData(del).then((value) {
uController.msgChat.forEach((el) {
if (el.msgId == del) {
print(el.msgId);
el.isRead = 'true';
}
});
});
});
my listview is wrap using an Obx widget which makes it work but it doesn't updated if a single value in the class of the list is changed please is there a way to like refresh the list or listview and also i can't use setState(() {}); because an not inside a Statefulbuilder but in a class which extends to getx
class SocketHandler extends GetxController {}
please how do i fix this issue. Thank. Also if you require more explanation then tell me to explain
you can use refresh method like this on your observable, after changing the value:
msgChat.refresh()
if this didn't work, it's probably because you didn't use Obs correctly
please share the Obs part if the problem remains so i can help

How to return data when popping multiple screens?

I know I can return data to the previous screen by using
Navigator.pop(context, 'Value');
But in my case I need to pop multiple screens by using
Navigator.popUntil(context, ModalRoute.withName('/login'));
I wonder in this case how do I pass the data back to the corresponding widget?
Thanks in advance.
you can send DATA in few ways
as a Parameter
using Shared_Preferences
using Static Variables
Only for Current Session
if you just need the DATA for Current Session you can go for Static Variables
step 1 : Create a Class and have Static Variable in it.
class Globaldata{
static String value;
}
step 2 : Initialise variable by
Globaldata.value="some_value";
step 3 : use of variable
String assigned_value = Globaldata.value;
The flutter API does not have that feature and from this https://github.com/flutter/flutter/issues/30112 discussion, that feature is not on the table yet. A walkaround was suggested though using the Page API.
However, in my opinion, it is cleaner to use the provider package https://pub.dev/packages/provider as part of your app state management to keep the data you want and make it available to any screen of interest. Follow these steps to achieve that.
Add the provider to your pubspec.yaml. Check the link above to see detailed instructions.
Create a notifier class that extends ChangeNotifier class as shown below. ChangeNotifier class is part of the flutter API.
class MyDataProvider extends ChangeNotifier {
//define your private data field(s). I'm using int here.
int _mydata;
//define a getter
int get myData => _myData;
// define a setter
set myData(newData){
_myData = newData;
notifyListeners();
}
}
Wrap your uppermost widget (or the parent of the screens where you want to pass the data) with the provider and instantiate it. I'm using main here.
void main(){
runApp(
ChangeNotifierProvider(create: (context) => MyDataProvider()),
child: MyApp(),
)
}
Assuming you have five screens: Screen1, Screen2, ..., Screen5 and you want to navigate to screen5, do some operations and return to screen1 with some data. On 1st screen, define a local variable for myData and create an instance of your myDataProvider. When a button is pressed to start the navigation, wrap up the push navigation in an asynchronous call.
//Screen1
int currentLocalData = 78;
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
onPressed: () async {
//Assign localData to myData in the provider
myProvider.myData = currentLocalData; //calls the setter define in the provider.
await Navigator.push(context, MaterialPageRoute(
builder: (context) => Screen5()
));
//Retrieve myData from the provider and assign it to currentLocalData.
//This executes after navigating back from Screen5
currentLocalData = myProvider.myData;
}
Let assume in Screen5 you retrieved the data and added 100 to it. Your aim is to return to Screen1 and make use of the new data, i.e 178. Here you will instantiate the provider again in Screen5 and assign the value 178 to myData.
//Screen5
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
myProvider.myData += 100;
//Use navigation.popUntil
Navigation.popUntil(context, ModalRoute.withName('/Screen1'));
Say you have Screen A,Screen B, Screen C. If you want to pop till Screen A and pass some data. Here is what you have to do.
1. Navigate from Screen A to Screen B
Navigator.pushNamed(context, '/screenb')
.then((value) {
//you will get return value here
});
2. To pop till Screen A.
//add thi code in Screen C
var nav = Navigator.of(context);
nav.pop('refresh');
nav.pop('refresh');