import 'package:fl_query/src/models/query_job.dart'; import 'package:fl_query/src/query.dart'; import 'package:fl_query/src/query_bowl.dart'; import 'package:fl_query/src/utils.dart'; import 'package:flutter/widgets.dart'; class QueryBuilder extends StatefulWidget { final Function(BuildContext context, Query query) builder; final QueryJob job; final Outside externalData; /// Called when the query returns new data, on query /// refetch or query gets expired final QueryListener? onData; /// Called when the query returns error final QueryListener? onError; const QueryBuilder({ required this.job, required this.externalData, required this.builder, this.onData, this.onError, Key? key, }) : super(key: key); @override State> createState() => _QueryBuilderState(); } class _QueryBuilderState extends State> { late QueryBowl queryBowl; late final ValueKey uKey; Query? query; @override void initState() { super.initState(); uKey = ValueKey(uuid.v4()); WidgetsBinding.instance.addPostFrameCallback((_) => init()); } void init([QueryBowl? bowl]) async { bowl ??= QueryBowl.of(context); query = bowl.addQuery( widget.job, externalData: widget.externalData, key: uKey, onData: widget.onData, onError: widget.onError, ); final hasExternalDataChanged = query!.externalData != null && query!.prevUsedExternalData != null && !isShallowEqual(query!.externalData!, query!.prevUsedExternalData!); if (query!.fetched && hasExternalDataChanged) { await query!.refetch(); } else if (!query!.fetched) { await query!.fetch(); } } @override void didUpdateWidget(covariant oldWidget) { final hasOnErrorChanged = oldWidget.onError != widget.onError && oldWidget.onError != null; final hasOnDataChanged = oldWidget.onData != widget.onData && oldWidget.onData != null; // re-init the query-builder when new queryJob is appended if (oldWidget.job.queryKey != widget.job.queryKey) { _queryDispose(); init(); } else if (oldWidget.externalData != null && widget.externalData != null && !isShallowEqual(oldWidget.externalData!, widget.externalData!)) { if (widget.job.refetchOnExternalDataChange ?? queryBowl.refetchOnExternalDataChange) { QueryBowl.of(context).fetchQuery( widget.job, externalData: widget.externalData, onData: widget.onData, onError: widget.onError, key: uKey, ); } else { QueryBowl.of(context) .getQuery(widget.job.queryKey) ?.setExternalData(widget.externalData); } if (hasOnDataChanged) query?.onDataListeners.remove(oldWidget.onData); if (hasOnErrorChanged) query?.onErrorListeners.remove(oldWidget.onError); } else { if (hasOnDataChanged) { query?.onDataListeners.remove(oldWidget.onData); if (widget.onData != null) query?.onDataListeners.add(widget.onData!); } if (hasOnErrorChanged) { query?.onErrorListeners.remove(oldWidget.onError); if (widget.onError != null) query?.onErrorListeners.add(widget.onError!); } } super.didUpdateWidget(oldWidget); } _queryDispose() { query?.unmount(uKey); if (widget.onData != null) query?.onDataListeners.remove(widget.onData); if (widget.onError != null) query?.onErrorListeners.remove(widget.onError); } @override void dispose() { _queryDispose(); super.dispose(); } @override Widget build(BuildContext context) { queryBowl = QueryBowl.of(context); final latestQuery = queryBowl.getQuery(widget.job.queryKey) ?? query; if (latestQuery == null) return Container(); return widget.builder(context, latestQuery); } }