Hooks are a way to reuse code more efficiently and are not technically a state-management solution ( docs )
- hooks can only be used in the
buildmethod of a widget - hooks are retrieved by their index, and any change in their order will affect all of the hooks that come after
- hooks aren't rebuilt with hot-reloads as the
HookWidgetholds on to them, and if it finds they haven't been changed ( depending on the index and class ) they won't be rebuilt useTextingEditingControllerhandles the initialization and disposing of the controller as any hook you create within aHookWidgetis registered within that instance using it's index and class so the lifetime of the controller would be automatically handledHookWidgetis aStatelessWidgetso in order to create and maintain a state we use theuseStatehook which returns aValueNotifier
useEffectUseful for side-effects and optionally canceling them.
useEffect is called synchronously on every
build, unlesskeysis specified. In which case useEffect is called again only if any value insidekeysas changed.It takes an
effectcallback and calls it synchronously. Thateffectmay optionally return a function, which will be called when theeffectis called again or if the widget is disposed.By default
effectis called on everybuildcall, unlesskeysis specified. In which case,effectis called once on the first useEffect call and whenever something withinkeyschange
-
useFuturecan be used to create anAsyncSnapshotthat rebuilds a widget whenever the future is resolved -
useMemoizedallows for caching of complex objects. If it has a cached version of that object from before it will return it otherwise it will kick off that future and hold on to it afterwards ( I wanna create something, hold on to it ) -
useAppLifecycleStatecan be used to keep track of the state of the application e.g. resumed, paused, inactive, or detached -
useEffectcan be the equivalent ofinitState+didUpdateWidget+dispose( depending on the implementation ) -
hooks should NOT be used within loops, conditionally, or inside a user interaction lifecycle such as
onPressed( read more )
-
I learned that this guy "Remi Rousselet" is fkin insane
-
three packages,
-
riverpod, Dart only (NO flutter) -
flutter_riverpod, A basic way of using Riverpod with flutter. You mostly use it for state management. -
If you are using hook widgets(
flutter_hooks) that reduce the boilerplate code like dispose, you can usehooks_riverpod
-
-
refis basically your window to the outside world. Providers are usually global functions with no idea about any other variable unless they're global and that's whererefcomes in. -
ProviderScopeshould be used to specify the scope of the providers whether for example for the entire app ( at the root of the application ), or actually for the widgets or part of the tree that needs those providers. -
"it is possible to insert other
ProviderScopeanywhere inside the widget tree to override the behavior of a provider for only a part of the application" ProviderScope class - flutter_riverpod library - Dart API -
ref.watchcan be used to "watch" or listen to changes within a provider -
ref.readobtains the current snapshot of the object specified. ( it reads basically, duh ) -
Consumercan be used to be more efficient in rebuilding the UI, you only rebuild the parts of the UI the actually needs to be rebuilt.Consumer class - flutter_riverpod library - Dart API
"If you pass the pre-built subtree as the child parameter, the Consumer will pass it back to your builder function so that you can incorporate it into your build. Using this pre-built child is entirely optional, but can improve performance significantly in some cases and is therefore a good practice."[StateProvider] vs [StateNotifierProvider] SP: provides you with a getter/setter [state] that can be changed from the outside SNP: provides you with a getter [state] that can be manipulated with prediffined methods " StateProvider is a provider that exposes a way to modify its state. It is a simplification of StateNotifierProvider, designed to avoid having to write a StateNotifier class for very simple use-cases. StateProvider exists primarily to allow the modification of simple variables by the User Interface. The state of a StateProvider is typically one of: an enum, such as a filter type a String, typically the raw content of a text field a boolean, for checkboxes a number, for pagination or age form fields You should not use StateProvider if: your state needs validation logic your state is a complex object (such as a custom class, a list/map, ...) the logic for modifying your state is more advanced than a simple count++. For more advanced cases, consider using StateNotifierProvider instead and create a StateNotifier class. While the initial boilerplate will be a bit larger, having a custom StateNotifier class is critical for the long-term maintainability of your project – as it centralizes the business logic of your state in a single place. " Sources ( official docs are really good ) - https://developermemos.com/posts/different-providers-riverpod - https://riverpod.dev/docs/providers/state_provider/ - https://riverpod.dev/docs/providers/state_notifier_provider/ -
FutureProviderallows you to handle various states ( loading, done, error ) veryconveniently with the
whenmethod -
FutureProvideris used for caching asynchronous computations, and if thisFutureis used in multiples places throughout the code, it'll only be resolved once unless you're e.g. combining other providers states -
all different types of providers at their core conform to another provider
AlwaysAliveProviderBaseso you can work with different types of providers easily or somewhat generically -
under the hood, riverpod checks for equality when providing us with new values so we might wanna override the equality to work as expected
-
.familymodifier can be used to get a unique provider based on external parameters. docs -
.autoDisposemodifier can be used to dispose of a provider when it's no longer being used ( using auto-dispose adds an extra property to the ref objectkeepAliveif you want to programatically decide whether or not you should dispose off the provider ) -
ref.readshould be avoided as much as possible even when the state may never change. Even then,ref.watchshould be used for maintability purposes. ref.read - docs -
ref.refreshcan be used to reset the value of the provider -
.selectcan be used to explicitly tell Riverpod that we want to listen to a certain property that we care aboutUsing StreamProvider over StreamBuilder has numerous benefits: * it allows other providers to listen to the stream using ref.watch. * it ensures that loading and error cases are properly handled, thanks to AsyncValue. * it removes the need for having to differentiate broadcast streams vs normal streams. * it caches the latest value emitted by the stream, ensuring that if a listener is added after an event is emitted, the listener will still have immediate access to the most up-to-date event. * it allows easily mocking the stream during tests by overriding the StreamProvider. -
ProviderObservercan be used to observe changes in aProviderContainersuch as adding, disposing, or updating a provider
-
Equality in dartlang is referential by default meaning it compares references to objects.
When instantiating an object
final potato = Potato();the variablepotatoactually referes or points to an object the lives in the memory. Unless a constant constructer is used ( both in the defintion and declaration ) and instantiated with the same values, each object would refer to or instantiate a new object.void main(){ final mrPotato1 = Potato('Michael Scott', '123'); final mrPotato2 = Potato('Michael Scott', '123'); print(mrPotato1 == mrPotato2); // false // mrPotato1 --refersTo--> Object1 in the memory // mrPotato2 --refersTo--> Object2 in the memory final mrPotato3 = const Potato('Michael Scott', '123'); final mrPotato4 = const Potato('Michael Scott', '123'); print(mrPotato3 == mrPotato4); // true // mrPotato3 --refersTo--> Object3 in the memory // mrPotato4 --refersTo--> Object3 in the memory } @immutable class Potato{ final String name; final String ssn; const Potato(this.name, this.ssn); }
class Potato{ ... /// To fix this, we can override the equality operator to be value equlaity instead @override bool operator ==(covariant Potato other) => identical(this, other) || // by defintion, if they reference the same object then they're equal other is Potato && other.runtimeType == runtimeType && other.name == name && other.ssn == ssn; // or [other.hashCode == hashCode] instead of comparing the attributes indivdually @override int get hashCode => name.hashCode ^ ssn.hashCode; }
Dart Data Class Generator vscode extension
equatable | Dart Package ( no need for explicit overriding )
- The
_testin the filename syntax marks the file as a testing file.
- testing a small piece or a "unit" of code possibly with fake data to mock an api call or the original data source
test("Define what the test will do", (){
// Arrange: Get the resources needed for testing
// Act: Perform operations you need
// Assert: Check if the output generated is the one you expected
});-
if you wish to perform ur test with the real API, ur gonna need to create ur own
HttpClientwhich can be done by extendingHttpOverridesand then usingHttpOverrides.global = _MyHttpOverrides();inside the test -
common instructions can be refactored into the
setUpAllfunction which is executed before any test ( docs )setUpAll(() async { HttpOverrides.global = _MyHttpOverrides(); });
-
related tests can be grouped in a
groupfunction to be run together
- checking to see if a widget (UI) behaves as expected with real or mocked data
testWidget('desc',
(tester) async {
/// if you wish to use async code you have to use `runAsync`
/// as widget testing doesn't support real async code
await tester.runAsync(() async {
//
});
});-
.pumpWidget"Renders the UI from the givenwidget" -
.pumpAndSettle"This essentially waits for all animations to have completed" -
you can interact with the widget through the
testeras it exposes methods such as.tapor.enterTexton a given widget that can be found through e.g. typefind.byType(see more finders) -
.pumpacts like asetState -
for test assert we can use a
matcher(all matchers)
-
testing the complete application flow in real-world conditions
-
The integration test runs in a real device
-
we need to add a dev dependancy to our project
integration_test: sdk: flutter
"Integration tests are used to check the complete app or to check the flow of the application. So here real HTTP requests are made and run in a real async environment. So we don’t need to create our custom HttpClient or use
tester.runAsyncto run asynchronous statements."
-
directory structure
lib/ ... integration_test/ foo_test.dart bar_test.dart test/ # Other unit tests go here -
basic setup would look like
void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; // fullyLive ensures that every frame is shown, suitable for heavy animations and navigation if (binding is LiveTestWidgetsFlutterBinding) { binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; } }
" You should only use
testWidgetsto declare your tests, or errors won’t be reported correctly."
| unit | widget | integration | |
|---|---|---|---|
| speed | fastest | fast | slow |
| dependencies | few | more | most |
| usage freq. | high | low | moderate |