Riverpod — ένας μεταγλωττίσιμος διαχειριστής κατάστασης και εξαρτήσεων για Flutter, που δημιουργήθηκε από τον Rémi Roussel το 2021 ως διάδοχος του Provider. Το Riverpod λύνει τα θεμελιώδη προβλήματα του Provider: έλλειψη ελέγχου μεταγλώττισης, εξάρτηση από το BuildContext και δυσκολία με το ProviderNotFoundException. Σύμφωνα με τα δεδομένα του pub.dev, το πακέτο έχει συγκεντρώσει πάνω από 5 χιλιάδες likes και αντικαθιστά ενεργά το Provider σε νέα έργα.
Κύρια Σημεία
Riverpod — μια βιβλιοθήκη για διαχείριση κατάστασης και έγχυση εξαρτήσεων στο Flutter που μεταγλωττίζει την περιγραφή των provider σε ασφαλή κώδικα Dart. Σε αντίθεση με το Provider, οι provider του Riverpod δεν είναι δεσμευμένοι στο BuildContext: δημιουργούνται καθολικά ή στο ProviderScope και είναι προσβάσιμοι από οπουδήποτε. Ο μεταγλωττιστής ελέγχει τους τύπους, τις εξαρτήσεις και την ακεραιότητα του γράφου provider στο στάδιο της δημιουργίας, εξαλείφοντας σφάλματα χρόνου εκτέλεσης όπως το ProviderNotFoundException.
Το Riverpod χρησιμοποιεί το μοντέλο override για δοκιμές: κάθε provider μπορεί να παρακαμφθεί μέσω του ProviderScope.overrideWith χωρίς να χρειάζεται δημιουργία υποκλάσεων ή mock διεπαφών. Αυτό καθιστά τη δοκιμή απομονωμένη: κάθε δοκιμή λαμβάνει το δικό της αντίγραφο του γράφου εξαρτήσεων που ελέγχεται πλήρως.
Σύμφωνα με το Flutter Community Survey 2025, το Riverpod κατατάσσεται τρίτο σε δημοτικότητα μετά το Provider και το BLoC. Ταυτόχρονα, το Riverpod είναι το ταχύτερα αναπτυσσόμενο πακέτο: +120% εγκαταστάσεις το 2024. Κύριοι λόγοι: ασφάλεια μεταγλώττισης, απουσία ProviderNotFoundException, ενσωματωμένη υποστήριξη ασύγχρονου προγραμματισμού μέσω του AsyncValue.
Το Riverpod παρέχει 8 τύπους provider, ο καθένας για συγκεκριμένο σενάριο: Provider (σταθερά/υπηρεσία), StateProvider (πρωταρχική κατάσταση), StateNotifierProvider (σύνθετη λογική με StateNotifier), ChangeNotifierProvider (για μετεγκατάσταση από Provider), FutureProvider (ασύγχρονα δεδομένα, μία φορά), StreamProvider (αντιδραστική ροή), NotifierProvider (νέο API, Flutter 3.10+) και AsyncNotifierProvider (ασύγχρονο Notifier).
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}ProviderRef — το αντικείμενο που μεταβιβάζεται σε κάθε provider για πρόσβαση σε άλλους provider. ref.watch — εγγραφή σε αλλαγές, ref.read — μία ανάγνωση, ref.invalidate — επαναφορά προσωρινής μνήμης. Το ProviderRef αντικαθιστά το BuildContext από το Provider: οποιοσδήποτε provider μπορεί να διαβάσει άλλους provider χωρίς πρόσβαση στο δέντρο widget. Αυτό επιτρέπει τη δημιουργία γράφου εξαρτήσεων εκτός του επιπέδου UI.
ProviderScope — το υποχρεωτικό ριζικό widget για τη λειτουργία του Riverpod. Το ProviderScope αποθηκεύει όλους τους provider, διαχειρίζεται τον κύκλο ζωής τους και αποθηκεύει τιμές στην προσωρινή μνήμη. Χωρίς ProviderScope, η εφαρμογή θα καταρρεύσει με ProviderNotFoundException. Το ProviderScope μπορεί να είναι ένθετο — το ένθετο scope παρακάμπτει τους provider του γονέα, το οποίο χρησιμοποιείται για δοκιμές και απομόνωση λειτουργιών.
AsyncValue — μια sealed κλάση του Riverpod για την αναπαράσταση ασύγχρονης κατάστασης. Το AsyncValue έχει τρεις παραλλαγές: AsyncData (επιτυχημένα δεδομένα), AsyncError (σφάλμα), AsyncLoading (φόρτωση). Αντί για χειροκίνητη εναλλαγή μεταξύ loading/error/data, κάθε FutureProvider ή StreamProvider επιστρέφει αυτόματα AsyncValue, και το widget διαχειρίζεται και τις τρεις καταστάσεις μέσω του ref.watch.
final userProvider = FutureProvider((ref) async {
final api = ref.watch(apiProvider);
return await api.fetchUser();
});
class UserScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => UserWidget(user),
error: (e, _) => ErrorWidget(e.toString()),
loading: () => CircularProgressIndicator(),
);
}
}AsyncValue.when — μέθοδος για αντιστοίχιση προτύπων και των τριών καταστάσεων. Ο μεταγλωττιστής ελέγχει ότι και οι τρεις περιπτώσεις έχουν αντιμετωπιστεί — αν ξεχαστεί το loading ή το error, ο κώδικας δεν θα μεταγλωττιστεί. AsyncValue.whenData — μόνο για data (αν loading/error δεν χρειάζονται). AsyncValue.guard — ένα περιτύλιγμα try-catch για μετατροπή εξαίρεσης σε AsyncError. keepAlive — μια σημαία που αποτρέπει την καταστροφή της προσωρινής μνήμης του provider κατά την έξοδο από το πεδίο ορατότητας.
Δημιουργία κώδικα — ένα βασικό χαρακτηριστικό του Riverpod 2.0+. Ο σχολιασμός @riverpod πάνω από μια συνάρτηση δημιουργεί αυτόματα έναν provider με τον σωστό τύπο, υποστήριξη αναδόμησης και αυτόματη συμπλήρωση. Η δημιουργία κώδικα χρησιμοποιεί riverpod_generator και build_runner. Ο προγραμματιστής γράφει μια καθαρή συνάρτηση και όλα τα άλλα — τύποι, κλάσεις, factory constructors — δημιουργούνται αυτόματα.
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello World';
}
// Δημιουργήθηκε: final helloWorldProvider = Provider((ref) => 'Hello World');
@riverpod
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}Notifier — ένα νέο API για μεταβλητή κατάσταση με δημιουργία κώδικα. Το Notifier είναι μια κλάση με μέθοδο build() και μεθόδους αλλαγής κατάστασης. Σε αντίθεση με το StateNotifier, το Notifier δεν απαιτεί ξεχωριστή κλάση κατάστασης και παρέχει άμεση πρόσβαση στο state μέσω getter/setter. Το Riverpod δημιουργεί αυτόματα NotifierProvider για κάθε κλάση Notifier με σχολιασμό @riverpod.
build_runner: η δημιουργία κώδικα εκτελείται με την εντολή dart run build_runner build. Τα αρχεία που δημιουργούνται έχουν επίθημα .g.dart και εισάγονται στον πηγαίο κώδικα. Όταν αλλάζουν οι σχολιασμοί ή οι τύποι provider, η δημιουργία κώδικα πρέπει να επανεκκινηθεί. Το Riverpod 2.x συνιστά τη δημιουργία κώδικα για όλα τα νέα έργα — η μη αυτόματη δημιουργία provider καθίσταται ξεπερασμένη.
Κύριες διαφορές του Riverpod από το Provider: ανεξαρτησία από το BuildContext, ασφάλεια μεταγλώττισης, ενσωματωμένη εργασία με ασύγχρονο προγραμματισμό, αυτόματη προσωρινή αποθήκευση και δοκιμή μέσω override. Το Provider απαιτεί BuildContext για πρόσβαση στην κατάσταση (context.watch, context.read), το Riverpod χρησιμοποιεί WidgetRef και καθολικά δηλωμένους provider.
| Χαρακτηριστικό | Provider | Riverpod |
|---|---|---|
| Εξάρτηση από BuildContext | Ναι | Όχι |
| Έλεγχος μεταγλώττισης | Όχι | Ναι (μέσω @riverpod) |
| ProviderNotFoundException | Runtime | Αδύνατο |
| Ασύγχρονος προγραμματισμός | Χειροκίνητα | AsyncValue (ενσωματωμένο) |
| Δοκιμή | Περιτύλιγμα σε Provider | ProviderScope.overrideWith |
| Προσωρινή αποθήκευση | Όχι | Αυτόματη + keepAlive |
Μετεγκατάσταση από Provider: Το Riverpod υποστηρίζει ChangeNotifierProvider.adaptive για χρήση υπαρχόντων ChangeNotifier χωρίς επανεγγραφή. Σταδιακή μετεγκατάσταση: πρώτα οι νέες λειτουργίες γράφονται με Riverpod, στη συνέχεια οι παλιοί Provider αντικαθίστανται με provider του Riverpod μέσω προσαρμογέα. Και τα δύο πακέτα μπορούν να συνυπάρχουν στο ίδιο έργο, επιτρέποντας τη μετεγκατάσταση χωρίς πάγωμα της ανάπτυξης.
Η δοκιμή του Riverpod βασίζεται στο ProviderScope.overrideWith. Κάθε provider παρακάμπτεται μέσα στο ProviderScope δοκιμής χωρίς mock και δοχεία DI. ProviderContainer — ένα απομονωμένο περιβάλλον για δοκιμές χωρίς Flutter (καθαρό Dart), που επιτρέπει τη δοκιμή provider χωρίς απόδοση widget.
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
void main() {
test('Counter increments correctly', () {
final container = ProviderContainer();
container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider), 1);
});
testWidgets('UI updates on increment', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [counterProvider.overrideWithValue(5)],
child: CounterScreen(),
),
);
expect(find.text('5'), findsOneWidget);
});
}ProviderContainer — χωρίς Flutter. Χρησιμοποιήστε το ProviderContainer για μοναδιαίες δοκιμές provider χωρίς widget. overrideWithValue — αντικατάσταση provider με συγκεκριμένη τιμή. overrideWith — αντικατάσταση με εργοστάσιο provider (για mock υπηρεσιών). autodispose — στις δοκιμές ελέγξτε αν ο provider καταστρέφεται κατά την έξοδο από το πεδίο ορατότητας, με container.dispose().
Συχνές Ερωτήσεις
Riverpod — βιβλιοθήκη διαχείρισης κατάστασης με καθολικούς provider, AsyncValue και δημιουργία κώδικα. BLoC — αρχιτεκτονικό μοτίβο με Event → Stream → State. Το Riverpod είναι ευκολότερο στην εκμάθηση και έχει καλύτερη εμπειρία προγραμματιστή μέσω των σχολιασμών @riverpod. Το BLoC παρέχει αυστηρή απομόνωση επιχειρηματικής λογικής και παρακολούθηση Event μέσω του BlocObserver. Η επιλογή εξαρτάται από το παράδειγμα του έργου: το Riverpod είναι πιο κοντά στο Provider, το BLoC — στα αντιδραστικά ρεύματα.
Autodispose — ο μηχανισμός αυτόματης καταστροφής του provider όταν κανείς δεν είναι εγγεγραμμένος σε αυτόν. Από προεπιλογή, όλοι οι provider του Riverpod είναι autodispose: όταν το widget εγκαταλείπει το δέντρο, ο provider αφαιρείται από τη μνήμη. keepAlive — μια σημαία που απενεργοποιεί το autodispose για provider που πρέπει να ζουν πάντα (πελάτες API, αποθετήρια, ρυθμίσεις). Αυτό αποτρέπει διαρροές μνήμης — οι μη χρησιμοποιούμενοι provider καταστρέφονται αυτόματα.
ref.invalidate — μια μέθοδος που αναγκάζει την επαναφορά της προσωρινής μνήμης του provider. Μετά το invalidate, ο provider αναδημιουργείται στην επόμενη ανάγνωση: το FutureProvider εκτελεί ξανά την async συνάρτηση, το StreamProvider εγγράφεται ξανά στη ροή. Χρησιμοποιήστε το invalidate για αναγκαστική ανανέωση δεδομένων (pull-to-refresh, αλλαγή χρήστη). ref.refresh — ένας συνδυασμός invalidate + ανάγνωση: επαναφέρει και διαβάζει αμέσως τη νέα τιμή σε μία λειτουργία.
Ναι. Το Riverpod 1.x λειτουργεί μόνο χωρίς δημιουργία κώδικα — οι provider δημιουργούνται χειροκίνητα μέσω Provider(), StateNotifierProvider(), FutureProvider() κ.λπ. Το Riverpod 2.x υποστηρίζει και τις δύο προσεγγίσεις. Χωρίς δημιουργία κώδικα υπάρχει περισσότερος boilerplate, αλλά καμία εξάρτηση από build_runner και dart run build_runner build. Για μικρά έργα (έως 30 provider) η χειροκίνητη δημιουργία είναι δικαιολογημένη, για μεγάλα έργα η δημιουργία κώδικα είναι υποχρεωτική.
Family — ένας τροποποιητής provider που δέχεται μια εξωτερική παράμετρο. Για παράδειγμα, userProvider(123) — ένας provider που φορτώνει τον χρήστη με ID 123. Οι Family provider αποθηκεύουν το αποτέλεσμα για κάθε μοναδική παράμετρο ξεχωριστά. Χρησιμοποιήστε το Family για μια λίστα στοιχείων όπου κάθε στοιχείο φορτώνεται με ID. Ο τροποποιητής Family είναι διαθέσιμος για όλους τους τύπους provider: Provider.family, FutureProvider.family, StreamProvider.family.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης