Family
Riverpod'un en güçlü özelliklerinden biri "Family"lerdir.
Kısaca, bir provider'ın benzersiz bir parametre kombinasyonuna dayanarak
birden fazla bağımsız durumla ilişkilendirilmesini sağlar.
Tipik bir kullanım senaryosu, yanıtın bazı parametrelere (kullanıcı ID'si, arama sorgusu veya sayfa numarası gibi) bağlı olduğu uzak bir API'den veri çekmektir. Bu sayede, olası her parametre kombinasyonunu çekmek ve önbelleğe almak için tek bir provider tanımlayabilirsiniz.
Sıradan provider'lar bir değişkene benzetilebiliyorsa, "family" provider'ları da bir Map'e benzetilebilir.
Bir Family oluşturmak
Bir family tanımlamak, provider tanımını bir parametre alacak şekilde hafifçe değiştirmekten ibarettir.
Fonksiyonel provider'lar için sözdizimi şu şekildedir:
- riverpod
- riverpod_generator
// When not using code-generation, providers can use ".family".
// This adds one generic parameter corresponding to the type of the parameter.
// The initialization function then receives the parameter.
final userProvider = FutureProvider.autoDispose.family<User, String>((ref, id) async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
return User.fromJson(response.data);
});
Future<User> user(
Ref ref,
// When using code-generation, providers can receive any number of parameters.
// They can be both positional/named and required/optional.
String id,
) async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
return User.fromJson(response.data);
}
Notifier provider'lar için ise sözdizimi şöyledir:
- riverpod
- riverpod_generator
// With notifiers providers, we also use ".family" and receive and extra
// generic argument.
// The main difference is that the associated Notifier needs to define
// a constructor+field to accept the argument.
final userProvider = AsyncNotifierProvider.autoDispose.family<UserNotifier, User, String>(
UserNotifier.new,
);
class UserNotifier extends AsyncNotifier<User> {
// We store the argument in a field, so that we can use it
UserNotifier(this.id);
final String id;
Future<User> build() async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
return User.fromJson(response.data);
}
}
class UserNotifier extends _$UserNotifier {
Future<User> build(
// When using code-generation, Notifiers can define parameters on their
// "build" method. Any number of parameter can be defined.
String id,
) async {
final dio = Dio();
final response = await dio.get('https://api.example.com/users/$id');
// The generated class will naturally have access to the parameters
// passed to the "build" method in "this":
print(this.id);
return User.fromJson(response.data);
}
}
Kesinlikle zorunlu olmasa da, family kullanırken Otomatik yok etme özelliğini etkinleştirmeniz şiddetle tavsiye edilir.
Bu sayede, parametre değiştiğinde ve önceki duruma artık ihtiyaç kalmadığında bellek sızıntılarının önüne geçilir.
Bir Family kullanmak
Parametre alan provider'ların kullanımı da biraz farklılaşır.
Uzun lafın kısası, provider'ınızın beklediği parametreleri şu şekilde iletmeniz gerekir:
final user = ref.watch(userProvider('123'));
İletilen parametrelerin tutarlı bir ==/hashCode davranışına sahip olması gerekir.
"family"yi bir Map olarak düşünün: parametreler anahtar, provider'ın durumu ise değerdir.
Dolayısıyla bir parametrenin ==/hashCode değeri değişirse, elde edilen değer de
farklı olacaktır.
Bu nedenle aşağıdaki gibi bir kod yanlıştır:
// Hatalı parametre, çünkü `[1, 2, 3] != [1, 2, 3]`
ref.watch(myProvider([1, 2, 3]));
Bu hatayı fark etmenize yardımcı olması için riverpod_lint paketini kullanmanız ve provider_parameters lint kuralını etkinleştirmeniz önerilir. Böylece yukarıdaki kod parçası bir uyarı gösterir. Kurulum adımları için Başlarken bölümüne bakın.
İstediğiniz kadar "family" provider'ı okuyabilirsiniz; hepsi birbirinden bağımsız olacaktır. Dolayısıyla şu kullanım geçerlidir:
class Example extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final user1 = ref.watch(userProvider('123'));
final user2 = ref.watch(userProvider('456'));
// user1 ve user2 birbirinden bağımsızdır.
}
}
Bir family'nin tüm aktif provider'larına erişmek
ProviderContainers/ProviderScopes kullanarak, bir `Family` ile ilişkili tüm provider'lara `allProviders` metodu sayesinde erişebilirsiniz:for (final reference in ref.container.allProviders(family: userProvider)) {
ref.invalidate(reference.provider);
}
Family'leri geçersiz kılmak
Testlerde bir provider'ı sahte bir örnekle değiştirmek isterken, bir family provider'ını geçersiz kılmak isteyebilirsiniz.
Bu durumda iki seçeneğiniz vardır:
- Yalnızca belirli bir parametre kombinasyonunu geçersiz kılmak:
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider('123').overrideWith((ref) => User(name: 'User 123')),
],
child: const MyApp(),
),
); - Tüm parametre kombinasyonlarını geçersiz kılmak:
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider.overrideWith((ref, arg) => User(name: 'User $arg')),
],
child: const MyApp(),
),
);