Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add documentation #8

Merged
merged 82 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
eb8ed1f
Add documentation
manuel-plavsic Jan 26, 2025
e565dfa
Rename file
manuel-plavsic Jan 26, 2025
ae0b741
Rename alternatives to miscellaneous
manuel-plavsic Jan 26, 2025
937dbf2
Improve docs
manuel-plavsic Jan 26, 2025
22780df
remove file prefixes
manuel-plavsic Jan 26, 2025
38222a0
Make small adjustments
manuel-plavsic Jan 26, 2025
9825b21
Make small correction
manuel-plavsic Jan 26, 2025
1009940
docs: updates
nank1ro Jan 27, 2025
2d13117
docs: add basic example
nank1ro Jan 27, 2025
ea7554e
docs: update sentence
nank1ro Jan 27, 2025
780c9ed
Add Installing page
manuel-plavsic Jan 27, 2025
af99e54
Reframe sentence
manuel-plavsic Jan 27, 2025
dd57382
Remove service_injection example
manuel-plavsic Jan 27, 2025
bb1a4aa
Prepare all examples
manuel-plavsic Jan 27, 2025
d0683b2
Fix typo
manuel-plavsic Jan 27, 2025
b4cfd62
Merge branch 'main' into feat/documentation
nank1ro Jan 27, 2025
2be31b9
Merge branch 'main' into feat/documentation
nank1ro Jan 27, 2025
917a6c7
docs: auto route
nank1ro Jan 27, 2025
4d19ab9
docs: update
nank1ro Jan 27, 2025
3deeb9e
docs: add file structure to bloc
nank1ro Jan 27, 2025
497a778
docs: use link card for source code
nank1ro Jan 27, 2025
cf00e9a
docs: update installing
nank1ro Jan 27, 2025
f3e2eda
docs: add empty line
nank1ro Jan 27, 2025
dd03b50
docs: solidart
nank1ro Jan 27, 2025
d7afcf3
Mention that global state solutions usually use extra objects to mana…
manuel-plavsic Jan 27, 2025
18fe128
Make small corrections
manuel-plavsic Jan 27, 2025
0aca5b0
Make small improvements
manuel-plavsic Jan 27, 2025
b588122
Refine bloc example
manuel-plavsic Jan 28, 2025
056fd32
docs: add example deps
nank1ro Jan 28, 2025
56d8d9a
docs: add dependency
nank1ro Jan 28, 2025
1afbffe
docs: authors
nank1ro Jan 28, 2025
15486af
docs: update bio
nank1ro Jan 28, 2025
a5205d1
docs: author card add social links
nank1ro Jan 28, 2025
7caac1d
docs: update
nank1ro Jan 28, 2025
790aa24
docs: graphical representation of provider scope
nank1ro Jan 28, 2025
aa0a563
docs: update provider scope graph
nank1ro Jan 28, 2025
195fbe9
docs: add provider scope exception
nank1ro Jan 28, 2025
95286e5
docs: theme image
nank1ro Jan 28, 2025
8057b0d
Improve authors section
manuel-plavsic Jan 28, 2025
50f5df6
Improve my own introduction
manuel-plavsic Jan 28, 2025
df7f25b
Improve authors
manuel-plavsic Jan 28, 2025
ab170c4
Small correction
manuel-plavsic Jan 28, 2025
1fe11be
Improve main page title and description
manuel-plavsic Jan 28, 2025
80492d8
Move author section to the end but also add link in index
manuel-plavsic Jan 28, 2025
78ab6bd
docs: add provider scope portal images
nank1ro Jan 28, 2025
95b43ca
docs: update authors
nank1ro Jan 28, 2025
4afd23e
docs: fix typo
nank1ro Jan 28, 2025
a0f049f
docs: add how providers are found graph
nank1ro Jan 28, 2025
958979e
docs: add note about big o notation
nank1ro Jan 28, 2025
af5c2e3
Make changes
manuel-plavsic Jan 28, 2025
f2c6692
Merge branch 'main' into feat/documentation
nank1ro Jan 28, 2025
7369695
Rename to Provider Retrieval Process
manuel-plavsic Jan 28, 2025
dfd6f7b
Add warnings
manuel-plavsic Jan 28, 2025
4b3daf8
Add comma
manuel-plavsic Jan 28, 2025
7bcf2e6
Fix typo
manuel-plavsic Jan 28, 2025
c246e41
Use notes and tips in testing and make other small improvements
manuel-plavsic Jan 28, 2025
9e64e67
Add example heading inside modals.mdx
manuel-plavsic Jan 28, 2025
5cacd72
Add two example headings to reactivity.md
manuel-plavsic Jan 28, 2025
e86f493
Reframe sentence
manuel-plavsic Jan 28, 2025
cca9c91
Rename heading in index
manuel-plavsic Jan 28, 2025
d859d6f
Clarify comment in reactivity.md
manuel-plavsic Jan 28, 2025
90c3c29
Do not capitalize "providers"
manuel-plavsic Jan 28, 2025
3f7108a
Remove unnecessary parts and make some fixes.
manuel-plavsic Jan 28, 2025
eb703d4
Make improvements to scoped-di.mdx
manuel-plavsic Jan 28, 2025
ec7a97d
Fix typos and reframe some sentences.
manuel-plavsic Jan 29, 2025
e79d1d8
Make small adjustment
manuel-plavsic Jan 29, 2025
0725865
Fix headings
manuel-plavsic Jan 29, 2025
a2f3ef9
Fix other headings
manuel-plavsic Jan 29, 2025
78996c3
Reframe text and change its order
manuel-plavsic Jan 29, 2025
17bc916
Simplify sentence in authors.mdx
manuel-plavsic Jan 29, 2025
986a923
docs: fix import
nank1ro Jan 29, 2025
17c6959
docs: fix typos
nank1ro Jan 29, 2025
aafce59
docs: fix variablee not useed
nank1ro Jan 29, 2025
968173c
Merge branch 'main' into feat/documentation
nank1ro Jan 29, 2025
416beaf
docs: preferences example
nank1ro Jan 29, 2025
ae06873
docs: preferences
nank1ro Jan 29, 2025
ddcff31
docs: immutability
nank1ro Jan 29, 2025
70c25cf
docs: remove prefixes from code title
nank1ro Jan 29, 2025
2d82a9c
docs: update var name in immutability
nank1ro Jan 29, 2025
4cc17de
Make improvements
manuel-plavsic Jan 29, 2025
d604538
fix typo
manuel-plavsic Jan 29, 2025
14894aa
Change from examples to single example in preferences.mdx
manuel-plavsic Jan 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ export default defineConfig({
},
sidebar: [
{
label: 'Guides',
autogenerate: { directory: 'guides' },
label: 'Introduction',
autogenerate: { directory: 'introduction' },
},
{
label: 'Concepts',
autogenerate: { directory: 'concepts' },
},
{
label: 'Alternatives',
autogenerate: { directory: 'alternatives' },
},
],
}),
Expand Down
43 changes: 43 additions & 0 deletions docs/src/content/docs/alternatives/1-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Comparison
description: Challenges and limitations found in previous approaches
---

