Riverpod Family Modifier | Deep Dive

Riverpod Family Modifier | Deep Dive

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

    Courses


    Recommended posts


    Recent posts