Riverpod Family Modifier | Deep Dive

Created At: 2023-09-10 16:21:08 Updated At: 2023-09-10 16:56:18

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  .familySource )

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 falsetrue

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).

Comment

Add Reviews