GetX + Flutter: how to make Obx rebuild when updating a list inside an entity (and in general, what triggers an Obx rebuild)? - flutter

I'm working on a Flutter project which uses GetX for state management.
We have some entities which contains List properties. A simplified version would be similar to this:
class UserFees extends Equatable {
final String userId;
final List<double> fees;
UserFees({required this.userId, this.fees = const []});
void addFee(double newFee) => fees.add(newFee);
#override
List<Object> get props => [fees];
// Alternatively, we have also tried to:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserFees &&
runtimeType == other.runtimeType &&
listEquals(fees, (other as UserFees).fees);
#override
int get hashCode => // I do not remember the calculus we'd used,
// but it did at least change when new item was added;
}
This entity is used in the controller:
class MyController extends GetxController {
final Rx<UserFees> userFees;
void addFee(double newFee) {
userFees.addFee(newFee);
// We have tried calling update() to no effect
}
}
When I use this inside a Widget with Obx, it doesn't rebuild when I add an item to the list, even though the object's hashCode gets updated.
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() => Column(children:
...controller.userFees.fees.map(
(item) => Text(item.toString()
);
),
),
floatingActionButton: IconButton(
icon: const Icon(Icons.add),
onPressed: controller.addFee(0.1)
),
);
);
}
Our team has made this work by turning the List inside the entity into an RxList, however it's not ideal because we did not want to couple our entities with GetX or RxDart.
GetX documentation is pretty poor on these details, and the source code ir pretty hard to understand, at least for me. So, can anyone explain to me how is it that Obx knows that an observable object has changed? Does it look at the hashCode? Will it only rebuild if my class is immutable, and I rebuild the object entirely when adding an item to the list?

There are some suggestions which I feel could help you.
In the MyController, you are using
final Rx<UserFees> userFees;
which means you are declaring as an Rx, but you are not making it observables. To make your UserFees observables, you should use
final Rx<UserFees> userFees = 0.0.obs; // if it's type is double;
Next, In your entity, if you think there is some variable who's value is changing and you want to reflect that value in the UI. Then you should also make that Observable.

Related

Best practice for passing param Riverpod's providers only once

I just want to build a Provider which asks params only one and inits correctly.
Since I am just passing params only once, I don't prefer to use .family methods.
I prefer to use .autoDispose which considered the better way.
Here my tryouts:
I tried to make my own .init() method. But it's disposing as soon as method called if it's .autodispose() and the widget not started to listen my provider yet (that's expected). Therefore I couldn't consider a safe way to do that.
I tried .overrideWith() method in a widget basis. But it's neither worked nor I am sure that it's best practice.
Here is my simple code:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
final myString = 'Hey';
#override
Widget build(BuildContext context, WidgetRef ref) {
//Not worked
ProviderContainer(
overrides: [messageProvider.overrideWith(() => ViewModel(myString))]);
return Scaffold(
body: ProviderScope(
//Not worked either
overrides: [messageProvider.overrideWith(() => ViewModel(myString))],
child: Center(
//I just didn't use .when to shorter code
child: Text(ref.watch(messageProvider).value!.counter.toString()),
),
),
);
}
}
final messageProvider = AsyncNotifierProvider.autoDispose<ViewModel, Model>(
() => throw UnimplementedError());
class ViewModel extends AutoDisposeAsyncNotifier<Model> {
final String param;
ViewModel(this.param);
#override
FutureOr<Model> build() {
//Make some fetch with param, (only once!)
return Model(param.length);
}
}
When I run that. It gives UnimplementedError
Waiting your suggestions & fixes. Thanks in advance!
Expected:
Works properly.
#riverpod
ViewModel myViewModel(MyViewModelRef ref, String param){
return ViewModel(param);
}
This is autoDispose by default in Riverpod 2. If you don't want to auto dispose you can use #Riverpod(keepalive:true) instead of #riverpod
If you don't want to pass the param to the provider, you can eliminate it and hardcode the value to the ViewModel, but at that point, if there are no other dependencies, might as well make it a public final variable in some file, since it looks like this is a singleton that never changes so it is questionable what you'd achieve by making it a Riverpod provider.

when should we use freezed as sealed classes or constructor?

