I want to create provider that will return String with Riverpod. I'm using Provider as below. I'm getting an error The instance member 'name' can't be accessed in an initializer. Try replacing the reference to the instance member with a different expression. May someone tell me how should I create provider?
class LearnUser {
final String uid;
final String name;
final usernameProvider = Provider((ref) {
return name;
});
LearnUser({required this.uid});
}
I would separate the provider from the LearnUser class. It gives cleaner separation of concerns in my opinion. And what i understood from the Riverpod docs they are ment as global vars. Like the above poster mentioned your code won't work cause you are trying to access an instance variable.
Maybe something like this with a user service. I would also advice you look into StateNotifier with StateNotifierProvider you could use for example to create a user service.
// Creating the provider as global variable
final userNameProvider = Provider<String>((ref) =>
ref.watch(userService.state)?.user.name ?? '';
);
'name' is instance member. You need an instance to access a member variable.
You can try this
class LearnUser {
final String uid;
String name;
Provider usernameProvider;
LearnUser({#required this.uid}){
this.usernameProvider = Provider((ref) {
return this.name;
});
}
}
Related
I am making first steps with Riverpod and just want to check if my understanding of handling changes of some data class properties using Riverpod is correct.
Imagine, I have a data class like that:
class MyClass {
final String name;
final int id;
const MyClass(this.name, this.id);
}
Then I create a StateNotifier:
class MyClassStateNotifier extends StateNotifier<MyClass> {
MyClassStateNotifier(MyClass state) : super(state);
void setName(String name) {
state.name = name;
}
}
And this won't work - UI will not be rebuilt after calling setName this way.
So I need to modify classes in the following way:
class MyClass {
final String name;
final int id;
const MyClass(this.name, this.id);
MyClass copyWith({name, id}) {
return MyClass(name ?? this.name, id ?? this.id);
}
}
and the StateNotifier as following:
class MyClassStateNotifier extends StateNotifier<MyClass> {
MyClassStateNotifier(MyClass state) : super(state);
void setName(String name) {
state = state.copyWith(name: name);
}
}
This pair will work and the UI will be rebuilt.
So, my question: does one always need to reinstantiate the object in this way?..
From my perspective, this is a bit strange (simple datatypes like String / int do not require this) and the boilerplate for copyWith method might become pretty huge if I have a dozen of object's properties.
Is there any better solution available for Riverpod or is it the only one and correct?..
Thanks in advance! :)
To trigger a state change you have to use the state setter. The implementation looks like this:
#protected
set state(T value) {
assert(_debugIsMounted(), '');
final previousState = _state;
_state = value;
/// only notify listeners when should
if (!updateShouldNotify(previousState, value)) {
return;
}
_controller?.add(value);
// ...
The internal StreamController<T> _controller needs to be triggered (add), to notify listeners (in this case riverpod) about updates.
By using state.name = something you're not informing the StateNotifier about a new state (not calling the state setter). Only your object holds the new value but nobody was notified.
Your state is mutable and that very often leads to such misbehavior. By using an immutable object you can prevent such errors in the first place. Write it yourself or use freezed.
Learn more about immutability in my talk
in the flutter when you are defining a model. the convention is to define properties as final and write a copyWith for class instead of defining non-final vars and removing the copyWith method. what is the exact reason for this? is it a flutter performance thing?
for example:
class Emplyee {
final String name;
final String id;
Emplyee({required this.name, required this.id});
Emplyee copyWith({String? name, String? id}) {
return Emplyee(id: id ?? this.id, name: name ?? this.name);
}
Map<String, dynamic> toJson() => {
"name": name,
"id": id,
};
Emplyee.fromJson(Map<String, dynamic> json)
: name = json["name"],
id = json["id"];
}
P.S. I know this convention makes sense in widgets. but my question is about data model classes.
It's for Immutability. Mutable class is error-prone.
So basically the copyWith method makes it possible for you to create a new instance of the class and then you can edit whatever you want in this new instance of the class, without affecting data in the original class.
So that's what the copyWith method does, I don't think it's for performance, I think it just aids some coding use cases.
Immutability reduces the risk of errors by side effects. Have a look at this code:
class User {
String name;
User({required this.name});
}
void main() {
final user = User(name: 'Stefan');
someFunction(user);
print(user.name);
}
someFunction(User user){
user.name = 'Thomas';
}
This snippet prints 'Thomas' because the function manipulates the user object. In the main function, you have no chance to know what happens with the object.
With immutability, this would not be possible. It would be necessary to create a new instance of User to have a User named 'Thomas'. The instance in the main function would be the same.
Using final makes a class immutable.
You can check Why do we need immutable class?
Immutable class is good for caching purpose and it is thread safe.
The reason behind using copyWith() is flexible, allowing any number of properties to be updated in a single call.
More about immutable-data-patterns-in-dart-and-flutter
Lets say a User in my App can note his weight. Now I want to use this weight in many other Widgets all over my App for example to calculate some data, which depends on the user weight.
Is it a good practise to use a static variable like this:
class UserManager {
static double weight;
}
So now I have access to the User weight in every Class and can make calculations for example:
double value = UserManager.weight * 0.4;
Is this a good practise or are there some better solutions?
You can use GetStorage()
final appData = GetStorage();
appData.writeIfNull("data", false);
bool yourVar = appData.read("data");
or SharedPreferences
var yourData;
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
void _getSomeTh() async {
final prefs = await _prefs;
final result = prefs.getBool("data");
yourData = result;
}
It would be better not to work with static variables, but to have an object that you distribute to all widgets in the widget tree. You can achieve this, for example, with an InheritedWidget or with packages such as BLoC or Provider. Making such data static seems to me to be an anti-pattern.
With the BLoC pattern:
class User {
final double weight;
User({required this.weight});
}
class UserManager extends Cubit<User>{
...
}
This way you have a state managing system with which you could very easily rebuild all the widgets concerned when the weight changes.
For a simple requirement as this, the static variable approach might be fine. However, for a more complex object that you wish to share in a hierarchy of widgets, you should use packages like provider
For example, you have a User object that you wish to share app-wide, then
class User with ChangeNotifier {
late String uuid;
late String name;
// Our one and only one static instance on which we operate
static late User? instance;
// There is a good chance that the init functions like below are async too
static void initUser(String uuid, String name) {
instance = User._init(uuid, name);
}
// It is usually the 'instance' that calls the below function
void changeFunction(/*params*/){
// Change the user instance, say, assign it to null if a user logs out
// More importanly, notify the listeners about the change
notifyListeners();
}
// Note this constructor is not exposed to the public.
User._init(this.uuid,this.name);
}
Then propagate the User object using a ChangeNotifierProvider using the guidelines mentioned here
I'm learning flutter coming from a react background. I want to use my model in another class.
This is my model
class User {
final String id;
final String userName;
User({
this.id,
this.userName,
});
}
On my Widget i want to use the properties of that model, so i can get some type safe.
class _SignUpScreenState extends State<SignUpScreen> {
#override
Widget build(BuildContext context) {
final User _user; // I get an error on this line.
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
I get teh following error.
The value of the local variable '_user' isn't used.
Try removing the variable, or using it.dartunused_local_variable
The final variable '_user' must be initialized.
Try initializing the variable.
I'm a little bit confused how do you properly infer a model on class widget?
Think about the model as a template - the way you are initializing the model - means there is nothing in it, and using the final variable means it will never change.
This causes an error, because the object is effectively null, and always will be.
If you initialize a variable with final you need give it data:
final User _user = User(id: 1, userName: test);
print(_user);
//Prints: Instance of User
Otherwise, don't use the final variable, and you can assign data to _user later"
User _user;
_user = User(id: 1, userName: test);
print(_user);
//Prints: Instance of User
I will explain what your error means one by one.
Flutter will have a warning that you are creating a value that you aren't using.
The value of the local variable '_user' isn't used.
This is what they suggested:
Try removing the variable, or using it.dartunused_local_variable
These two error code is saying that your variable has to be instantiated:
The final variable '_user' must be initialized.
Try initializing the variable.
The solution to your problem is to instansiate User:
final User _user=User(id:"myId",userName:"myUserName");
Note that the final keyword is means that you are declaring a variable that will never have its value change.
https://flutter.dev/docs/cookbook/networking/fetch-data
In the last 'complete example' of the above page,
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
It is an Album class to receive the JSON string received in the request and handle it in the application,
The constructor provides a factory constructor in addition to the normal constructor.
About the factory constructor,
https://dart.dev/guides/language/language-tour#constructors
I have read the Factory constructors section of the above page.
The factory constructor of the Logger class in the sample does not always create a new instance, so
I can understand adding the factory keyword,
Is it necessary to use the factory constructor even in the Album class of this Complete example?
In the case of the Album class, since the normal constructor is used in the factory constructor,
I feel that this factory constructor (Album.fromJson) always creates a new instance.
In fact
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/16');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var temp=json.decode(response.body);
return Album(userId:temp['userId'],id:temp['id'],title:temp['title']);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
As you can see, it seems that it works without any problems even if I try using only the normal constructor.
Is there any advantage to preparing and using a factory constructor?
Or is there something wrong without using the factory constructor in this situation?
I'm not sure when to use the factory constructor in the first place,
Is there a clear definition?
Before diving into factory as keyword in flutter, you might have a look at Factory as design pattern to have the full picture in mind.
The main benefit of using factory design pattern
That is, the Factory Method design pattern defines an interface for a class responsible for creating an object, hence deferring the instantiation to specific classes implementing this interface. This resolves the issue of creating objects directly within the class which uses them.
Also, it enables compile-time flexibility via subclassing. When objects are created within the class, it is very inflexible since you cannot change the instantiation of the object independently from the class — the class is committed to a particular object. By implementing the pattern, subclasses can be written to redefine the way an object is created.
For more info, see here
and as docs refers
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype. Another use case for factory constructors is initializing a final variable using logic that can’t be handled in the initializer list.
So it's all about hiding the creation logic from outside world.
And of course you can do the following
return Album(userId:temp['userId'],id:temp['id'],title:temp['title']);
But if you did that in many different components or classes let's say, so whenever you change the logic behind the creation of Album object you will be in-need to change it over all places.
On the other hand the classes which use Album class they only care about having an Object of Album, they care not about how it got instantiated, so if you put the logic of having an instance outside the class itself you are going into what's called spaghetti code
You can use factory to test for example if the json data returned by som request is null so you return a null dirctly by the use of factory named constructor for example look at this
//class for Product, Brand, Model
class PBM {
static const String pbmCollectionName = 'productsBrandsModels';
static const String pbmIdField = 'pbmId';
static const String pbmNameField = 'pbmName';
static const String parentIdField = 'parentId';
static const String iconUrlField = 'iconUrl';
//general
final String pbmId;
final String pbmName;
final String parentId;
//icon
final String iconUrl;
PBM({
this.pbmId,
this.pbmName,
this.parentId,
this.iconUrl,
});
Map<String, dynamic> toMap() {
return {
'pbmId': pbmId,
'pbmName': pbmName,
'parentId': parentId,
'iconUrl': iconUrl,
};
} //end of toMap method
factory PBM.fromFirestore(Map<String, dynamic> firestore) {
//here the benefit of factory comes into play: it will return a null
//otherwise it gonna create the object
if (firestore == null) return null;
return PBM(
pbmId: firestore['pbmId'],
pbmName: firestore['pbmName'],
parentId: firestore['parentId'],
iconUrl: firestore['iconUrl'],
);
} //end of PBM.fromFirestore named constructor
} //end of PBM class