A Five-Layer Mixin Stack Eliminates Boilerplate Pull-to-Refresh in Flutter
Pull-to-refresh is deceptively tedious: page counters, loading guards, footer resets, and empty states multiply across every list screen. A composable mixin stack that bakes in these edge cases means fewer copy-paste bugs and a single place to fix pagination logic when an API changes.
Pull-to-refresh and pagination logic repeats across nearly every screen in a Flutter app. This pattern collapses that repetition into five layers of Dart mixins and widgets, starting from a bare interface (NRefreshable) and building up to ready-to-use Sliver-based components. List pages get NCustomScrollView with built-in page tracking, load-more, and empty-state handling; detail pages get NCustomScrollViewForModel with skeleton-screen support. External controllers expose state manipulation without touching the widget tree.
The design separates concerns vertically: interface contracts define what data a page holds, mixin implementations handle the EasyRefresh controller and request lifecycle, State mixins wire in setState and disposal, and final widget classes provide the CustomScrollView shell. A developer only supplies an onRequest callback and an itemBuilder.
SoaringHeart refined the approach across multiple production apps and two major rewrites. The result is a set of components that auto-trigger the first refresh, guard against concurrent requests, and reset footer state on refresh — details that otherwise accumulate bugs when copied between screens.
Vertical layering of mixins — interface, logic, State, widget — mirrors the OSI model and works well for Flutter because it keeps each concern testable and replaceable independently.
Baking the first-refresh trigger into initState via addPostFrameCallback avoids the common mistake of firing network requests before the widget is mounted, a subtle source of exceptions.
Exposing a controller with attach/detach is a pragmatic alternative to passing GlobalKeys around; it decouples imperative actions from the widget tree without introducing a full state-management library.
The `firstPageItems` preset mechanism solves a real-world UX pattern where a dialog or bottom sheet needs to show cached data immediately, then refresh — a detail most generic refresh wrappers ignore.