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

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!

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.

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 correct way to specify generic argument type on callback function

I'vo got a strange error, for a class similar to this one:
class UpdatableListPage<T> extends ConsumerStatefulWidget {
final StateNotifierProvider<UpdatableNotifier, List<T>> provider;
final Widget Function(T t) callbackWidget;
[...]
#override
_UpdatableListPageState<T> createState() => _UpdatableListPageState<T>();
}
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
#override
Widget build(BuildContext context) {
// Here the IDE said modelList is dynamic
var modelList = ref.watch(widget.provider);
[...]
ListView(
key: _refreshKey,
shrinkWrap: true,
scrollDirection: widget.scrollDirection,
children: [
for (final product in modelList as List<T>) widget.callbackWidget.call(product),
],
}
}
And I call the funciton as:
UpdatableListPage<RsMsgMetaData>(
userPostsProvider,
callbackWidget: (t) => PostTeaserCard(t,),
),
Where PostTeaserCard is a statefull Widget that recieve a RsMsgMetaData object as parameter.
The IDE say that everything is Ok but at run time, I got the following error:
type '(RsMsgMetaData) => PostTeaserCard' is not a subtype of type '(dynamic) => Widget'`
Seems like callbackWidget acts as (dynamic) => Widget function, but anyway... Should this function be compatible with the function signature of the anonymous function, right?
I don't know what is going on with this...
You wrote:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage> {
which is equivalent to:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<dynamic>> {
Consequently, the type of _UpdatableListPageState<T>.widget.callbackWidget is Widget Function(dynamic t). You cannot pass a PostTeaserCard Function(RsMsgMetaData) for a Widget Function(dynamic) because the latter is callable with any argument, but the function you actually passed can be called only with an RsMsgMetaData argument.
To fix this, fix your _UpdatableListPageState class declaration to avoid the implicit use of dynamic and to fully depend on T:
class _UpdatableListPageState<T> extends ConsumerState<UpdatableListPage<T>> {
This probably will fix the type for modelList to List<T> instead of List<dynamic>.
Enabling the strict-raw-types check in your analysis_options.yaml should help catch this kind of error in the future.

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.

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?