Everyone, didn't you think that Flutter Riverpod simply allows you to provide arguments .family
when using a Provider ? (I thought so too until recently). Why does it have a name anyway ? I had a question until now, but I recently solved it, so I'll write about it .family
About .family
Riverpod .family
has usage and explanations in the official document , but it allows you to specify arguments when using it. As an example, define a Provider using as follows .family
( Source )
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
return dio.get('http://my_api.dev/messages/$id');
});
id
When using it, you can give the argument as follows. You can specify arguments when using it like a normal function.
Widget build(BuildContext context, WidgetRef ref) {
final response = ref.watch(messagesFamily('id'));
}
It's convenient to use it like a function, but you can also get a unique value.
Unique values .family
As written at the beginning of the official document , it is possible to obtain a unique provider with an ID that identifies the argument as a use. I'll show you that with a sample code.
Unique Provider .family
import 'package:riverpod/riverpod.dart';
// Define the class returned by Provider
class Target {
}
// Define Provider.family. final targetProviderFamily = Provider.family
((ref, arg){
return Target(); // return an instance with the same value regardless of arguments
});
void main() {
final container = ProviderContainer();
// Specify the same argument = 0
final t1 = container.read(targetProviderFamily(0));
final t2 = container.read(targetProviderFamily(0)); // print
becomes true because it is the same instance
(t1 == t2); // true
// Specify different argument = 1
final t3 = container.read(targetProviderFamily(1));
// Comparison of t1 and t3 returns false
print(t1 == t3); // false
}
Explanation of the code
The following will return an instance of targetProviderFamily
regardless of the arguments. Note that Target()
this example returns an instance regardless of the argument
// Define Provider.family. Returns an instance with the same value regardless of the argument
final targetProviderFamily = Provider.family ((ref, arg){
return Target(); // Returns an instance with the same value regardless of the argument
});
Same Value for .family
Next main
, argument=0
extract the values specified for the same provider ==
and compare them. The result at this time true
will be: This Target()
means that if the arguments are the same, it will return the same instance.
// Specify the same argument = 0
final t1 = container.read(targetProviderFamily(0));
final t2 = container.read(targetProviderFamily(0)); // Print(t1 == t2
becomes true because it is the same instance)
); // true
Specifically, it should be because Target() == Target()
it is a different instance, but it means that with the same arguments you are reusing a unique instance without creating an instance again false
true
Different Value for .family
In addition to the above, the following part will compare argument=1
the specified value and the value obtained argument=0
in false
// Specify different argument = 1
final t3 = container.read(targetProviderFamily(1));
// Comparison of t1 and t3 returns false
print(t1 == t3); // false
Different arguments mean different instances.
.family allows you to get a unique value instead of the last name as an argument. By taking advantage of this characteristic, it is possible to reuse without creating unnecessary instances.
Notes on arguments
If you use a literal value as an int
argument, a unique value will be returned, but you need to be careful when using an Object as an argument String
. In order to make sure that Object has the same arguments hashCode
, ==
you need to override Object and Operator.
hashCode== Example of overriding Operator
import 'package:riverpod/riverpod.dart';
// Serve as an argument Class
class Arg {
final String value;
const Arg(this.value);
// override == operator
@override
bool operator == (Object other) {
if (identical(this, other)) {
return true;
}
if (other is Arg) {
return runtimeType == other.runtimeType && value == other.value;
} else {
return false;
}
}
// override hasCode
@override
int get hashCode => value.hashCode;
}
class Target {}
final targetProviderFamily = Provider.family ((ref, arg){
return Target();
});
void main() {
final container = ProviderContainer();
// Specify the same argument = 0
final t1 = container.read(targetProviderFamily(const Arg('Zero')));
final t2 = container.read(targetProviderFamily(const Arg('Zero') ));
// Return the same instance
print(t1 == t2); // true
// Specify different argument=1
final t3 = container.read(targetProviderFamily(const Arg('One')));
// Different instance becomes
print(t1 == t3); // false
}
If you override the Object or Operator with the argument Object or Class, you can use it in the same way as a hashCode
literal. However, overriding Operator is troublesome, so using equatable makes it easier to write. ==
hashCode
==
.family
Why does Riverpod family
have a name? I think I understood it somehow.
By using frequently used providers .family
and allowing them to be reused, your app will be more resource efficient..family
The weakness is that the number of arguments is limited to one. However, as a solution when using multiple values, it is possible to use the above-mentioned equatable or tuple in the official document (I often use tuple because it is troublesome to define a class as an argument).