Can I pass a setter as an argument to another widget? - flutter

I have a widget (slider) that changes a value.
Right now, I am passing a VoidCallback to each slider, so they can update the individual setter of the provider.
Is it possible to pass the setter in the constructor of the slider, so it can call the setter directly, thus simplifying the construction of the slider (no VoidCallbacks needed)?

You can by wrapping the setter in a function. So a callback is still needed but it's a lean wrapper around the setter. Not much boilerplate to handle here. I made an example on gist/dartpad:
https://dartpad.dev/?id=38f013e47f8c85057c44a0a326df33dd
void main() async {
final a = A(value: 2);
print('A=${a.value}');
final setter = (int i) => a.data = i;
final b = B(callback: setter);
b.exec(10);
print('A=${a.value}');
}
class A {
int value;
A({required this.value});
set data(int newValue) => value = newValue;
}
class B {
final void Function(int) callback;
B({required this.callback});
void exec(int i){
callback(i);
}
}

Related

Read provider inside a global or static method in Flutter

I have a question, regarding reading providers from inside static methods or global methods. I am using riverpod and awesome_notification packages, and I need to alter the state the app, from the action of the notification, for this, the package uses static methods inside a controller class.
class NotificationController{
...
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction) async {
...//some way to access a provider, to call methods on it
}
...
}
If there is another way of doing this that I am not seeing, please let me know.
I have not been able to find a way to do this.
You can:
Pass to the ref function as a parameter.
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction, Ref ref) async {
final some = ref.read(someProvider);
}
Create a class that accepts the ref field in the constructor.
final notificationProvider = Provider((ref) => NotificationController(ref));
// or use tear-off
final notificationProvider = Provider(NotificationController.new);
class NotificationController {
NotificationController(Ref ref) {
_ref = ref;
}
static late final Ref _ref;
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction) async {
final some = _ref.read(someProvider);
}
}
An additional example:
import 'package:riverpod/riverpod.dart';
final valueProvider = Provider<int>((_) => 5);
final managerProvider = Provider(ManagerProvider.new);
class ManagerProvider {
ManagerProvider(Ref ref) {
_ref = ref;
}
static late final Ref _ref;
static int getValue() => _ref.read(valueProvider);
}
void main() {
final container = ProviderContainer();
container.read(managerProvider);
final value = ManagerProvider.getValue();
print(value); // 5
}
Either way, you should always have access to `Ref'.
Update:
As #OppositeDragon and #Eran Ravid pointed out, we really can't access _ref in a static method. However, if you define _ref in the constructor, it is possible. I think it's a terrible anti-pattern, though. Use method 1 and you will be fine.

Flutter - Provider - Different ChangeNotifier instance for different context

I created two widgets from same custom StatefulWidget class. I want them to use separate ChangeNotifier instance from same ChangeNotifier derived class because they need to consume different data set. Unfortunately just like below example, it's not working like I want it to. Both read() and watch() respectively write and read data on the same ChangeNotifier instance.
Wait a minute. Isn't that what Provider supposed to do?. Yes I know. I'm aware that. But now I just need a little flexibility. I think I'm just using Provider the wrong way if I'm not wrong.
Thank you for your help. Greatly appreciate it.
MultiProvider App() => MultiProvider(
...
providers : [
...
ChangeNotifierProvider(create : (_) => Notifier()),
]
);
class TestState extends State<Test>{
GlobalKey<CounterState> gk1 = GlobalKey<CounterState>();
GlobalKey<CounterState> gk2 = GlobalKey<CounterState>();
#override
Widget build(BuildContext context){
...
.. [Counter(gk1), Counter(gk2)]
...
.. onPressed: (){
.. gk1.currentState?.increment(1);
.. gk2.currentState?.increment(2);
.. },
...
}
}
class CounterState extends State<Counter>{
#override
Widget build(BuildContext context){
...
.. context.watch<Notifier>().count
...
}
void increment(int v){
context.read<Notifier>().count += v;
}
}
class Notifier with ChangeNotifier{
int _count = 0;
int get count => _count;
void set count(int v){
_count = v;
notifyListeners();
}
}
I remember facing this exact issue when I was using this package.
I did a little search but couldn't find much info about it, so I decided to change approach.
Instead of having N providers of the same type, I now create 1 provider containing all the info for the N widgets.
In your case I would do something like:
class Notifier with ChangeNotifier{
List<int> _counts = [0, 0];
int getCountAt(int index) {
return _counts[index]; //Control list lenght of course
}
void set count(int index, int v){
_count[index] = v;
notifyListeners();
}
}
Relying on index to access the correct counter might not work.
If that's the case, you can create a more complex object to access it's counter (maybe an id? a UniqueKey?).
I guess there's no other way yet to solve this problem easily. Either I was missing something or Provider doesn't yet support binding with ChangeNotifier by any passed instance directly not by only a single instance of the class. So I marked Axel's as the answer for now.
But based on the example in the question, for my case I use state instance for the key to identify the different data set, and binding it with a "change notifier instance" exactly.
class CounterState extends State<Counter>{
#override
Widget build(BuildContext context){
...
.. context.watch<Notifier>().instance(this).count
...
}
void increment(int v){
context.read<Notifier>().instance(this).count += v;
}
}
class Notifier with ChangeNotifier{
Map<State, NotifierInstance> _instance = {};
NotifierInstance instance(State state){
if(_instance[state] == null) _instance[state] = NotifierInstance(this);
return _instance[state]!;
}
}
class NotifierInstance{
Notifier notifier;
int _count = 0;
NotifierInstance(this.notifier);
int get count => _count;
void set count(int v){
_count = v;
notifier.notifyListeners();
}
}

How to pass initial value to a Notifier using family modifier?

This is my Notifier:
class Counter extends Notifier<int> {
final int initial;
Counter(this.initial);
#override
int build() => initial;
}
I need to pass initial value to it, but I'm unable to do that using the family modifier anymore.
// Error
final counterProvider = NotifierProvider.family<Counter, int, int>((initial) {
// How to get the initial value to pass here?
return Counter(initial);
});
The syntax for using family/autoDispose using Notifier/AsyncNotifier is different. You're supposed to change the inherited type
So instead of:
final provider = NotifierProvider(MyNotifier.new);
class MyNotifier extends Notifier<Value> {
With family you should do:
final provider = NotifierProvider.family(MyNotifier.new);
class MyNotifier extends FamilyNotifier<Value, Param> {
And the same reasoning applies with autoDispose.

What does " int? get priority => 1;" do?

I was reading a flutter code as below:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_get_app/user_controller.dart';
class AuthMiddleware extends GetMiddleware {
final authService = UserController.findOrInitialize; // Here is error, this line can't find UserController
#override
int? get priority => 1;
bool isAuthenticated = false;
#override
RouteSettings? redirect(String? route) {
isAuthenticated = true;
if (isAuthenticated == false) {
return const RouteSettings(name: '/login');
}
return null;
}
}
When I reached to the following line, I couldn't understand it's syntax and how does it work?
int? get priority => 1;
int? Means it is an int but the int can be null
=> 1 Means () {return 1;}
This is a so-called getter. Getters can be used to provide read access to class properties.
They can also return values directly, like in your case.
They are accessed like properties of the class they are declared in:
final middleWare = AuthMiddleware();
final priority = middleWare.priority;
In your case the getter probably must or can be implemented (see the #override annotation), since all implementations of a middleware must declare their priority, I guess. Since the declared type is int? it may also return null instead of an integer.
Getters can be declared using an expression. Like in your case. Using a block body does also work:
int? get priority {
return 1;
}

Flutter object is not initialized after initState

I'm new to flutter, and i bumped into a problem.
I have a Feed model in my app that looks like this:
import 'package:uuid/uuid.dart';
class Feed {
// Static Members
var uuid = new Uuid();
// Members
String id;
bool isScheduled;
DateTime createdTime;
DateTime feedingTime;
String deviceId;
// Constructors
Feed({this.feedingTime, this.deviceId, this.isScheduled}) {
id = uuid.v4();
createdTime = DateTime.now();
}
Feed.fromDevice(deviceId) {
Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
}
Now i have my AddFeedForm that i'm trying to initialize with default values, in the InitState:
class _AddFeedFormState extends State<AddFeedForm> {
// Final Members
final _formKey = GlobalKey<FormState>();
final List<Machine> _devices = machinesFromServer;
// Members
Feed _feed;
#override
void initState() {
_feed = Feed.fromDevice(_devices.first.id);
super.initState();
}
But somehow after the initState the _feed parameter stays null!
Any ideas?
But somehow after the initState the _feed parameter stays null!
Are you sure this is the case, and not that you're getting a Feed instance that has null fields?
It looks like your named constructor is incorrect:
Feed.fromDevice(deviceId) {
Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
Here you're calling the default Feed constructor inside a named constructor, but not doing anything with the result - this is creating another Feed and then throwing it away. The one returned by the named constructor has not been initialised.
What you probably wanted was this:
Feed.fromDevice(deviceId):
this(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
This makes the fromDevice constructor call the default constructor for initialisation of the instance, rather than creating another copy that goes unused.
Another option would be to make it a static method:
static fromDevice(deviceId) {
return Feed(deviceId: deviceId, feedingTime: DateTime.now(), isScheduled: false);
}
There wouldn't be much difference in this case.. Constructors seem nicer, but sometimes you might find that you want to a) make initialisation async (static methods can return a Future<Feed> but constructors cannot or b) do more processing of the arguments before they're passed to the real constructor that might not fit nicely in the initialiser call.