|
5 | 5 |
|
6 | 6 | Complete dart port of [Preact signals](https://preactjs.com/blog/introducing-signals/) and takes full advantage of [signal boosting](https://preactjs.com/blog/signal-boosting/).
|
7 | 7 |
|
8 |
| -| Package | Pub | |
9 |
| -|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------| |
10 |
| -| `preact_signals` | [](https://pub.dev/packages/preact_signals) | |
11 |
| -| `flutter_preact_signals` | [](https://pub.dev/packages/flutter_preact_signals) | |
| 8 | +| Package | Pub | |
| 9 | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | |
| 10 | +| [`preact_signals`](packages/preact_signals) | [](https://pub.dev/packages/preact_signals) | |
| 11 | +| [`flutter_preact_signals`](packages/flutter_preact_signals) | [](https://pub.dev/packages/flutter_preact_signals) | |
| 12 | +| [`preact_signals_devtools_extension`](packages/preact_signals_devtools_extension) | | |
12 | 13 |
|
13 | 14 | ## Guide / API
|
14 | 15 |
|
@@ -193,223 +194,3 @@ batch(() {
|
193 | 194 | });
|
194 | 195 | // Now the callback completed and we'll trigger the effect.
|
195 | 196 | ```
|
196 |
| - |
197 |
| -## Extensions |
198 |
| - |
199 |
| -### `Future` |
200 |
| - |
201 |
| -Futures can be converted to signals by either a method `signalFromFuture` or as an extension method on a `Future`: |
202 |
| - |
203 |
| -```dart |
204 |
| -import 'package:preact_signals/preact_signals.dart'; |
205 |
| -
|
206 |
| -final future = Future(() => 1); |
207 |
| -final signal = future.toSignal(); // or signalFromFuture(future) |
208 |
| -``` |
209 |
| - |
210 |
| -> This will return a sealed union based on `SignalState` that will return `SignalValue` for success, `SignalError` for errors (and `SignalTimeout` on optional timeout), and `SignalLoading`. |
211 |
| -
|
212 |
| -### `Stream` |
213 |
| - |
214 |
| -Futures can be converted to signals by either a method `signalFromFuture` or as an extension method on a `Future`: |
215 |
| - |
216 |
| -```dart |
217 |
| -import 'package:preact_signals/preact_signals.dart'; |
218 |
| -
|
219 |
| -Stream<int> createStream() async* { |
220 |
| - yield 1; |
221 |
| - yield 2; |
222 |
| - yield 3; |
223 |
| -} |
224 |
| -final stream = createStream(); |
225 |
| -final signal = stream.toSignal(); // or signalFromStream(stream) |
226 |
| -``` |
227 |
| - |
228 |
| -> This will return a sealed union based on `SignalState` that will return `SignalValue` for success, `SignalError` for errors (and `SignalTimeout` on optional timeout), and `SignalLoading`. |
229 |
| -
|
230 |
| -### `ValueListenable` |
231 |
| - |
232 |
| -To create a `ReadonlySignal` from `ValueListenable`: |
233 |
| - |
234 |
| -```dart |
235 |
| -import 'package:flutter_preact_signals/flutter_preact_signals.dart'; |
236 |
| -import 'package:flutter/material.dart'; |
237 |
| -
|
238 |
| -final ValueListenable listenable = ValueNotifier(10); |
239 |
| -final signal = listenable.toSignal(); // or signalFromValueListenable(listenable) |
240 |
| -``` |
241 |
| - |
242 |
| -### `ValueNotifier` |
243 |
| - |
244 |
| -To create a `MutableSignal` from `ValueNotifier`: |
245 |
| - |
246 |
| -```dart |
247 |
| -import 'package:flutter_preact_signals/flutter_preact_signals.dart'; |
248 |
| -import 'package:flutter/material.dart'; |
249 |
| -
|
250 |
| -final notifier = ValueNotifier(10); |
251 |
| -final signal = notifier.toSignal(); // or signalFromValueNotifier(notifier) |
252 |
| -``` |
253 |
| - |
254 |
| - |
255 |
| -### `BuildContext` and Widgets |
256 |
| - |
257 |
| -`StatefulWidget` and `StatelessWidget` widgets can both react to changes on a signal by adding a `watch`` command: |
258 |
| - |
259 |
| -```dart |
260 |
| -import 'package:flutter_preact_signals/flutter_preact_signals.dart'; |
261 |
| -
|
262 |
| -Text( |
263 |
| - '${counter.watch(context)}', |
264 |
| - style: Theme.of(context).textTheme.headlineMedium!, |
265 |
| -) |
266 |
| -``` |
267 |
| - |
268 |
| -or with `watchSignal`: |
269 |
| - |
270 |
| -```dart |
271 |
| -import 'package:flutter_preact_signals/flutter_preact_signals.dart'; |
272 |
| -
|
273 |
| -Text( |
274 |
| - '${watchSignal(context, counter)}', |
275 |
| - style: Theme.of(context).textTheme.headlineMedium!, |
276 |
| -) |
277 |
| -``` |
278 |
| - |
279 |
| -This will mark the widget as dirty and rebuild on next frame. This will all be optimized for batched effects and multiple signals being updated at the same time. |
280 |
| - |
281 |
| -## Examples |
282 |
| - |
283 |
| -### Dart |
284 |
| - |
285 |
| -```dart |
286 |
| -import 'package:preact_signals/preact_signals.dart'; |
287 |
| -
|
288 |
| -// Create signals |
289 |
| -final count = signal(0); |
290 |
| -final multiplier = signal(2); |
291 |
| -
|
292 |
| -// Creating a computed value |
293 |
| -final multipliedCount = computed(() { |
294 |
| - return count.value * multiplier.value; |
295 |
| -}); |
296 |
| -
|
297 |
| -effect(() { |
298 |
| - print('Effect called: Count is ${count.value} and multiplier is ${multiplier.value}'); |
299 |
| -}); |
300 |
| -
|
301 |
| -expect(multipliedCount.value, 0); |
302 |
| -
|
303 |
| -count.value = 1; |
304 |
| -expect(multipliedCount.value, 2); |
305 |
| -
|
306 |
| -multiplier.value = 3; |
307 |
| -expect(multipliedCount.value, 3); |
308 |
| -``` |
309 |
| - |
310 |
| -This should print: |
311 |
| - |
312 |
| -``` |
313 |
| -Effect called: Count is 0 and multiplier is 2 |
314 |
| -Effect called: Count is 1 and multiplier is 2 |
315 |
| -Effect called: Count is 1 and multiplier is 3 |
316 |
| -``` |
317 |
| - |
318 |
| -See [preact_signals/example](packages/preact_signals/example/web/main.dart) for a full example. |
319 |
| - |
320 |
| -### Flutter |
321 |
| - |
322 |
| -Reacting to signal changes can be done with one extension method: `watch(context)`: |
323 |
| - |
324 |
| -```dart |
325 |
| -import 'package:flutter/material.dart'; |
326 |
| -import 'package:flutter_preact_signals/flutter_preact_signals.dart'; |
327 |
| -
|
328 |
| -void main() { |
329 |
| - runApp(const MyApp()); |
330 |
| -} |
331 |
| -
|
332 |
| -final brightness = signal(Brightness.light); |
333 |
| -final counter = signal(0); |
334 |
| -
|
335 |
| -class MyApp extends StatelessWidget { |
336 |
| - const MyApp({super.key}); |
337 |
| -
|
338 |
| - @override |
339 |
| - Widget build(BuildContext context) { |
340 |
| - return MaterialApp( |
341 |
| - title: 'Flutter Demo', |
342 |
| - debugShowCheckedModeBanner: false, |
343 |
| - theme: ThemeData( |
344 |
| - colorScheme: ColorScheme.fromSeed( |
345 |
| - seedColor: Colors.deepPurple, |
346 |
| - brightness: Brightness.light, |
347 |
| - ), |
348 |
| - brightness: Brightness.light, |
349 |
| - useMaterial3: true, |
350 |
| - ), |
351 |
| - darkTheme: ThemeData( |
352 |
| - colorScheme: ColorScheme.fromSeed( |
353 |
| - seedColor: Colors.deepPurple, |
354 |
| - brightness: Brightness.dark, |
355 |
| - ), |
356 |
| - brightness: Brightness.dark, |
357 |
| - useMaterial3: true, |
358 |
| - ), |
359 |
| - themeMode: brightness.watch(context) == Brightness.dark |
360 |
| - ? ThemeMode.dark |
361 |
| - : ThemeMode.light, |
362 |
| - home: const MyHomePage(title: 'Flutter Demo Home Page'), |
363 |
| - ); |
364 |
| - } |
365 |
| -} |
366 |
| -
|
367 |
| -class MyHomePage extends StatelessWidget { |
368 |
| - const MyHomePage({super.key, required this.title}); |
369 |
| -
|
370 |
| - final String title; |
371 |
| -
|
372 |
| - void _incrementCounter() { |
373 |
| - counter.value++; |
374 |
| - } |
375 |
| -
|
376 |
| - @override |
377 |
| - Widget build(BuildContext context) { |
378 |
| - return Scaffold( |
379 |
| - appBar: AppBar( |
380 |
| - title: Text(title), |
381 |
| - actions: [ |
382 |
| - Builder(builder: (context) { |
383 |
| - final isDark = brightness.watch(context) == Brightness.dark; |
384 |
| - return IconButton( |
385 |
| - onPressed: () { |
386 |
| - brightness.value = isDark ? Brightness.light : Brightness.dark; |
387 |
| - }, |
388 |
| - icon: Icon(isDark ? Icons.light_mode : Icons.dark_mode), |
389 |
| - ); |
390 |
| - }), |
391 |
| - ], |
392 |
| - ), |
393 |
| - body: Center( |
394 |
| - child: Column( |
395 |
| - mainAxisAlignment: MainAxisAlignment.center, |
396 |
| - children: <Widget>[ |
397 |
| - const Text( |
398 |
| - 'You have pushed the button this many times:', |
399 |
| - ), |
400 |
| - Text( |
401 |
| - '${counter.watch(context)}', |
402 |
| - style: Theme.of(context).textTheme.headlineMedium!, |
403 |
| - ), |
404 |
| - ], |
405 |
| - ), |
406 |
| - ), |
407 |
| - floatingActionButton: FloatingActionButton( |
408 |
| - onPressed: _incrementCounter, |
409 |
| - tooltip: 'Increment', |
410 |
| - child: const Icon(Icons.add), |
411 |
| - ), |
412 |
| - ); |
413 |
| - } |
414 |
| -} |
415 |
| -``` |
0 commit comments