Which way to use freezed library with bloc ? first one as a sealed classes, the other is a constructor.
First way
abstract class HomeState with _$HomeState {
const factory HomeState.initial() = _Initial;
const factory HomeState.addNewNoteButtonClicked(#Default(false) bool isClicked) = AddNewNoteClicked;
factory HomeState.addNewNote( Note value) = AddNewNote;
}
Second Way:
abstract class HomeState with _$HomeState {
const factory HomeState({
required Note value,
required bool isClicked,
}) = AddNewNoteClicked;
factory HomeState.init() => HomeState(
value: Note(value: ''),
isClicked: false,
);
}
TL;DR: I think there is no correct way, just what works for you.
When using freezed, every factory constructor generates a separate class. Together with that, it generates some convenience methods, like map/maybeMap/when/maybeWhen. This is very convenient when your BLoC has obvious and different states and you want to handle your UI accordingly. For instance: initial, loadInProgress, loadSuccess, loadFailure. Then, in your UI, you could use something like:
class Example extends StatelessWidget {
const Example();
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleCubit, ExampleState>(
builder: (_, state) => state.maybeWhen(
loadInProgress: () => const LoaderView(),
loadFailure: () => const ErrorView(),
loadSuccess: (categories) => const SomeView(),
orElse: () => const SizedBox(),
),
);
}
}
However, it also brings some inconvenience when you need to take data from the specific state: you must check if the state is a specific one and only then you can process with your code, e.g:
if (state is ExampleStateSuccess) {
...
}
In such cases, when you need to have just a single state but a lot of different properties (a good example would be form validation when you store all the field properties in your BLoC and you want to validate/update them, submit the form later) it is better to use a single state with properties. By better, I mean it's just easier this way.

Flutter interaction with change in memory

I am trying to understand how a flutter Widget populated by a memory item behaves when the underlying memory item changes. My use case is that: by trigger of some event(using EventBus package), this underlying memory item may or may not change. If it does, then I would like to trigger a setState(), but not otherwise as I wouldn't want to unnecessarily call it. Kindly help me as to how I can achieve this.Currently the onclick changes the str but the Text widget doesn't change. My example -
button click(acting as event trigger) changes underlying str item that populates the Text item. Thanks in advance.
class _MainApp extends State<MainApp>{
#override
String str = "1";
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
CustomText(),
RaisedButton(onPressed: (){
print(CustomTextS.str);
CustomTextS.setStr("f");
print(CustomTextS.str);
},)
],
),
),
);
}
}
class CustomText extends StatefulWidget{
CustomTextS createState() => CustomTextS();
}
class CustomTextS extends State<CustomText>{
static String str = "1";
static setStr(String a){
str = a;
}
Widget build(BuildContext context){
return Text(str);
}
}
Currently I proceeded to use the fact that Strings and integers are passed by value. Thus one can use that fact to get effectively a copy which one can use to compare with the updated value to determine whether or not on event action a widget's properties have changed or not.

What's the design benefit of Flutter's (Widget)State/StatefulWidget pattern?

