Skip to content

Commit bd0fdaf

Browse files
authored
Merge pull request #617 from Flutterando/add-watchbinds
Add Modular Watch and Read method (Like Provider)
2 parents 30a32f3 + a6b85cb commit bd0fdaf

File tree

11 files changed

+306
-17
lines changed

11 files changed

+306
-17
lines changed

doc/docs/flutter_modular/dependency-injection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 3
2+
sidebar_position: 4
33
---
44

55
# Dependency Injection

doc/docs/flutter_modular/module.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 4
2+
sidebar_position: 5
33
---
44

55
# Module

doc/docs/flutter_modular/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 6
2+
sidebar_position: 7
33
---
44

55
# Tests

doc/docs/flutter_modular/triple-integration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 7
2+
sidebar_position: 8
33
---
44

55
# Triple Pattern integration

doc/docs/flutter_modular/watch.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Watch
6+
7+
Modular also has an InheritedWidget instance that can use the `dependsOn` API of [BuildContext].
8+
This way, the developer can recover binds and, if it is a supported reactivity, will watch the modifications
9+
changing the state of the widget in question.
10+
11+
The `watch()` method was added to [BuildContext] through extensions, making access easy.
12+
```dart
13+
class Body extends StatelessWidget {
14+
Widget build(BuildContext context){
15+
final notifier = context.watch<ValueNotifier>();
16+
return Text('${notifier.value}')
17+
}
18+
}
19+
```
20+
21+
Supported reactivities are:
22+
- [Listenable](https://api.flutter.dev/flutter/foundation/Listenable-class.html)
23+
24+
Used in `ChangeNotifier/ValueNotifier` classes and in RxNotifier.
25+
26+
- [Stream](https://api.dart.dev/stable/2.15.0/dart-async/Stream-class.html)
27+
28+
Used in StreamController or BLoC/Cubit
29+
30+
- [Store](https://triple.flutterando.com.br/docs/getting-started/using-flutter-triple)
31+
32+
Used in Triple Pattern in StreamStore and NotifierStore classes.
33+
34+
:::tip TIP
35+
36+
In addition to the **context.watch()** method, the read-only **context.read()** method has been added.
37+
It's the same as using **Modular.get()**, but this addition helps projects that are being migrated
38+
**Provider**.
39+
40+
:::
41+
42+
## With selectors
43+
44+
Sometimes binds are not a supported reactivity, but one of their properties can be.
45+
As in the case of BLoC, where the Stream is available through a `bloc.stream` property;
46+
47+
We can add a selection through an anonymous function indicating which property is a supported reactivity to be watched:
48+
49+
```dart
50+
class Body extends StatelessWidget {
51+
Widget build(BuildContext context){
52+
final bloc = context.watch<CounterBloc>((bloc) => bloc.stream);
53+
return Text('${bloc.state}')
54+
}
55+
}
56+
```
57+
58+
Note that the use of the selector does not change on bind return.
59+
60+
We can also use selectors for Triple objects, which have their own selectors for each of their segments:
61+
See the Triple documentation for more details [by clicking here](https://triple.flutterando.com.br/docs/getting-started/using-flutter-triple#selectors):
62+
63+
```dart
64+
class OnlyErrorWidget extends StatelessWidget {
65+
Widget build(BuildContext context){
66+
// changes with store.setError();
67+
final store = context.watch<MyTripleStore>((store) => store.selectError);
68+
return Text('${store.error}')
69+
}
70+
}
71+
```
72+

doc/docs/flutter_modular/widgets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 5
2+
sidebar_position: 6
33
---
44

55
# Widgets

doc/package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flutter_modular/CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
## [4.3.0] - 2021-12-10
2+
* Added BuildContext extension [context.read()] and [context.watch()];
3+
* The [context.watch()] listen changes of [Listanable], [Stream] and [Store] by Triple;
4+
```dart
5+
class Body extends StatelessWidget {
6+
Widget build(BuildContext context){
7+
final notifier = context.watch<ValueNotifier>();
8+
return Text('${notifier.value}')
9+
}
10+
}
11+
```
12+
* Use `select` in `.watch()` to select the reactive property:
13+
```dart
14+
class Body extends StatelessWidget {
15+
Widget build(BuildContext context){
16+
final bloc = context.watch<CounterBloc>((bloc) => bloc.stream);
17+
return Text('${bloc.state}')
18+
}
19+
}
20+
```
21+
22+
Also, use `Store Selectors` in conjunction with `.watch`:
23+
```dart
24+
class OnlyErrorWidget extends StatelessWidget {
25+
Widget build(BuildContext context){
26+
// changes with store.setError();
27+
final store = context.watch<MyTripleStore>((store) => store.selectError);
28+
return Text('${store.error}')
29+
}
30+
}
31+
```
32+
33+
See more details [here](https://modular.flutterando.com.br/docs/flutter_modular/watch)
34+
135
## [4.2.0] - 2021-10-28
236
* Added cleanInjector() and cleanModular() for restart Modular. [#601](https://github.com/Flutterando/modular/pull/601)
337
* Updated modular_core.

flutter_modular/lib/src/presenter/widgets/modular_app.dart

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ class ModularApp extends StatefulWidget {
2626
/// Prohibits taking any bind of parent modules, forcing the imports of the same in the current module to be accessed. This is the same behavior as the system. Default is false;
2727
bool notAllowedParentBinds = false,
2828
}) : super(key: key) {
29-
(Modular as ModularBase).flags.experimentalNotAllowedParentBinds =
30-
notAllowedParentBinds;
29+
(Modular as ModularBase).flags.experimentalNotAllowedParentBinds = notAllowedParentBinds;
3130
(Modular as ModularBase).flags.isDebug = debugMode;
3231
}
3332

@@ -51,8 +50,7 @@ class ModularAppState extends State<ModularApp> {
5150
@override
5251
void dispose() {
5352
Modular.destroy();
54-
Modular.debugPrintModular(
55-
'-- ${widget.module.runtimeType.toString()} DISPOSED');
53+
Modular.debugPrintModular('-- ${widget.module.runtimeType.toString()} DISPOSED');
5654
cleanGlobals();
5755
super.dispose();
5856
}
@@ -65,6 +63,128 @@ class ModularAppState extends State<ModularApp> {
6563

6664
@override
6765
Widget build(BuildContext context) {
68-
return widget.child;
66+
return _ModularInherited(child: widget.child);
67+
}
68+
}
69+
70+
typedef SelectCallback<T> = Function(T bind);
71+
72+
class _Register<T> {
73+
final T value;
74+
Type get type => T;
75+
final SelectCallback<T>? _select;
76+
77+
_Register(this.value, this._select);
78+
79+
dynamic getSelected() => _select != null ? _select!(value) : value;
80+
81+
@override
82+
bool operator ==(Object object) => identical(this, object) || object is _Register && runtimeType == object.runtimeType && type == object.type;
83+
84+
@override
85+
int get hashCode => value.hashCode ^ type.hashCode;
86+
}
87+
88+
class _ModularInherited extends InheritedWidget {
89+
const _ModularInherited({Key? key, required Widget child}) : super(key: key, child: child);
90+
91+
static T of<T extends Object>(BuildContext context, {bool listen = true, SelectCallback<T>? select}) {
92+
final bind = Modular.get<T>();
93+
if (listen) {
94+
final registre = _Register<T>(bind, select);
95+
final inherited = context.dependOnInheritedWidgetOfExactType<_ModularInherited>(aspect: registre)!;
96+
inherited.updateShouldNotify(inherited);
97+
}
98+
99+
return bind;
100+
}
101+
102+
@override
103+
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
104+
return false;
105+
}
106+
107+
@override
108+
InheritedElement createElement() => _InheritedModularElement(this);
109+
}
110+
111+
class _InheritedModularElement extends InheritedElement {
112+
_InheritedModularElement(InheritedWidget widget) : super(widget);
113+
114+
bool _dirty = false;
115+
116+
Type? current;
117+
118+
@override
119+
void updateDependencies(Element dependent, covariant _Register aspect) {
120+
var registers = getDependencies(dependent) as Set<_Register>?;
121+
122+
registers ??= {};
123+
124+
if (registers.contains(aspect)) {
125+
return;
126+
}
127+
128+
final value = aspect.getSelected();
129+
130+
if (value is Listenable) {
131+
value.addListener(() => _handleUpdate(aspect.type));
132+
} else if (value is Stream) {
133+
value.listen((event) => _handleUpdate(aspect.type));
134+
} else if (value is Store) {
135+
value.observer(
136+
onState: (state) => _handleUpdate(aspect.type),
137+
onError: (error) => _handleUpdate(aspect.type),
138+
onLoading: (isLoading) => _handleUpdate(aspect.type),
139+
);
140+
}
141+
registers.add(aspect);
142+
setDependencies(dependent, registers);
143+
}
144+
145+
@override
146+
Widget build() {
147+
if (_dirty) notifyClients(widget);
148+
return super.build();
149+
}
150+
151+
void _handleUpdate(Type type) {
152+
current = type;
153+
_dirty = true;
154+
markNeedsBuild();
155+
}
156+
157+
@override
158+
void notifyClients(InheritedWidget oldWidget) {
159+
super.notifyClients(oldWidget);
160+
_dirty = false;
161+
current = null;
162+
}
163+
164+
@override
165+
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
166+
var registers = getDependencies(dependent) as Set<_Register>?;
167+
registers ??= {};
168+
169+
for (var register in registers) {
170+
if (register.type == current) {
171+
dependent.didChangeDependencies();
172+
}
173+
}
174+
}
175+
}
176+
177+
extension ModularWatchExtension on BuildContext {
178+
/// Request an instance by [Type] and
179+
/// watch your changes
180+
///
181+
/// SUPPORTED CLASS ([Listenable], [Stream] and [Store] by Triple).
182+
T watch<T extends Object>([SelectCallback<T>? select]) {
183+
return _ModularInherited.of<T>(this, select: select);
184+
}
185+
186+
/// Request an instance by [Type]
187+
T read<T extends Object>() {
188+
return _ModularInherited.of<T>(this, listen: false);
69189
}
70190
}

flutter_modular/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_modular
22
description: Smart project structure with dependency injection and route management
3-
version: 4.2.0
3+
version: 4.3.0
44
homepage: https://github.com/Flutterando/modular
55

66
environment:

0 commit comments

Comments
 (0)