Skip to content

Commit 9073ab5

Browse files
authored
docs: enhance provider documentation (#273)
1 parent 9e6d75f commit 9073ab5

File tree

1 file changed

+131
-4
lines changed

1 file changed

+131
-4
lines changed

docs/docs/basics/dependency-injection.md

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ sidebar_position: 3
44

55
# Dependency Injection 💉
66

7-
Middleware can also be used to provide dependencies to a `RequestContext` via a `provider`.
7+
Middleware can be used to inject dependencies into a `RequestContext` via a `provider`.
8+
9+
## Provider
810

911
`provider` is a type of middleware that can create and provide an instance of type `T` to the request context. The `create` callback is called lazily and the injected `RequestContext` can be used to perform additional lookups to access values provided upstream.
1012

13+
### Basics
14+
1115
In the following example, we'll use a `provider` to inject a `String` into our request context.
1216

1317
```dart
1418
import 'package:dart_frog/dart_frog.dart';
1519
1620
Handler middleware(Handler handler) {
17-
return handler
18-
.use(requestLogger())
19-
.use(provider<String>((context) => 'Welcome to Dart Frog!'));
21+
return handler.use(provider<String>((context) => 'Welcome to Dart Frog!'));
2022
}
2123
```
2224

@@ -30,3 +32,128 @@ Response onRequest(RequestContext context) {
3032
return Response(body: greeting);
3133
}
3234
```
35+
36+
### Extracting Providers
37+
38+
In the above example, we defined the `provider` inline. This is fine for simple cases, but for more complex providers or providers which you want to reuse, it can be helpful to extract the provider to its own file:
39+
40+
```dart
41+
Middleware greetingProvider() {
42+
return provider<String>((context) => 'Hello World');
43+
}
44+
```
45+
46+
Then, we can import and use the provider in one or more middleware:
47+
48+
```dart
49+
Handler middleware(Handler handler) {
50+
return handler.use(greetingProvider());
51+
}
52+
```
53+
54+
### Providing Asynchronous Values
55+
56+
A `provider` can also be used to inject asynchronous values -- we just need to change the generic type to a `Future`:
57+
58+
```dart
59+
Middleware asyncGreetingProvider() {
60+
return provider<Future<String>>((context) async => 'Hello World');
61+
}
62+
```
63+
64+
We can then use the provider in one or more middleware just as before:
65+
66+
```dart
67+
Handler middleware(Handler handler) {
68+
return handler.use(asyncGreetingProvider());
69+
}
70+
```
71+
72+
Later, we can read the async value from a route handler via `context.read`:
73+
74+
```dart
75+
Future<Response> onRequest(RequestContext context) async {
76+
final value = await context.read<Future<String>>();
77+
return Response(body: value);
78+
}
79+
```
80+
81+
:::note
82+
When accessing a `Future` via `context.read` be sure to specify the `Future` as the generic type and `await` the result.
83+
:::
84+
85+
:::tip
86+
You can create a custom extension if you prefer:
87+
88+
```dart
89+
extension ReadAsync on RequestContext {
90+
Future<T> readAsync<T extends Object>() => read<Future<T>>();
91+
}
92+
```
93+
94+
With the above extension, you can access the provided `Future` like:
95+
96+
```dart
97+
Future<Response> onRequest(RequestContext context) async {
98+
final value = await context.readAsync<String>();
99+
return Response(body: value);
100+
}
101+
```
102+
103+
:::
104+
105+
### Lazy Initialization
106+
107+
By default, `provider` creates the provided value only when it is accessed. For example, given the following middleware:
108+
109+
```dart
110+
import 'package:dart_frog/dart_frog.dart';
111+
112+
Handler middleware(Handler handler) {
113+
return handler.use(
114+
provider<String>((context) {
115+
// This code will never execute if `context.read<String>()` isn't called.
116+
print('create!');
117+
return 'Welcome to Dart Frog!';
118+
}),
119+
);
120+
}
121+
```
122+
123+
If we have a route handler that never invokes `context.read<String>()`, our value will never be created, and `create!` will never be logged:
124+
125+
```dart
126+
import 'package:dart_frog/dart_frog.dart';
127+
128+
Response onRequest(RequestContext context) => Response();
129+
```
130+
131+
### Caching
132+
133+
By default, a provided value will be created when it is accessed. This means that each time you read a value via `context.read`, the associated `create` method will be invoked.
134+
135+
As a result, you may wish to cache a provided value so that it isn't unnecessarily recreated on each read. We can do this quite easily by defining a provide value which we use to reference the provided value once it is created.
136+
137+
```dart
138+
String? _greeting;
139+
140+
Middleware cachedGreetingProvider() {
141+
return provider<String>((context) => _greeting ??= 'Hello World');
142+
}
143+
```
144+
145+
:::note
146+
The cached `_greeting` is private so that it can only be accessed within the context of this provider.
147+
:::
148+
149+
This pattern can also be applied to async providers:
150+
151+
```dart
152+
String? _greeting;
153+
154+
Middleware cachedAsyncGreetingProvider() {
155+
return provider<Future<String>>((context) async => _greeting ??= 'Hello World');
156+
}
157+
```
158+
159+
With the above implementations, the greeting will only be computed once and the cached value will be used for the duration of the application lifecycle.

0 commit comments

Comments
 (0)