when should we use freezed as sealed classes or constructor? - flutter

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.

Related

What is the efficient way to pass arguments to a Riverpod provider each time it gets initialized in Flutter?

I am currently trying to create an instance of a widget's state (ChangeNotifier) using a global auto-disposable ChangeNotifierProvider. The notifier instance takes in a few arguments to initialize each time the UI is built from scratch.
Let's assume we have the following simple state (or notifier):
class SomeState extends ChangeNotifier {
int _someValue;
SomeState({required int initialValue})
: _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
I used to use the Provider package before switching to Riverpod, where this could've easily been done like so:
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// Passing 2 into state initializer, which may be
// obtained from a different state, but not necessarily.
create: (_) => SomeState(initialValue: 2),
builder: (context, child) => Consumer<SomeState>(
builder: (context, state, child) {
// Will print 2, as it's currently the default value.
return Text('${state.someValue}');
},
),
);
}
}
So with Provider, you can manually call to SomeState constructor with arbitrary arguments when the state is being set up (i.e. provided). However, with Riverpod, it doesn't seem as intuitive to me, mainly because the provider is made to be declared globally:
static final someProvider = ChangeNotifierProvider.autoDispose((ref) => SomeState(2));
Which would end up being used like so:
class SomeWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(someProvider);
return Text('${state.someValue}');
}
}
However, with this approach I can't pass parameters like I did in the example using Provider. I also don't want to use the family modifier because I would need to pass the same parameter each time I read/watch the state, even if it's already created.
If it helps, in my current situation I am trying to pass a function (say String Function()? func) into my state on initialization. It's also not feasible to depend on a different provider in this case which would provide such function.
How could I replicate the same functionality in the Provider example, but with Riverpod?
P.S. Apologies if code has syntax errors, as I hand-typed this and don't have an editor with me at the moment. Also, this is my first post so apologies for lack of clarity or format.
Use provider overrides with the param that you need:
First, let's ensure the ProviderScope in the root of the widget-tree.
// Root
ProviderScope(
child: MaterialApp(...)
)
After, create another one in some widget:
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
someProvider.overrideWithProvider(
ChangeNotifierProvider.autoDispose((ref) => SomeState(5)),
),
],
child: Consumer(
builder: (context, ref, child) {
final notifier = ref.watch(someProvider);
final value = notifier.someValue;
return Text('$value'); // shows 5 instead of 2
}
),
);
}
If you do not want to use family then you can put value in another way by combining two providers.
final someValue = StateProvider((ref) => 0);
final someProvider = ChangeNotifierProvider.autoDispose((ref) {
final value = ref.watch(someValue);
return SomeState(value);
});
class SomeState extends ChangeNotifier {
int _someValue;
SomeState(int initialValue) : _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
USAGE:
// From everywhere you can put new value to your ChangeNotifier.
ref.read(someValue.notifier).state++;
But in your case, it's better to use the `family method. It's cleaner and less complicated.

Dart freezed same field on all constructors

I'm trying to make a state class for my todo bloc, and I want to keep the loaded todos when I start refreshing. Is there a better way to do this, basically having a mutual field in all the constructors of the class using freezed package
#freezed
class TodoState with _$TodoState {
const factory TodoState.loading(List<Todo> todos) = TodoStateLoading;
const factory TodoState.loaded(List<Todo> todos) = TodoStateLoaded;
const factory TodoState.error(List<Todo> todos, String message) = TodoStateError;
}
I can already use it like this, but i would like to just call state.todos and not having to check for its type:
TodoState state = TodoStateLoaded([/*example*/]);
state.todos // ERROR
(state as TodoStateLoaded).todos // OK
As per this comment freezed do not support super classes. The closest solution that might work for you is this:
// create mixin with getter of properties that you would like to share between states
mixin TodoMixin {
List<Todo> get todos;
}
#freezed
// add mixin to your class with state
class TodoState with TodoMixin, _$TodoState {
const TodoState._(); // add private constructor
//override getter with map function that will return data for each state
#override
List<Todo> get todos {
return map(
loading: (state) => state.todos,
loaded: (state) => state.todos,
error: (state) => state.todos,
);
}
const factory TodoState.loading(List<Todo> todos) = TodoStateLoading;
const factory TodoState.loaded(List<Todo> todos) = TodoStateLoaded;
const factory TodoState.error(List<Todo> todos, String message) = TodoStateError;
}
It will create common getter for state and when you will introduce new state map method will show you, that you need handle new state in getter. Add another property in mixin and you will need override it too.
To use it you can call it in cubit:
state.todos; or in view: context.read<TodoCubit>().state.todos;

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

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.

How to use Provider/Consumer when extending a class

To start, I'm new to Flutter, so I am completely open to the possibility that my problem stems from a fundamental misunderstanding, but here is my question:
I am trying to get a good understanding of how to use Provider in conjunction with with the get_it package.
I think I understand how to use the Provider pattern in the standard case, by which I mean creating a unique class with a view and a view_model. Where I seem to have become lost is when I design a custom widget as a base template class and then extend that widget so that it can be tailored for use in a specific class view, I'm not seeing how to connect it to the Provider pattern because the base class doesn't know in advance which view_model it needs to listen to.
Below I will provide short example of what I am doing in the standard case, where things seem to work fine, and then I will show a short example of how I am trying to build the custom widget and extend it...
Here is the sample standard way in which I am using the Provider pattern with get_it, in which everything seems to work just fine:
class MyScreenView extends StatefulWidget{
#override
_ProfileEditScreenViewState createState() => _ProfileEditScreenViewState();
}
class _MyScreenViewState extends State<MyScreenView>{
final MyScreenViewModel model = serviceLocator<MyScreenViewModel>();
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyScreenViewModel>(
create: (context) => model,
child: Material(
color: Colors.white,
child: Consumer<MyScreenViewModel>(
builder: (context,model,child) => Text(model.someText),
),
),
);
}
}
class MyScreenViewModel extends ChangeNotifier{
String? _someText;
MyScreenViewModel() {
this._someText= 'Sample Text';
}
String get someText=> _someText;
set someText(String value) {
_someText= value;
notifyListeners();
}
}
Here is an example of how I am trying to build a base class, but am uncertain as to how I go about connecting it to Provider: (The idea here is that the below widget would be part of a more complex widget that would have a view_model where the state for the overall widget would be maintained)
class BaseCheckBoxTile extends StatefulWidget{
bool isChecked;
Function(bool) checkBoxOnChanged;
BaseCheckBoxTile({this.isChecked = false, required this.checkBoxOnChanged});
#override
_BaseCheckBoxTileState createState() => _BaseCheckBoxTileState();
}
class _BaseCheckBoxTileState extends State<BaseCheckBoxTile>{
#override
Widget build(BuildContext context) {
return SizedBox(
child: Checkbox(value: widget.isChecked,onChanged: widget.checkBoxOnChanged,),
);
}
}
class CustomCheckBoxTile extends BaseCheckBoxTile{
bool isChecked;
Function(bool) checkBoxOnChanged;
CustomCheckBoxTile({this.isChecked =false, required this.checkBoxOnChanged})
:super(isChecked: isChecked, checkBoxOnChanged: checkBoxOnChanged);
}
My instinct is to want to put something in my _BaseCheckBoxTileState that gives me access to the larger widget's view_model, like what I do in the first example with:
"MyScreenViewModel model = serviceLocator<MyScreenViewModel>(); "
If I had that, then I could assign the values in my _BaseCheckBoxTileState by referring to the model instead of widget (e.g., model.isChecked instead of widget.isChecked). The model would obviously extend ChangeNotifier, and the view that is making use of the custom widget would wrap the widget in a Consumer. However, the _BaseCheckBoxTileState doesn't know what view_model to listen to.
Would I accomplish this by putting some generic Type or Object in for my View_Model which could be assigned when the class is built? Or am I approaching this in a completely wrong way?