Disco was developed to overcome the challenges and limitations of the Flutter ecosystem in the context of dependency injection. Let's understand them together.

### Provider package

Provider, a well known package among the Flutter community, works by providing a value of some type into the widget tree (the descendants of the relative `ProviderScope` where the value was provided), and injecting it into the tree by solely specifying its type, e.g. `context.get<SomeClass>()`. This approach has had some drawbacks, mainly:

- Shadowing: providers with the same type are shadowed by the nearest one.
- Solutions include using a wrapper type or specifying an ID.
- Wrapper types are a good approach, but they introduce a lot of verbosity and can hide the intent of the providers.
- The IDs are usually strings, which are very error prone. This is especially noticeable when refactoring.
- Not compile-time safe: when injecting a type, deducing if a provider even exists for that type implies inspecting the codebase. Similarly, when a provider is removed but not its type — which means no wrapper type was used — this does not result in a static error; thus, the IDE cannot detect invalid provider injections.
- Even worse than invalid provider injections are injections of the wrong provider with the same type (i.e. after the removal of some provider, its unaltered provider injections result in another provider (with the same type), provided higher in the widget tree, being found and injected).
- Reported errors have information in the stack trace that is only about the filepath and line of the injection and the type that is not found. There is no concept of unique provider instance used as ID. So this might result in some debugging if no wrapper type or additional ID was used.

### Global State Management Approaches

Due to the limitations mentioned earlier, the Flutter ecosystem saw the emergence of multiple global state management packages. These packages address issues like compile-time safety and shadowing, while also separating business logic from UI. However, they introduce new challenges:

