From 69fb484c8cd007fcdf94ce53f8ef414ba121f00c Mon Sep 17 00:00:00 2001 From: julien-levarlet Date: Fri, 29 May 2026 19:00:26 +0200 Subject: [PATCH 1/2] Fix: asyncListCallback called at initState --- .../widgets/async_searchable_listview.dart | 2 +- lib/searchable_listview.dart | 40 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/example/lib/widgets/async_searchable_listview.dart b/example/lib/widgets/async_searchable_listview.dart index cf65364..a58e92f 100644 --- a/example/lib/widgets/async_searchable_listview.dart +++ b/example/lib/widgets/async_searchable_listview.dart @@ -20,7 +20,7 @@ class AsyncSearchableListview extends StatelessWidget { }, asyncListFilter: (query, list) async { await Future.delayed(const Duration(seconds: 3)); - var result = actors + var result = list .where((element) => element.name.contains(query) || element.lastName.contains(query)) diff --git a/lib/searchable_listview.dart b/lib/searchable_listview.dart index 657c00e..84cc7bb 100644 --- a/lib/searchable_listview.dart +++ b/lib/searchable_listview.dart @@ -519,6 +519,7 @@ class _SearchableListState extends State> { CancelableOperation?>? _activeOperation; late Debouncer? _debouncer; List expansionTileControllers = []; + bool isAsyncCallBackRunning = true; @override void initState() { @@ -541,7 +542,7 @@ class _SearchableListState extends State> { if (widget.asyncListCallback != null) { // Load the initial list for the async constructor WidgetsBinding.instance.addPostFrameCallback((_) async { - _asyncFilter(""); + await _asyncInitialList(); if (mounted) setState(() {}); }); _debouncer = Debouncer(milliseconds: widget.asyncDebounceTime); @@ -612,7 +613,7 @@ class _SearchableListState extends State> { if (asyncError) { return widget.errorWidget ?? const DefaultErrorWidget(); } - if (_activeOperation != null) { + if (_activeOperation != null || isAsyncCallBackRunning) { return widget.loadingWidget ?? const DefaultLoadingWidget(); } return renderSearchableListView(); @@ -799,8 +800,9 @@ class _SearchableListState extends State> { } else if (widget.asyncListCallback != null) { // Debouncer always has a value in this case _debouncer!.run(() async { + if (mounted) setState(() {}); _activeOperation?.cancel(); - _asyncFilter(value); + await _asyncFilter(value); if (mounted) setState(() {}); }); } else { @@ -862,6 +864,11 @@ class _SearchableListState extends State> { } Future _asyncFilter(String value) async { + if (isAsyncCallBackRunning) { + // Wait for the initial list before filtering + return; + } + final operation = CancelableOperation.fromFuture( widget.asyncListFilter!( value, @@ -879,11 +886,34 @@ class _SearchableListState extends State> { asyncError = true; } } finally { - // Make sure to not interrupt an other operation + // Make sure to not interrupt another operation if (_activeOperation == operation) { _activeOperation = null; } } - setState(() {}); + } + + Future _asyncInitialList() async { + isAsyncCallBackRunning = true; + // First get the initial list + try { + final initialData = await widget.asyncListCallback!(); + if (initialData != null) { + asyncListResult = initialData; + } + } catch (e) { + setState(() { + asyncError = true; + }); + return; + } + isAsyncCallBackRunning = false; + // Then check if a query was typed during the initial list loading to filter if needed + if ((widget.searchTextController?.text ?? "") != "") { + await _asyncFilter(widget.searchTextController!.text); + } else { + filtredAsyncListResult.clear(); + filtredAsyncListResult.addAll(asyncListResult); + } } } From 2e8c4fb5ad43a44066dd27a328202cdb3b2a6ec0 Mon Sep 17 00:00:00 2001 From: julien-levarlet Date: Fri, 29 May 2026 21:14:21 +0200 Subject: [PATCH 2/2] Fix review comments --- lib/searchable_listview.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/searchable_listview.dart b/lib/searchable_listview.dart index 84cc7bb..a600d38 100644 --- a/lib/searchable_listview.dart +++ b/lib/searchable_listview.dart @@ -900,11 +900,11 @@ class _SearchableListState extends State> { final initialData = await widget.asyncListCallback!(); if (initialData != null) { asyncListResult = initialData; + } else { + asyncError = true; } } catch (e) { - setState(() { - asyncError = true; - }); + asyncError = true; return; } isAsyncCallBackRunning = false;