Riverpod

Jun's Coding Journey·2023년 9월 16일
0

[Learn] Flutter

목록 보기
21/22

Overview


Riverpod (anagram of Provider) is a reactive caching framework for Flutter/Dart. Using declararive and reactive programming, Riverpod is able to take care of a large part of your application's logic for you. It can perform network-requests with built-in error handling and caching, while automatically re-fetching data when necessary.


 

Provider Scope


ProviderScope is a widget specific to the Riverpod package in Flutter. It is the root of your application (or a subtree) that enables the use of providers. Without ProviderScope, you won't be able to read from or provide any state using Riverpod in the respective part of your widget tree.

When using Riverpod for state management in Flutter, you often wrap the root of your application (typically the MaterialApp or CupertinoApp) with ProviderScope.

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Riverpod Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

 

Notifier Provider


NotifierProvider is a specific kind of provider from the Riverpod package in Flutter, used in conjunction with the ChangeNotifier class from the Flutter framework. Its primary purpose is to provide and manage an instance of a ChangeNotifier in a manner that's more integrated with Riverpod's philosophy and features.

If you're familiar with the Provider package in Flutter, you might know the ChangeNotifierProvider. The concept in Riverpod with NotifierProvider is quite similar but adapted to the Riverpod ecosystem.

// define
class CounterNotifier extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}
// create
final counterProvider = NotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});
// use
final count = watch(counterProvider);
final count = read(counterProvider);
// manipulate
final counterNotifier = context.read(counterProvider.notifier);
counterNotifier.increment();

 

Consumer Widget


ConsumerWidget is a special type of widget that makes it easier to listen to providers. ConsumerWidget is an alternative to the regular StatelessWidget, but with the added capability of "consuming" or "watching" providers directly.

When using ConsumerWidget, you can override the build method which provides an additional watch function. This function can be used to listen to providers and rebuild the widget whenever the provider's data changes.

// define
final helloWorldProvider = Provider<String>((ref) {
  return 'Hello, World!';
});
// use
class HelloWorldWidget extends ConsumerWidget {
  
  Widget build(BuildContext context, ScopedReader watch) {
    final greeting = watch(helloWorldProvider);
    
    return Text(greeting);
  }
}

 

Async Notifier Provider


AsyncNotifierProvider is a provider that is used to listen to and expose an AsyncNotifier. AsyncNotifier is a Notifier that can be asynchronously initialized.
(Async)NotifierProvider along with (Async)Notifier is Riverpod's recommended solution for managing state which may change in reaction to a user interaction.

It is typically used for:

  • exposing a state which can change over time after reacting to custom events.
  • centralizing the logic for modifying some state (aka "business logic") in a single place, improving maintainability over time.
// define
class TimelineViewModel extends AsyncNotifier<List<VideoModel>> {
  List<VideoModel> _list = [];

  void uploadVideo() async {
    state = const AsyncValue.loading();
    await Future.delayed(const Duration(seconds: 2));
    final newVideo = VideoModel(title: "${DateTime.now()}");
    _list = [..._list, newVideo];
    state = AsyncValue.data(_list);
  }

  
  FutureOr<List<VideoModel>> build() async {
    await Future.delayed(const Duration(seconds: 5));
    return _list;
  }
}

final timelineProvider =
    AsyncNotifierProvider<TimelineViewModel, List<VideoModel>>(
  () => TimelineViewModel(),
);
// use
class VideoTimelineScreen extends ConsumerStatefulWidget {
  const VideoTimelineScreen({super.key});

  
  VideoTimelineScreenState createState() => VideoTimelineScreenState();
}

class VideoTimelineScreenState extends ConsumerState<VideoTimelineScreen> {
  int _itemCount = 4;

  final PageController _pageController = PageController();

  final Duration _scrollDuration = const Duration(milliseconds: 250);
  final Curve _scrollCurve = Curves.linear;

  void _onPageChanged(int page) {
    _pageController.animateToPage(
      page,
      duration: _scrollDuration,
      curve: _scrollCurve,
    );
    if (page == _itemCount - 1) {
      _itemCount = _itemCount + 4;
      setState(() {});
    }
  }

  void _onVideoFinished() {
    return;
    // _pageController.nextPage(
    //   duration: _scrollDuration,
    //   curve: _scrollCurve,
    // );
  }

  
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  Future<void> _onRefresh() {
    return Future.delayed(
      const Duration(seconds: 5),
    );
  }

  
  Widget build(BuildContext context) {
    return ref.watch(timelineProvider).when(
          loading: () => const Center(
            child: CircularProgressIndicator(),
          ),
          error: (error, stackTrace) => Center(
            child: Text(
              "Could not load videos: $error",
              style: const TextStyle(
                color: Colors.white,
              ),
            ),
          ),
          data: (videos) => RefreshIndicator(
            onRefresh: _onRefresh,
            displacement: 50,
            edgeOffset: 20,
            color: Theme.of(context).primaryColor,
            child: PageView.builder(
              controller: _pageController,
              scrollDirection: Axis.vertical,
              onPageChanged: _onPageChanged,
              itemCount: videos.length,
              itemBuilder: (context, index) => VideoPost(
                onVideoFinished: _onVideoFinished,
                index: index,
              ),
            ),
          ),
        );
  }
}

 

Stream Provider


Overview

StreamProvider is part of the provider package, which offers an effective way to manage state and make it available to multiple widgets in the widget tree. The StreamProvider specifically is used for exposing a Stream to the widget tree and rebuilding the widgets that are listening to this stream when a new value is emitted.

Stream<int> countStream() async* {
  for (int i = 1; ; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

StreamProvider<int>(
  create: (context) => countStream(),
  initialData: 0,
  child: YourChildWidget(),
)

Widget build(BuildContext context) {
  int currentCount = context.watch<int>();
  return Text('Current Count: $currentCount');
}

 

autoDispose

autoDispose is an enhancement in the provider package for Flutter that's especially beneficial for scenarios where you want to automatically release the resources associated with a provider when it's no longer in use.

With the standard behavior of provider, once a provider is created (e.g., StreamProvider, ChangeNotifierProvider), it remains in memory even if no widgets are currently listening to it. Depending on the context and the type of resource, this can sometimes be undesirable. For instance, a Stream that keeps fetching data from a server would keep doing so even if no widget is interested in the data anymore.

StreamProvider.autoDispose<int>(
  create: (context) => countStream(),
  initialData: 0,
  child: YourChildWidget(),
)

profile
Greatness From Small Beginnings

0개의 댓글