My docs and Flutter videos, the explanation of the design of the StatefulWidget (+(Widget)State) is that it:
promotes a declarative design (good)
formalizes the process by which Flutter to efficiently decide which components need to be re-rendered (also good)
From the example:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {...}
}
However:
since we have to explicitly remember call setState in order to invalidate the state, is this really a declarative design?
Flutter doesn't automatically detect changes in the State object and decide to call build (although it could have), and so it doesn't really formalize/automate/make-safe the invalidation of view components. Since we have to explicitly call setState, what's the benefit of the Flutter's (Widget)State/StatefulWidget pattern over, let's say:
class MyHomePage extends StatefulWidget // Define dirty method
{
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
int _counter = 0;
_incrementCounter() {
_counter++;
this.dirty(); // Require the view to be rebuilt. Arranges generateView to be called.
}
#override
Widget generateView(BuildContext context) {return ... rendering description containing updated counter ... ;}
}
... which would place the same burden of marking the UI dirty on the programmer, is no less decalrative, and avoids additional abstraction that obfuscates the intention of the program.
What have I missed? What's the benefit of separating of StatefulWidget from (Widget)State in Flutter?
[Before people chime in with MVC comments, note that the Flutter model rather explicitly only manages only the widget's state and its tightly coupled to the UI's Widget through the build method - there is no separation of concern here and it doesn't have a lot to say about larger application state that's not attached to a view.]
[Also, moderators, these not the same questions: Why does Flutter State object require a Widget?, What is the relation between stateful and stateless widgets in Flutter?. My question is one about what's the benefit of the present design, not how this design works.]
Update: #Rémi Rousselet -- Here's a declarative example with only a new state class needing to be declared. With some work, you could even get rid of that (though it may not be better).
This way of declaring interaction with need didn't require (the user) declaring two new circularly type-referencing class, and the widget that is changing in response to state is decoupled from the state (its constructed a pure function of the state and does not need to allocate the state).
This way of doing things doesn't survive hot-reload. (sad face).
I suspect this is more of an issue with hot-reload, but if there's a way to make it work it would be great,
import 'dart:collection';
import 'package:flutter/material.dart';
////////////////////////////////
// Define some application state
class MyAppState with ChangeSubscribeable<MyAppState> {
/***
* TODO. Automate notifyListeners on setter.
* Binds changes to the widget
*/
int _counter;
get counter => _counter;
set counter(int c) {
_counter = c;
notifyListeners(); // <<<<<< ! Calls ... .setState to invalidate widget
}
increment() {
counter = _counter + 1;
}
MyAppState({int counter: 0}) {
_counter = counter;
}
}
void main() => runApp(MyApp5());
class MyApp5 extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Declare the mutable state.
// Note because the state is not coupled to any particular widget
// its possible to easily share the state between concerned.
// StateListeningWidgets register for, and are notified on changes to
// the state.
var state = new MyAppState(counter: 5);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: Column(
children: [
// When the button is click, increment the state
RaisedButton(
onPressed: () => {
state.increment(),
print("Clicked. New state: ${state.counter}")
},
child: Text('Click me'),
),
// Listens for changes in state.
StateListeningWidget(
state,
// Construct the actual widget based on the current state
// A pure function of the state.
// However, is seems closures are not hot-reload.
(context, s) => new Text("Counter4 : ${s.counter}"),
),
],
))),
);
}
}
// //////////////////////
// Implementation
// This one is the onChange callback should accept the state.
//typedef OnChangeFunc<ARG0> = void Function(ARG0);
typedef OnChangeFunc = void Function();
mixin ChangeSubscribeable<STATE> {
final _listener2Notifier =
new LinkedHashMap<Object, OnChangeFunc>(); // VoidFunc1<STATE>>();
List<OnChangeFunc> get _listeners => List.from(_listener2Notifier.values);
void onChange(listenerKey, OnChangeFunc onChange) {
// onChange(listenerKey, VoidFunc1<STATE> onChange) {
assert(!_listener2Notifier.containsKey(listenerKey));
_listener2Notifier[listenerKey] = onChange;
print("Num listeners: ${_listener2Notifier.length}");
}
void removeOnChange(listenerKey) {
if (_listener2Notifier.containsKey(listenerKey)) {
_listener2Notifier.remove(listenerKey);
}
}
void notifyListeners() {
// _listener2Notifier.forEach((key, value)=>value(state));
// Safer, in-case state-update triggers add/remove onChange:
// Call listener
_listeners.forEach((value) => value());
}
}
typedef StateToWidgetFunction<WIDGET extends Widget,
STATE extends ChangeSubscribeable>
= WIDGET Function(BuildContext, STATE);
void noOp() {}
class _WidgetFromStateImpl<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends State<StatefulWidget> {
STATE _state;
// TODO. Make Widget return type more specific.
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
_WidgetFromStateImpl(this.stateToWidgetFunc, this._state) {
updateState(){setState(() {});}
this._state.onChange(this, updateState);
}
#override
Widget build(BuildContext context) => stateToWidgetFunc(context, this._state);
#override
dispose() {
_state.removeOnChange(this);
super.dispose();
}
}
class StateListeningWidget<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends StatefulWidget {
STATE _watched_state;
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
StateListeningWidget(this._watched_state, this.stateToWidgetFunc) {}
#override
State<StatefulWidget> createState() {
return new _WidgetFromStateImpl<WIDGET, STATE>(
stateToWidgetFunc, _watched_state);
}
}
I've been directed at the ChangeProvider pattern: https://github.com/flutter/samples/blob/master/provider_counter/lib/main.dart
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Demo Home Page'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Consumer<Counter>( // <<< Pure. Hidden magic mutable parameter
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
),),],),),
floatingActionButton: FloatingActionButton(
onPressed: () =>
// <<< Also a hidden magic parameter
Provider.of<Counter>(context, listen: false).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
... but this also suffers problems:
its not clear to reader of what the state requirements are or how to provide them -- the interface (at least in this github example HomePage) example does not require Counter as a formal parameter. Here we have new HomePage() that has configuration that is not provided in its parameters - this type of access suffers similar problems to global variables.
access to state is by class type, not object reference - so its not clear (or at least straightforward) what to do if you want two objects of the same type (e.g. shippingAddress, billingAddress) that are peers in the model. To resolve this, the state model likely needs to be refactored.
I think I'm with user48956 on this. (Catchy name by the way).
Unfortunately, the Flutter authors seem to have suffixed their View class with the word 'State'. This has rather confused the whole Flutter state management discussions.
I think the purpose of the two classes is actually to make the painting more performant but it comes with a very heavy plumbing cost for us developers.
As to the naming convention:
The dirty flag approach allows the widget painter to optimise their painting without knowing about our state, thereby alleviation the need for two classes.
Also generateView() is kinda meaningful (unless of course, you start using these widgets to hold model-fragments (as per Package:provider).

Understanding Flutter didChangeDependencies mechanics

After reading the docs here and the State lifecycle here, I am still not sure about how didChangeDependencies works.
As far as I understand it will be triggered after initState and after any change in an InheritedWidget, but what are these changes? I think it's important to understand what changes trigger didChangeDependencies, so we can understand when and how to use it properly.
When Flutter calls updateShouldNotify() and it returns true, then widgets that requested an inherited widget in build() previously are notified by didChangeDependencies being called.
updateShouldNotify should return true if its state changed since the last time it was called.
TLDR
As the creator of the Widget, you set it's dependencies (InheritedWidget) by using of or maybeOf, etc., so you should understand when they call updateShouldNotify (or they're dependencies call it). It's quite complex:
Usage
You usually don't have to override this method in State because your widget will rebuild when a dependency changes anyway. If you want to do expensive work, like making a network request, then you would avoid making that network request in a normal build, and put this expensive work in didChangeDependencies instead. This allows you to only make these expensive operations when a dependency changes. Tbh, I struggled to think of a situation where you'd want to make an expensive operation purely if an InheritedWidget changes if you're properly structuring your app's logic into services. So I looked at the Flutter framework internals...
Examples of didChangeDependencies overrides:
Flutter's Image widget (the comments are my commentary, and tbh load is probably not the right word I'm using, but resolve isn't much better IMHO)
#override
void didChangeDependencies() {
_updateInvertColors(); // Checks if the image should inverted or not.
_resolveImage(); // Reloads the image if necessary?
if (TickerMode.of(context)) // This method returns a bool: Whether tickers in the given subtree should be enabled or disabled.
_listenToStream(); // Keep loading the image
else
_stopListeningToStream(keepStreamAlive: true); // Be efficient and not read from the stream if not needed. (ticker is false)
super.didChangeDependencies();
}
And when is didChangeDependencies called? aka. what are Image's dependencies (which are all InheritedWidgets)? It uses both of and maybeOf to register the InheritedWidget dependencies, including MediaQuery, Directionality, DefaultAssetBundle, TickerMode, Localizations. So when these dependencies change/ get updated, Image's didChangeDependencies will be called.
What is a dependency?
You make a widget depend on a subtype of InheritedWidget with InheritedWidgetType.of(context), which internally calls context.dependOnInheritedWidgetOfExactType<InheritedWidgetType>();. So the widget has a dependency on that InheritedWidget. For example, Theme.of(BuildContext context) can be seen here.
From the docs inside the Flutter Framework's comments about dependOnInheritedWidgetOfExactType:
Obtains the nearest widget of the given type T, which must be the
type of a concrete [InheritedWidget] subclass, and registers this
build context with that widget such that when that widget changes (or
a new widget of that type is introduced, or the widget goes away),
this build context is rebuilt so that it can obtain new values from
that widget.
Once a widget registers a dependency on a particular type by calling
this method, it will be rebuilt, and [State.didChangeDependencies]
will be called, whenever changes occur relating to that widget until
the next time the widget or one of its ancestors is moved (for
example, because an ancestor is added or removed).
There's more docs, which are really interesting, have a read.
Jitesh's answer is currently just wrong. Dependencies are not Widget's state, they are only relevant for InheritedWidget.
didChangeDependencies() Called when a dependency of this [State] object changes.
So, exactly How it gets called? as by the above definition, it looks like it will be called after state changes but how we come to know the state is changed?
Example:
The below example uses the Provider state management mechanism to update the child widget from the parent widget. The Provider has an attribute called updateShouldNotify which decides whether to state is changed or not. If it's returning true then only didChangeDependencies get called in ChildWidget class.
updateShouldNotify is returning true by default internally, as it knows the state got changed.
Why do we need updateShouldNotify?
It’s needed because if someone wants to update the state on a specific condition. Eg: if UI required to show only even values then we can add a condition like
updateShouldNotify: (oldValue, newValue) => newValue % 2 == 0,
Code Snippet:
class ParentWidget extends StatefulWidget {
ParentWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Life Cycle'),
),
body: Provider.value(
value: _counter,
updateShouldNotify: (oldValue, newValue) => true,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Press Fab button to increase counter:',
),
ChildWidget()
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ChildWidget extends StatefulWidget {
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
void initState() {
print('initState(), counter = $_counter');
super.initState();
}
#override
void didChangeDependencies() {
_counter = Provider.of<int>(context);
print('didChangeDependencies(), counter = $_counter');
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
print('build(), counter = $_counter');
return Text(
'$_counter',
);
}
}
Output Logs:
I/flutter ( 3779): didChangeDependencies(), counter = 1
I/flutter ( 3779): build(), counter = 1
For more info:
https://medium.com/#jitsm555/differentiate-between-didchangedependencies-and-initstate-f98a8ae43164