body_builder 2.0.6
body_builder: ^2.0.6 copied to clipboard
BodyBuilder is a widget that manage the loading of your data
body_builder is a lightweight Flutter package to load UI data from state, cache, and remote sources with a consistent lifecycle.
How It Works #
- Create one or more
BodyProviders. - Pass them to a
BodyBuilder. - Render data with
builderor full state withcustomBuilder.
final myProvider = BodyProvider<String>(
state: myState,
cache: ([params]) => loadFromCache(params?.query),
data: ([params]) => loadFromApi(params?.query),
);
BodyBuilder<String>(
providers: [myProvider],
builder: (data) => Text(data),
);
BodyProvider #
BodyProvider describes how data is loaded for one source.
class BodyProvider<T> extends BodyProviderBase<T> {
BodyProvider({
this.state,
this.cache,
required this.data,
super.name,
});
}
state: optionalStateProvider<T>already holding data, useful to avoid excesive calls ofdata.cache: optional function called beforedata.data: required function that returns your latest value.name: optional name, useful withBodyState.byName.
Both cache and data receive optional DataBuilderParams:
class DataBuilderParams {
final String? query;
final DataState? lastPage;
}
Use query for search and lastPage for pagination-aware requests.
CachedBodyProvider #
Use CachedBodyProvider when working with cache_annotations.
CachedBodyProvider<User>(
state: userState,
cacheEntry: userCacheEntry,
data: ([params]) => fetchUser(params?.query),
);
It reads from cacheEntry first and writes successful data results back to cache.
State Providers #
SimpleStateProvider #
For a single value.
class UserState extends SimpleStateProvider<User> {}
final userState = UserState();
final provider = BodyProvider<User>(
state: userState,
data: ([params]) => api.fetchUser().then(userState.on),
);
PaginatedState #
For paginated lists (query-aware).
class FollowersState extends PaginatedState<String> {}
final followersState = FollowersState();
Future<Iterable<String>> loadFollowers([DataBuilderParams? params]) {
int previousPage = params?.lastPage?.page ?? -1;
int pageToLoad = previousPage + 1; // First page to load will be 0
return Future.delayed(const Duration(seconds: 1), () {
return PaginatedResponse<String>(
items: List.generate(10, (i) => 'Follower ${pageToLoad * 10 + i}'),
page: pageToLoad,
lastPage: 5,
);
}).then((response) => followersState.on(response, query: params?.query));
}
RelatedStateProvider<K, T> #
Map of SimpleStateProvider<T> by key.
class UserByIdStates extends RelatedStateProvider<String, User> {}
final users = UserByIdStates();
BodyProvider<User>(
state: users.byId('123'),
data: ([params]) => api.fetchUser('123').then((u) => users.on('123', u)),
);
RelatedPaginatedStates<K, T> #
Map of PaginatedState<T> by key.
BodyBuilder #
Use GlobalKey<BodyBuilderState> when you want to trigger retries manually.
final key = GlobalKey<BodyBuilderState>();
BodyBuilder<String>(
key: key,
providers: [myProvider],
builder: (data) => Text(data),
);
// Force reload and ignore current state value
key.currentState?.retry(allowState: false);
Constructor Parameters #
| Name | Type | Details |
|---|---|---|
providers* |
Iterable<BodyProviderBase<T>> |
Providers used to resolve data. Must not be empty. |
builder* |
Function? |
Data builder. Mutually exclusive with customBuilder. Up to 9 provider values when combined. |
customBuilder* |
CustomBuilder? |
Receives the merged BodyState and lets you render loading/error/data manually. |
progressBuilder |
Widget? |
Custom progress widget. |
errorBuilder |
ErrorBuilder? |
Custom error widget with retry callback. |
childWrapper |
ChildWrapper? |
Wraps the final child and exposes state/retry/search/scroll. Can be used to implement pull-to-refresh. |
animationDuration |
Duration? |
Transition duration between states (0 disables animation). |
searchController |
TextEditingController? |
Enables query-based reloads on text changes. |
searchFetchDelay |
Duration |
Debounce applied when searchController changes. |
scrollController |
ScrollController? |
Forwarded to childWrapper for custom scroll integrations. |
mergeDataStrategy |
MergeDataStrategy |
allAtOne or oneByOne behavior when multiple providers are used. |
onStateChanged |
void Function(BodyState? previous, BodyState next)? |
Called whenever merged state changes. |
retry and reload #
BodyBuilderState exposes:
retry({bool allowState = false, bool waitNextFrame = true})reload({bool allowState = false, bool allowCache = false, bool allowData = true, bool clearData = false, bool force = false, bool ignoreLoading = false})loadMoreIfNeeded()andhasMore()for paginated flows.
LoadMore #
LoadMore can auto-trigger the next page when visible, or show a button.
LoadMore(_bKey)
builder, customBuilder and BodyState #
When you have a fixed number of providers, builder receives each provider value as positional args (up to 9):
BodyBuilder(
providers: [p1, p2, p3],
builder: (String data1, int data2, User data3) => const SizedBox.shrink(),
);
For dynamic provider counts, use customBuilder:
BodyBuilder(
providers: [p1, p2],
customBuilder: (state) {
final user = state.byType<User>()?.data;
final count = state.byName<int>('counter')?.data;
return Text('user=$user count=$count');
},
);
BodyState<T> contains:
bool isCacheT? databool isLoadingdynamic errorStackTrace? errorStackString? providerName
Global Defaults #
You can configure default progress/error wrappers once. You can also set a default childWrapper, which is the recommended way to add pull-to-refresh behavior:
BodyBuilder.setDefaultConfig(
defaultProgressBuilder: () => const Center(child: CircularProgressIndicator()),
defaultErrorBuilder: (error, stack, retry) => Center(
child: TextButton(onPressed: retry, child: const Text('Retry')),
),
childWrapper: (child, state, onRetry, {searchController, scrollController}) {
if (scrollController != null) {
...
}
return child;
},
debugLogsEnabled: false,
);
More Details #
Additional Information #
If you find a bug or want a feature, please open an issue: https://github.com/jeromecaudoux/body_builder/issues