flutter refresh obs list of classes after changing data in it - flutter

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

Related

is it possible to automatically refresh component when getx value changed 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.

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

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

Riverpod - ref.onDispose called when object is created, and not when disposed

I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.