Skip to content

Commit

Permalink
Add ScopedPottery (#4)
Browse files Browse the repository at this point in the history
* Add ScopedPottery

* Add tests

* Add documents of ScopedPottery

* Add example

* Update tests to avoid unreachable_from_main warnings
  • Loading branch information
kaboc committed Oct 8, 2023
1 parent fb701d3 commit 398d5e8
Show file tree
Hide file tree
Showing 9 changed files with 676 additions and 9 deletions.
64 changes: 63 additions & 1 deletion packages/pottery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ dependencies:

## Usage

Create a pot as pending if the pot is not necessary at the beginning of an app.
This package comes with two widgets:

- Pottery
- ScopedPottery

### Pottery

Creates a pot as pending if it is not necessary yet at the start of an app.

```dart
final counterNotifierPot = Pot.pending<CounterNotifier>();
Expand Down Expand Up @@ -84,6 +91,60 @@ Removing Pottery (e.g. navigating back from the page where Pottery is used) rese
all pots passed to the `pots` argument and replaces their factories to throw an
[PotNotReadyException].

### ScopedPottery

This widget defines new factories for existing pots and binds the objects created by
them to the pots so that those objects are made available to descendants.

An important fact is that the factories of the existing pots are not actually replaced,
therefore calling the `call()` method of a pot still returns the object held in the
global pot. Use `of()` instead to obtain the scoped object. The example below illustrates
the behaviour.

```dart
final fooPot = Pot(() => Foo(111));
```

```dart
class ParentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedPottery(
pots: {
fooPot: () => Foo(222),
},
builder: (context) {
print(fooPot()); // 111
print(fooPot.of(context)); // 222
return ChildWidget();
},
);
}
}
class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print(fooPot()); // 111
print(fooPot.of(context)); // 222
...
}
}
```

See the examples in [main2.dart] and in the document of `ScopedPottery` for usage in
more practical use cases.

Note that there are several important differences between `ScopedPottery` and `Pottery`:

- Objects are created immediately when `ScopedPottery` is created.
- As already mentioned, objects created with `ScopedPottery` are only accessible with
`of()`.
- Objects created with `ScopedPottery` are not automatically discarded when the
`ScopedPottery` is removed from the tree. Use the `disposer` argument to specify a
callback function to clean them up.

## Caveats

### Make sure to specify a factory that returns a correct type.
Expand Down Expand Up @@ -211,3 +272,4 @@ ElevatedButton(
[Pot]: https://pub.dev/packages/pot
[PotNotReadyException]: https://pub.dev/documentation/pot/latest/pot/PotNotReadyException-class.html
[Grab]: https://pub.dev/packages/grab
[main2.dart]: https://github.com/kaboc/pot/blob/main/packages/pottery/example/lib/main2.dart
17 changes: 14 additions & 3 deletions packages/pottery/example/lib/counter_notifier.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import 'package:flutter/material.dart';

class CounterNotifier extends ValueNotifier<int> {
CounterNotifier() : super(0) {
_showMessage('CounterNotifier was created.', Colors.green);
CounterNotifier({this.showMessage = false}) : super(0) {
if (showMessage) {
_showMessage('CounterNotifier was created.', Colors.green);
}
}

final bool showMessage;

@override
void dispose() {
_showMessage('CounterNotifier was discarded.');
if (showMessage) {
_showMessage('CounterNotifier was discarded.');
}
super.dispose();
}

Expand All @@ -32,3 +38,8 @@ void _showMessage(String message, [Color? color]) {
);
});
}

class HyperCounterNotifier extends CounterNotifier {
@override
void increment() => value += 10;
}
2 changes: 1 addition & 1 deletion packages/pottery/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class CounterPage extends StatelessWidget with Grab {
return MaterialPageRoute(
builder: (_) => Pottery(
pots: {
counterNotifierPot: CounterNotifier.new,
counterNotifierPot: () => CounterNotifier(showMessage: true),
},
builder: (_) => const CounterPage._(),
),
Expand Down
96 changes: 96 additions & 0 deletions packages/pottery/example/lib/main2.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// ignore_for_file: avoid_print

import 'package:flutter/material.dart';
import 'package:grab/grab.dart';
import 'package:pottery/pottery.dart';

import 'package:pottery_example/counter_notifier.dart';

final indexPot = Pot.pending<int>();
final counterPot = Pot.pending<CounterNotifier>(
disposer: (notifier) => notifier.dispose(),
);

void main() {
runApp(const App());
}

class App extends StatelessWidget {
const App();

@override
Widget build(BuildContext context) {
return Pottery(
pots: {
counterPot: CounterNotifier.new,
},
builder: (context) {
return MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
for (var i = 0; i < 50; i++)
ScopedPottery(
pots: {
indexPot: () => i,
// A scoped notifier is provided to the descendant
// when the index is an odd number.
if (i.isOdd) counterPot: HyperCounterNotifier.new,
},
disposer: (pots) {
// It is your responsibility to dispose of the
// objects created by ScopedPottery.
(pots[counterPot] as CounterNotifier?)?.dispose();
},
builder: (context) {
// The object created by the factory specified
// above is immediately accessible here using the
// given BuildContext.
// (But this callback is not called after the first
// build because this example has no logic to trigger
// rebuilds of widgets above the _Item widget.)
final index = indexPot.of(context);
final count = counterPot.of(context);
print('$index: $count');

return const _Item();
},
),
],
),
),
),
);
},
);
}
}

class _Item extends StatelessWidget with Grab {
// This widget is const with no parameter.
// It is possible because of ScopedPottery and `of()`.
// The index and other values are obtained from the ScopedPottery above.
const _Item();

@override
Widget build(BuildContext context) {
final index = indexPot.of(context);
final notifier = counterPot.of(context);
final value = notifier.grab(context);

return ListTile(
title: Text('Index $index'),
subtitle: Text(
'$value',
style: const TextStyle(fontSize: 24.0),
textAlign: TextAlign.center,
),
trailing: ElevatedButton(
onPressed: notifier.increment,
child: const Icon(Icons.add),
),
);
}
}
1 change: 1 addition & 0 deletions packages/pottery/lib/pottery.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'src/pottery.dart';
export 'src/scoped_pottery.dart';

export 'package:pot/pot.dart';

0 comments on commit 398d5e8

Please sign in to comment.