Skip to content

State Management Integration

Integrating smart_refresher with your state management solution is essential for building production-ready apps. The key is coordinating the RefreshController with your data fetching logic.

The most elegant way to integrate with reactive state is using the .builder constructor. It allows you to separate the refresh logic from your list’s layout.

SmartRefresher.builder(
controller: _controller,
onRefresh: () => ref.refresh(myProvider.future), // Riverpod example
builder: (context, physics) {
return ListView.builder(
physics: physics, // IMPORTANT: Use the provided physics
itemCount: data.length,
itemBuilder: (c, i) => ItemTile(data[i]),
);
},
)

final itemsProvider = FutureProvider<List<Item>>((ref) async {
return await api.fetchItems();
});
// In your Widget
final items = ref.watch(itemsProvider);
return items.when(
data: (list) => SmartRefresher(
controller: _controller,
onRefresh: () async {
await ref.refresh(itemsProvider.future);
_controller.refreshCompleted();
},
child: ListView.builder(...),
),
loading: () => const LoadingSpinner(),
error: (e, stack) => ErrorView(e),
);

When your state manager determines that the last page has been reached, the UI should reflect this to prevent unnecessary API calls.

// In your listener or after fetching data
if (data.isEmpty || data.length < pageSize) {
_controller.loadNoData();
} else {
_controller.loadComplete();
}

Keep your RefreshController in the StatefulWidget’s State class. It manages UI-specific animations and shouldn’t live in your Business Logic/Store.

If you switch filters or search terms, remember to reset the controller so the user can “load more” from the new results:

void onSearch(String query) {
_controller.resetNoData(); // Resets footer from "No more data" to active
_bloc.add(SearchData(query));
}