rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.28k stars 955 forks source link

Documentation rework plan #1762

Closed rrousselGit closed 1 year ago

rrousselGit commented 2 years ago

This is a meta issue about what's planned for the new documentation. The following list represents the sidebar of the new docs

The order in which these items are placed on the list is important. They are supposed to be in order of difficulty and how common the topic they cover is.

To anyone reading: If you see anything possibly missing from this list, please make a comment. This list is by no means final and it's open to suggestions

Also, if a topic is on the list, but you think it's important for the article to cover one case, please say so! (cf "testing" with its sub-list)

Introduction
Getting started

- Riverpod for Provider users
  + Recommend Riverpod over Provider
  + Why Riverpod exists
     -> widgets never stop subscribing to InheritedWidgets
     -> InheritedWidgets cannot react to consumer removals
     Both of which are core features for the reactive declarative syntax Riverpod offers, which enables massive improvements over Provider.
- Riverpod for Bloc users

Guides:

- Performing a network request
  + testing
- Passing parameters to your network request
  + explain that this is not a one-time initialization, but rather a branching in state
  + Mention potential props drilling and discourage fighting against it
   + Link advanced scoping article
- Clearing the cache when the user leaves the screen
  + Control how long the cache stays
- Rendering errors and loading states
  + cover when(skipLoadingOnRefresh) & AsyncValue.value/requireValue
- Explain how to perform cleanup logic when the state is destroyed/recreated
- Case study: Cancelling network requests when no longer used
- Case study: Infinite list
- Case study: Implementing a pull-to-refresh
- Case study: Search as we type
- Case study: HTTP polling
- Performing side effects
- Combining providers
- Testing providers
  - reading providers
  - warning: avoid container.read(autoDispose) -> `final sub = container.listen(autoDispose); sub.read()`
  - mocking
  - Avoid sharing mocks/overrides between tests. This includes sharing a ProviderScope creating a bunch of mocks/overrides.
    Tests should be isolated. Rather, create utils that enables the creation of your mocks/overrides without sharing them.
  - testing that a provider changed
- Initializing async operations
  -> Creating a FutureProvider + awaiting it in the main + skipping "when"
- Optimizing performance: `select`
- Case study: Showing a modal/snackbar on error
- Reporting errors to crashlytics
- Offline persistence

Concepts

- About hooks
  + Providers are for shared state. Hooks are for local state
   -> Hooks are great for holding your editing/scroll controllers. Put the form state inside providers after it has been validated+submitted
- About code generation
- About providers
  - The initialization function (called "build" as it is inspired from widgets) should be pure-like
    In there, reads are fine (such as HTTP get). But any form of side-effect is considered anti-pattern.
    So no HTTP post/delete or modifying providers
- Provider life-cycles
- When do providers notify their listeners?
- (existing concepts)

API reference (link)

Tutorials:

- pub.dev client (step by step)

Advanced guides:

- How to de-duplicate network requests
  Cf  ref.exists and this question: https://stackoverflow.com/questions/74248527/how-can-i-bulk-load-data-for-a-family-provider-in-riverpod
- Scoping providers to a page
- Converting an InheritedWidget into a Riverpod's provider
- Optimizing performance: making widgets `const` using scoping
- Passing scoped providers to modals/dialogs/other routes

FAQ
- I have some reusable utility that needs both a Ref and a WidgetRef, what can I do?
  -> refactor your code such that WidgetRef is no-longer needed and Ref is used instead
- I got "A provider cannot modify other providers" exception. How can I fix it?
- Why is there no shared interface between Ref and WidgetRef?
  -> Logic should not depend on a different type of Ref as they have small behavior differences.
      Also, relying on WidgetRef is equivalent to putting your logic in the UI.
      Move it to a provider instead. The logic will be both more robust and maintainable
- Why does modifying a provider inside `State.initState` throws?
- I get an error when using WidgetRef inside State.dispose, why?
  Related to https://github.com/rrousselGit/riverpod/issues/2538
- How to deal with forms?
  -> Don't put form state inside providers until it has been submitted and validated.
   -> Consider using flutter_hooks

Good practices