- **Circular dependencies**
- **Local-state-like logic** that doesn’t behave exactly like real local state
- This complicates logic, especially for beginners
- Sometimes it feels like you’re fighting against the framework
- **Code generation** in some packages
- It should not be necessary
- Creates a high learning curve for new developers

## Inspirations and Key Features

Disco is inspired by the approaches mentioned above, particularly:

- **Scoping from Provider**
- It fosters synergy with the widget tree.

- **Safety from Riverpod**
- Providers are injected via their instance, acting as an identifier, rather than by type.

- **Injecting observables/signals directly**
- Allows for injecting the observables/signals themselves, enabling loose coupling with third-party state management solutions.
71 changes: 71 additions & 0 deletions docs/src/content/docs/concepts/1-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: Providers
description: How to create and use providers
---

A provider is a tool that helps manage and inject dependencies in an application, making it easier to share data or services across different parts of the app.

**NB:** When we use the term "provider", it can refer to either the `Provider` class of this library or the value it contains, depending on the context.

## Providers

Declare a new provider either as a global `final` variable or a `final` static field.

**NB:** while the providers are declared globally, they **do not function globally**. They are just used as IDs when registered in a scope.

Example with a global `final` variable.

```dart
/// global scope
final numberProvider = Provider((context) => 5);
```

If there is only a provider per class, you can also create a `final` static field. This comes down to personal preference.

```dart
class MyDatabase {
static provider = Provider((context) => MyDatabase());
}
```

### Injection of other providers with context

Providers can leverage the context to inject other providers. The context will be relative to the scope in which they are provided.

```dart
final doubleNumberProvider = Provider.withArgument((context) {
final number = numberProvider.of(context);
return number * 2;
});
```

## Providers with argument

Providers need to be provided before they can be injected in the widget tree. Sometimes, they need an initial argument so that they can be instantiated correctly. This is possible with `Provider.withArgument`.

```dart
final numberPlusArgProvider = Provider.withArgument((context, int arg) {
return 5 + arg;
});
```

An example where this might make more sense would be an application with multi-account support, where the database is loaded per user, and the filepath of the database contains the user ID:

```dart
class MyDatabase {
static provider = Provider((context, String userId) => MyDatabase.fromId(id));
}
```

This `MyDatabase.provider` has to be provided in a subtree (of the widget tree) belonging to the currently logged user.

### Injection of other providers with context

Providers can both take an argument and rely on context.

```dart
final doubleNumberPlusArgProvider = Provider.withArgument((context, int arg) {
final number = numberProvider.of(context);
return number * 2 + arg;
});
```
108 changes: 108 additions & 0 deletions docs/src/content/docs/concepts/2-di-and-scopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: DI and Scopes
description: How to provide values and how to inject them
---

Providers have to be provided before they can be injected.

Let us recall the two providers of the previous page for this section.

```dart
// NB: we renamed the `context` to `_` because it is unused.
final numberProvider = Provider((_) => 5);

// NB: in this provider the `context` is used
final doubleNumberPlusArgProvider = Provider.withArgument((context, int arg) {
final number = numberProvider.of(context);
return number * 2 + arg;
});
```

### How to provide

"Providing a provider" is a bit of a play on words. In this context, providing means that the provider must be specified within a `ProviderScope` before it can be injected.

In case the provider does not take an argument, we provide it the following way:

```dart
ProviderScope(
providers: [numberProvider]
child: // ...
)
```

In case the provider takes an argument we need to specify it when providing it.

```dart
ProviderScope(
providers: [doubleNumberPlusArgProvider(10)]
child: // ...
)
```

### How to inject

Injection is the act of retrieving a dependency. It is done with the methods `of(context)` and `maybeOf(context)`, the latter one being safer because if the provider is not found in any scopes it returns null instead of throwing. If you are unsure about which one to use, we recommend you use `of(context)` (and maybe set up an error monitoring solution to detect invalid injection).

We inject the two providers above by using the `numberProvider.of(context)` and `doubleNumberPlusArgProvider.of(context)`.

### Full example

Try and guess what the displayed text will be before reading the solution.

```dart
runApp(
MaterialApp(
home: Scaffold(
body: ProviderScope(
providers: [numberProvider],
child: ProviderScope(
providers: [doubleNumberPlusArgProvider(10)],
child: Builder(
builder: (context) {
final number = numberProvider.of(context);
final doubleNumberPlusArg = doubleNumberPlusArgProvider.of(context);
return Text('$number $doubleNumberPlusArg');
},
),
),
),
),
),
);
```

