Hooks (flutter_hooks)
Si votre projet utilise flutter_hooks,
qora_hooks fournit une API basée sur les hooks avec des primitives composables pour les écrans complexes.
Installation
# Il n'est pas nécessaire d'ajouter `qora` : il est inclus transitivement.
dependencies:
flutter_hooks: ^0.21.0
qora_hooks: ^ 1.3.1
useQuery
Récupère les données et reconstruit le widget à chaque changement d'état.
Équivalent à QoraBuilder sans callback builder imbriqué.
class UserScreen extends HookWidget {
final String userId;
const UserScreen({super.key, required this.userId});
@override
Widget build(BuildContext context) {
final state = useQuery<User>(
key: ['users', userId],
fetcher: () => Api.getUser(userId),
options: const QoraOptions(staleTime: Duration(minutes: 5)),
);
return switch (state) {
Initial() => const SizedBox.shrink(),
Loading(:final previousData) => previousData != null
? UserCard(previousData)
: const CircularProgressIndicator(),
Success(:final data) => UserCard(data),
Failure(:final error) => ErrorView(error),
};
}
}
Paramètres de useQuery
| Paramètre | Type | Description |
|---|---|---|
key | List<Object?> | Identifiant de requête. Un changement déclenche un refetch. |
fetcher | Future<T> Function() | Fonction asynchrone retournant les données. |
options | QoraOptions? | Configuration de cache et de retry par requête. |
Valeur de retour de useQuery
Retourne un QoraState<T> : classe scellée à quatre variantes :
Initial, Loading, Success, Failure.
useMutation
Déclenche une mutation et suit son état.
class EditProfileScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final mutation = useMutation<User, UpdateUserInput>(
mutator: (input) => Api.updateUser(input),
options: MutationOptions(
invalidates: [['users', currentUserId]],
),
);
return ElevatedButton(
onPressed: mutation.isPending
? null
: () => mutation.mutate(UpdateUserInput(name: 'Alice')),
child: mutation.isPending
? const CircularProgressIndicator()
: const Text('Sauvegarder'),
);
}
}
Paramètres de useMutation
| Paramètre | Type | Description |
|---|---|---|
mutator | Future<TData> Function(TVariables) | Fonction asynchrone exécutant la mutation. |
options | MutationOptions? | Callbacks de cycle de vie et configuration de retry. |
Valeur de retour de useMutation
Retourne un MutationHandle<TData, TVariables> :
| Propriété | Type | Description |
|---|---|---|
mutate | void Function(TVariables) | Déclenche sans attendre ; les erreurs vont dans l'état. |
mutateAsync | Future<TData?> Function(TVariables) | Retourne le résultat ou null en cas d'échec. |
reset | void Function() | Réinitialise l'état à MutationIdle. |
isIdle | bool | true quand aucune mutation n'a encore été exécutée. |
isPending | bool | true pendant l'exécution de la mutation. |
isSuccess | bool | true après une complétion réussie. |
isError | bool | true après un échec. |
data | TData? | Disponible quand isSuccess. |
error | Object? | Disponible quand isError. |
useQueryClient
Retourne le QoraClient le plus proche dans l'arbre de widgets.
final client = useQueryClient();
client.invalidate(['posts']);
Le widget doit être encapsulé dans un QoraScope pour que cela fonctionne.
useIsFetching
Retourne true tant qu'au moins une requête gérée par le QoraClient le plus proche
a une requête réseau en cours. Utile pour un indicateur de chargement global en haut
de l'écran sans le coupler à une requête individuelle.
class AppLoadingBar extends HookWidget {
@override
Widget build(BuildContext context) {
final isFetching = useIsFetching();
return AnimatedOpacity(
opacity: isFetching ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: const LinearProgressIndicator(),
);
}
}
Valeur de retour de useIsFetching
bool : true quand isFetchingCount > 0, false sinon.
Reconstruit le widget uniquement quand la valeur franchit la limite zéro.
useIsMutating
Retourne true tant qu'au moins une mutation gérée par le QoraClient le plus proche
est à l'état MutationPending. Pratique pour désactiver un bouton de soumission global
pendant qu'une opération d'écriture est en cours.
class SubmitButton extends HookWidget {
final VoidCallback onPressed;
const SubmitButton({super.key, required this.onPressed});
@override
Widget build(BuildContext context) {
final isMutating = useIsMutating();
return FilledButton(
onPressed: isMutating ? null : onPressed,
child: isMutating
? const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Sauvegarder'),
);
}
}
Valeur de retour de useIsMutating
bool : true quand activeMutations.length > 0, false sinon.
useInfiniteQuery
Gère les données paginées : accumule les pages et expose une
fonction fetchNextPage.
class PostsScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final query = useInfiniteQuery<PostsPage, String?>(
key: const ['posts'],
fetcher: (cursor) => Api.getPosts(cursor: cursor),
getNextPageParam: (page) => page.nextCursor,
initialPageParam: null,
);
final posts = query.pages.expand((p) => p.posts).toList();
return ListView.builder(
itemCount: posts.length + (query.hasNextPage ? 1 : 0),
itemBuilder: (context, i) {
if (i == posts.length) {
if (!query.isFetchingNextPage) query.fetchNextPage();
return const Center(child: CircularProgressIndicator());
}
return PostTile(posts[i]);
},
);
}
}
Paramètres de useInfiniteQuery
| Paramètre | Type | Description |
|---|---|---|
key | List<Object?> | Identifiant de requête. Un changement déclenche un refetch. |
fetcher | Future<TData> Function(TPageParam) | Appelé avec le paramètre de page pour chaque page. |
getNextPageParam | TPageParam? Function(TData) | Retourne null pour signaler la dernière page. |
initialPageParam | TPageParam | Premier paramètre de page passé à fetcher. |
Valeur de retour de useInfiniteQuery
Retourne un InfiniteQueryHandle<TData, TPageParam> :
| Propriété | Type | Description |
|---|---|---|
pages | List<TData> | Toutes les pages récupérées dans l'ordre. |
isLoading | bool | true pendant le chargement de la première page. |
isFetchingNextPage | bool | true pendant le chargement d'une page suivante. |
hasNextPage | bool | false quand plus aucune page n'est disponible. |
fetchNextPage | Future<void> Function() | Charge la page suivante. Sans effet si occupé. |
error | Object? | Dernière erreur, ou null. |
Composition de hooks
Les hooks se composent naturellement. L'exemple suivant récupère un utilisateur, met à jour son profil et affiche une erreur inline dans un seul widget, sans setState ni StreamBuilder.
class ProfileScreen extends HookWidget {
final String userId;
const ProfileScreen({super.key, required this.userId});
@override
Widget build(BuildContext context) {
final client = useQueryClient();
final userQuery = useQuery<User>(
key: ['users', userId],
fetcher: () => Api.getUser(userId),
);
final updateMutation = useMutation<User, UpdateUserInput>(
mutator: Api.updateUser,
options: MutationOptions(
invalidates: [['users', userId]],
),
);
return switch (userQuery) {
Loading() => const CircularProgressIndicator(),
Failure(:final error) => ErrorView(error),
Success(:final data) => ProfileForm(
user: data,
isSaving: updateMutation.isPending,
saveError: updateMutation.error,
onSave: (input) => updateMutation.mutate(input),
),
_ => const SizedBox.shrink(),
};
}
}
qora_hooks requiert que votre widget étende HookWidget (ou HookConsumerWidget
si vous combinez avec Riverpod). StatelessWidget et StatefulWidget standard
ne sont pas pris en charge : utiliser QoraBuilder et QoraMutationBuilder à la place.InfiniteQueryBuilder
Le widget qui gère les requêtes paginées et expose fetchNextPage, fetchPreviousPage et refetch via une interface de contrôleur stable.
Widgets de statut réseau
NetworkStatusBuilder et NetworkStatusIndicator : widgets réactifs pour afficher l'état hors-ligne dans les applications Flutter.