Skip to content

Commit

Permalink
V0.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
escamoteur committed Jun 15, 2023
1 parent c762504 commit bfc60c7
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 356 deletions.
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{
"cmake.configureOnOpen": false
"cmake.configureOnOpen": false,
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
]
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 0.9.0
* First beta release
## 0.0.1

* This is currently just a placeholder for the new version of the get_it_mixin
87 changes: 65 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# watch_it

A simple state management solution powered by get_it.

[:heart: Sponsor](https://github.com/sponsors/escamoteur) <a href="https://www.buymeacoffee.com/escamoteur" target="_blank"><img align="right" src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>
###

>This package is the successor of the get_it_mixin in [here you can find what's new](#whats-different-from-the-get_it_mixin)
This package offers a set of functions to `watch` data registered with `GetIt`. Widgets that watch data will rebuild automatically whenever that data changes.

Supported data types that can be watched are `ChangeNotifier`, `ValueNotifier`, `Stream` and `Future`.
Supported data types that can be watched are `Listenable / ChangeNotifier`, `ValueListenable / ValueNotifier`, `Stream` and `Future`. On top off that there are several other powerful function to use in `StatelessWidgets` that normally would require a `StatefulWidget`

`ChangeNotifier` based example:
```dart
// Create a ChangeNotifier based model
// Create a ChangeNotifier based model
class UserModel extends ChangeNotifier {
get name = _name;
Expand All @@ -32,14 +35,14 @@ Supported data types that can be watched are `ChangeNotifier`, `ValueNotifier`,
}
}
```
Whenever the name property changes the `watchPropertyValue` function will trigger a rebuild and return the lates value of name.
Whenever the name property changes the `watchPropertyValue` function will trigger a rebuild and return the latest value of `name`.
## Accessing GetIt

WatchIt exports the default instance of get_it as a global variable `di` which lets
WatchIt exports the default instance of get_it as a global variable `di` (**d**ependency **i**njection) which lets
you access it from anywhere in your app. To access any get_it registered
object you only have to type `di<MyType>()` instead of `GetIt.I<MyType>()`.
If you prefer to use `GetIt.I` or you have your own globale variable that's fine too as they all
will use the same instace of GetIt.
If you prefer to use `GetIt.I` or you have your own global variable that's fine too as they all
will use the same instance of GetIt.

If you want to use a different instance of get_it you can pass it to
the functions of this library as an optional parameter.
Expand Down Expand Up @@ -73,7 +76,7 @@ There are various `watch` methods, for common types of data sources, including `
|---|---|
| `watch` | observes any Listenable you have access to
| `watchIt` | observes any Listenable registered in get_it
| `watchValue` | observes a ValueListenable property of an object regisertered in get_it
| `watchValue` | observes a ValueListenable property of an object registered in get_it
| `watchPropertyValue` | observes a property of a Listenable object and trigger a rebuild whenever the Listenable notifies a change and the value of the property changes
| `watchStream` | observes a Stream and triggers a rebuild whenever the Stream emits a new value
| `watchFuture` | observes a Future and triggers a rebuild whenever the Future completes
Expand Down Expand Up @@ -124,22 +127,22 @@ To run an action when data changes you can use the `register*Handler` methods:
| `.registerStreamHandler` | Add an event handler for a `Stream` |
| `.registerFutureHandler` | Add an event handler for a `Future` |

All these `register` methods have `select` delegate parameter that can be used to watch a specific field of an object in GetIt. The second param is the action which will be triggered when that field changes:
All these `register` methods have an optional `select` delegate parameter that can be used to watch a specific field of an object in GetIt. The second parameter is the action which will be triggered when that field changes:
```dart
class MyWidget extends StatelessWidget with WatchItMixin {
@override
Widget build(BuildContext context) {
registerHandler(
(Model x) => x.name,
(context, value, cancel) => showNameDialog(context, value));
select: (Model x) => x.name,
handler: (context, value, cancel) => showNameDialog(context, value));
...
}
}
```

In the example above you see that the handler function receives the value that is returned from the select delegate (`(Model x) => x.name`), as well as a `cancel` function that the handler can call to cancel registration at any time.

As with `watch` calls, all `registerHandler` calls are cleaned up when the Widget is destroyed.
As with `watch` calls, all `registerHandler` calls are cleaned up when the Widget is destroyed. If you want to register a handler for a local variable all the functions offer a `target` parameter.

# Rules

Expand All @@ -154,11 +157,11 @@ If you want to know more about the reasons for this rule check out [Lifting the
# The watch functions in detail:

## `watch()`
`watch` observes any Listenables that you pass as parameter and triggers a rebuild whenever it notifies a change.
`watch` observes any `Listenable` that you pass as parameter and triggers a rebuild whenever it notifies a change.
```dart
T watch<T extends Listenable>(T target);
```
That listenable could be passed in as a parameter or be accessed via get_it. Like `final userName = watch(di<UserManager>()).userName;` given that `UserManager` is a Listenable (eg. ChangeNotifier).
That listenable could be passed in as a parameter or be accessed via get_it. Like `final userName = watch(di<UserManager>()).userName;` given that `UserManager` is a `Listenable` (eg. `ChangeNotifier`).
If all of the following functions don't fit your needs you can probably use this one by manually providing the Listenable that should be observed.

## `watchIt`
Expand All @@ -184,12 +187,12 @@ class MyWidget extends StatelessWidget with WatchItMixin {
```

## `watchPropertyValue`
If the listenable parent object that you watch with `watchIt` notifies often because other properties have changed that you don't want to watch the widget would rebuild without any need. In this case you can use `watchPropertyValue`
If the listenable parent object that you watch with `watchIt` notifies often because other properties have changed that you don't want to watch, the widget would rebuild without any need. In this case you can use `watchPropertyValue`
```dart
R watchPropertyValue<T extends Listenable, R>(R Function(T) selectProperty,
{T? target, String? instanceName, GetIt? getIt});
```
It will only trigger a rebuild if the watched listenable notifies a change AND the value of the selected propery has really changed.
It will only trigger a rebuild if the watched listenable notifies a change AND the value of the selected property has really changed.
```dart
final userName = watchPropertyValue<UserManager, String>((user) => user.userName);
```
Expand All @@ -201,6 +204,35 @@ which lets the analyzer infer the type of T and R.

If you have a local Listenable and you want to observe only a single property
you can pass it as [target].

## `watchValue`
```dart
R watchValue<T extends Object, R>(ValueListenable<R> Function(T) selectProperty,
{String? instanceName, GetIt? getIt}) {
```
`watchValue` observes a `ValueListenable` (e.g. a `ValueNotifier`) property of an object registered in get_it.
It triggers a rebuild whenever the `ValueListenable` notifies a change and returns its current
value. It's basically a shortcut for `watchIt<T>().value`
As this is a common scenario it allows us a type safe concise way to do this.
```dart
final userName = watchValue<UserManager, String>((user) => user.userName);
```
is an example of how to use it.
We can use the strength of generics to infer the type of the property and write
it even more expressive like this:

```dart
final userName = watchValue((UserManager user) => user.userName);`
```

`instanceName` is the optional name of the instance if you registered it
with a name in get_it.
`getIt` is the optional instance of get_it to use if you don't want to use the
default one. 99% of the time you won't need this.

# `watchStream and watchFuture`
They follow the same pattern. Please check the API docs for details

# __isReady<T>() and allReady()__
A common use case is to toggle a loading state when side effects are in-progress. To check whether any async registration actions inside `GetIt` have completed you can use `allReady()` and `isReady<T>()`. These methods return the current state of any registered async operations and a rebuild is triggered when they change.
```dart
Expand All @@ -219,11 +251,11 @@ https://pub.dev/packages/get_it

# Pushing a new GetIt Scope

With `pushScope()` you can push a scope when a Widget/State is mounted, and automatically droped when the Widget/State is destroyed. You can pass an optional init or dispose function.
With `pushScope()` you can push a scope when a Widget/State is mounted, and automatically dropped when the Widget/State is destroyed. You can pass an optional init or dispose function.
```dart
void pushScope({void Function(GetIt getIt) init, void Function() dispose});
```
The newly created Scope gets a unique name so that it is ensured the right Scope is droppped even if you push or drop manually other Scopes.
The newly created Scope gets a unique name so that it is ensured the right Scope is dropped even if you push or drop manually other Scopes.

# The WatchingWidgets
Some people don't like mixins so `WatchIt` offers two Widgets that can be used instead.
Expand All @@ -234,13 +266,24 @@ Some people don't like mixins so `WatchIt` offers two Widgets that can be used i
*It's not necessary to understand the following chapter to use `WatchIt` sucessfully.
You might be wondering how on earth is this possible, that you can watch multiple objects at the same time without passing some identifier to any of the `watch` functions. The reality might feel a bit like a hack but the advantages that you get from it justify it abolutely.
When applying the `WatchItMixin` to a Widget you add a handler into the build mechanism of Flutter that makes sure that before the `build` function is called a `_watchItState` object that contains a reference to the `Element` of this widget plus a list of `WatchEntry`s is assigned to a private global variable. Over this global variable the `watch*` functions can access the `Element` to trigger a rebuild.
With each `watch*` function call a new `Watchentry` is added to that list and a counter is incremented.
With each `watch*` function call a new `WatchEntry` is added to that list and a counter is incremented.
When a rebuild is triggered the counter is reset and incremented again with each `watch*` call so that it can access the data it stored during the last build.
Now it should be clear why the `watch*` functions always have to happen in the same order and no conditionals are allowed that would change the order between two builds because then the relation between `watch*` call and its `WatchEntry` would be messed up.
If you think that all sounds very familiar to youm then probably because the exactly same machanism is used by `flutter_hooks` or React Hooks.
If you think that all sounds very familiar to you then probably because the exactly same mechanism is used by `flutter_hooks` or React Hooks.

# Find out more!
For more background on the history of WatchIt you can check out the [README_EXTENDED](README_EXTENDED.md).

For a more complete explanation of the API watch the presentation: [GetIt in action By Thomas Burkhart](https://youtu.be/YJ52kSfSMyM).
To learn more about GetIt, watch the presentation: [GetIt in action By Thomas Burkhart](https://youtu.be/YJ52kSfSMyM), in there the predecessor of this package called ´get_it_mixin´ is described but the video should still be helpful for the GetIt part.

## What's different from the `get_it_mixin`
Two main reasons lead me to replace the `get_it_mixin` package with `watch_it`
* The name `get_it_mixin seemed not to catch with people and only a fraction of my get_it users used is.
* The API naming wasn't as intuitive as I thought when first wrote them.

These are the main differences:
* Widgets now can be `const`!
* a reduced API with more intuitive naming.The old package had too many functions which were only slight variations of each others. You can easily achieve the same functionality with the functions of this package.
* no `get/getX` functions anymore because you can just use the included global `get_it` instance `di<T>`.
* only one mixin for all Widgets. You only need to apply it to the widget and no mixin for `States` as no all `watch*` functions are global functions.

Please let me know if you miss anything
Loading

0 comments on commit bfc60c7

Please sign in to comment.