- DON'T modify providers while widgets are building (including initState).
- DON't modify providers during the initialization of a provider (including ref.invalidate)
- AVOID using `ref.read` inside the initialization function of a provider
- AVOID using `ref.watch`/`ref.listen` outside of the initialization function of a provider
- DO specify `dependencies` if a provider depends on a possibly scoped provider.
- AVOID create global instances of `ProviderContainer`
- AVOId reusing a `ProviderContainer` instance between tests
- DON'T do ref.watch(myProvider([])) (new instance of object that does not override ==)
- DO declare providers as global final variables
- AVOID ref.read on "auto dispose" providers.
- AVOID passing providers as parameter (context https://github.com/rrousselGit/riverpod/discussions/1960#discussioncomment-4284905)

List of official Riverpod talks/articles

- https://youtu.be/C2Zp731g8Es?t=12779
- https://www.youtube.com/watch?v=BJtQ0dfI-RA

Examples: (unchanged)
Third party examples (unchanged)

Associated more specific issues:

TimWhiting commented 2 years ago

Personally I'd like to see Initializing providers inside the main higher in the list, since it is very common, especially with SharedPreferences / Firebase.

rrousselGit commented 2 years ago

I moved it a bit. It should be fine now I think

Edit: I've changed my stance on this. I'll likely promote a different approach instead.

gjhoppy commented 2 years ago

I'd like to see some more details on ref.refresh and ref.invalidate with some use cases. I only found these in the API documents and invalidate solved a problem I had with refreshing a pane containing a watch in a desktop app. However not 100% sure I'm using it correctly.

temcewen commented 1 year ago

Can you clarify something that's really bugging me? I understand why you should never modify another provider from a provider's build method as is specified in "Good practices" point 2. However, does this also apply to the build method of a FutureProvider? For example, say I have a FutureProvider that gets some data for a widget, would it be bad to have this FutureProvider update other providers with that data after it gets it asynchronously (so a certain amount of time after the build method was called). I understand that this doesn't technically cause an error as of now, but I don't want to build my code around this practice and then be unable to update my Riverpod package version because that functionality was patched as a bug. I am trying to do it like this for a complex reason, and I will explain it if anyone wants to know why, but I thought it would be best to just ask about the problem in a general sense first.

rrousselGit commented 1 year ago

However, does this also apply to the build method of a FutureProvider?

Yes

You shouldn't modify providers inside future/StreamProvider's initialization method, even after an await

temcewen commented 1 year ago

@rrousselGit Thanks for the response. I just opened up a discussion thread with my follow up question so that I don't pollute this thread. https://github.com/rrousselGit/riverpod/discussions/2388

dgaedcke commented 1 year ago

Now that StreamProvider<>().stream is being deprecated, I'm having trouble figuring out how to refactor my code. The issue is that I'm building a library without any UI ... instead, I'm using streams like an event-bus. One provider creates a new (mapped or filtered) stream based on ref.watch() another streamProvider.

So I'm unclear how to convert from syntax like: await for (final NcCommandEvent cmdEvt in newBStream) { if (cmdEvt.isErrorEvent) { yield cmdEvt; } } to the new AsyncValue.when() syntax

how do I map-across to a new stream within the dependent provider??

kwill39 commented 1 year ago

@rrousselGit, I created 2 issues related to existing documentation. One of them is simply covering an additional case within the testing docs, but I'm including a code snippet, so I thought it would be better organized within its own issue. If you'd prefer me to put both of those issues in here as comments instead, just let me know!

2520 (The additional testing case one)

2521

leventkantaroglu commented 1 year ago

Hi. Is there any update? 😔

I can't suggest this package to anyone, because I can't show them a valid documentation. Documentation for Riverpod 1.0 was very good. But I should not tell to use one year old version (1.0.3)

image

rrousselGit commented 1 year ago

Docs-v2 has been merge to master.

Docs are up-to-date with many additions and more to come. They should be in a good place right now. So closing this in favour of the other issues tracking docs changes

catalunha commented 1 year ago

Yesterday I read all the documentation. Congratulations. The text is very readable and comprehensive.