How to avoid redundant type and instance of type in widget constructor?

I am creating a wrapper which takes two arguments: a model and a screen.
This wrapper uses ChangeNotifierProvider<T> internally, T being the model type.
Calling this wrapper widget at the moment looks like this:
NotifierWrapper<Preferences>(Preferences(), PreferencesScreen());
The above works, but it's verbose and redundant and frankly bad API design.
This is the wrapper widget:
class NotifierWrapper<T extends ChangeNotifier> extends StatelessWidget {
final T _model;
final _screen;
NotifierWrapper(this._model, this._screen);
#override
Widget build(context) {
return ChangeNotifierProvider<T>(
builder: (_) => _model,
child: _screen
);
}
}
I want this API to look like:
NotifierWrapper(Preferences(), PreferencesScreen());
But this does not work as ChangeNotifierProvider<T> requires a type. Can I provide a type to ChangeNotifierProvider from an instance of T?
Something like this (and variants) fail:
// ...
Type<ChangeNotifier> T = _model.runtimeType;
return ChangeNotifierProvider<T>(
// ...
or otherwise
NotifierWrapper<Preferences>(PreferencesScreen());
Deriving a constructor from a type?
// ...
return ChangeNotifierProvider<T>(
builder: (_) => T()
// ...
If these are not possible as I fear, how can I provide a sane API design in this case?
Tell me there is a way to avoid supplying type Preferences and a instance of Preferences() at the same time!