The solution is "5 20".

## Scoping correctly

Some providers might have a dependency on other providers or an an argument. It is important that the following considerations are made.

### Context

Note that the scope containing `doubleNumberPlusArgProvider` needs to be a descendant of the one containing `numberProvider`. This is because `doubleNumberPlusArgProvider` uses the context to find the value of `numberProvider`. The following will thus **not** work:

```dart
// bad example
ProviderScope(
providers: [
numberProvider,
doubleNumberPlusArgProvider(10),
],
child: // ...
)
```

Placing the ProviderScope containing `doubleNumberPlusArgProvider` above the one containing `numberProvider` would also not work.

### Argument

Let's recall the example from the previous page.

```dart
class MyDatabase {
static provider = Provider((context, String userId) => MyDatabase.fromId(id));
}
```

This `MyDatabase.provider` should be provided in a subtree (of the widget tree) belonging to the currently logged user. If you provide it too high up in the widget tree (before the account switching logic) it will not be possible to change database, because the `ProviderScope` where `MyDatabase.provider` is provided is never disposed. In practice, this particular error is unlikely due to the `userId` being first available in the account switching logic. However, this kind of scenario needs to be considered.
52 changes: 52 additions & 0 deletions docs/src/content/docs/concepts/3-modals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Modals
description: How to access providers inside modals
---

A modal spawns a new widget tree, making injecting providers not possible out of the box.

This library offers `ProviderScopePortal`, which gives access to all the providers that were created in the main widget tree.

Note that you have to pass it the context of the main tree for it to work. Also, a `context` that is a descendant of `ProviderScopePortal` needs to be used. This is why in the following example we created a `Builder` and used its argument `innerContext` to inject the provider.

```dart
final numberContainerProvider = Provider((_) => 1);

Future<void> showNumberDialog({required BuildContext context}) {
return showDialog(
context: context,
builder: (dialogContext) {
return ProviderScopePortal(
mainContext: context,
child: Builder(
builder: (innerContext) {
final numberContainer =
numberContainerProvider.of(innerContext);
return Text('$numberContainer');
},
),
);
},
);
}

runApp(
MaterialApp(
home: Scaffold(
body: ProviderScope(
providers: [numberContainerProvider],
child: Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
showNumberDialog(context: context);
},
child: const Text('show dialog'),
);
},
),
),
),
),
);
```
64 changes: 64 additions & 0 deletions docs/src/content/docs/concepts/4-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Testing
description: How to use overrides for testing
---

Testing is done with overrides. You will need to place a `ProviderScopeOverride` (preferability as the root widget) and then specify the `overrides`, which is a list containing the providers followed by `.overrideWithValue(T value)`.

Note that you can only use one `ProviderScopeOverride` per test.

The text displayed in the following example will be "100".

```dart
// NB: usually we rename the `context` to `_` when it is unused.
final numberProvider = Provider<int>((context) => 0);

runApp(
ProviderScopeOverride(
overrides: [
numberProvider.overrideWithValue(100),
],
child: MaterialApp(
home: ProviderScope(
providers: [
numberProvider,
],
child: Builder(
builder: (context) {
final number = numberProvider.of(context);
return Text(number.toString());
},
),
),
),
),
);
```

Testing is possible also with providers that take an argument, and it is done same exact way. The text displayed in the following example will be "16".

```dart
// NB: usually we rename the `context` to `_` when it is unused.
final numberProvider = Provider.withArgument((context, int arg) => arg);

runApp(
ProviderScopeOverride(
overrides: [
numberProvider.overrideWithValue(8 * 2),
],
child: MaterialApp(
home: ProviderScope(
providers: [
numberProvider(1),
],
child: Builder(
builder: (context) {
final number = numberProvider.of(context);
return Text(number.toString());
},
),
),
),
),
);
```
19 changes: 19 additions & 0 deletions docs/src/content/docs/concepts/5-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Configuration
description: How to set up application-wide configuration
---

Disco is as little opinionated as possible. If you want to change the default configuration, set the right preferences with `DiscoConfig` before calling `runApp`. For example:

```dart
DiscoConfig.lazy = false;
runApp(
// ...
);
```

### All options

| Option | Default | Description |
| ---------------| ------- | ------------|
| `lazy` | true | The values of the providers provided in a `ProviderScope` are created lazily. |
Loading
Loading