build method
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the tree in a given BuildContext and when the dependencies of this widget change (e.g., an InheritedWidget referenced by this widget changes). This method can potentially be called in every frame and should not have any side effects beyond building a widget.
The framework replaces the subtree below this widget with the widget returned by this method, either by updating the existing subtree or by removing the subtree and inflating a new subtree, depending on whether the widget returned by this method can update the root of the existing subtree, as determined by calling Widget.canUpdate.
Typically implementations return a newly created constellation of widgets that are configured with information from this widget's constructor and from the given BuildContext.
The given BuildContext contains information about the location in the tree at which this widget is being built. For example, the context provides the set of inherited widgets for this location in the tree. A given widget might be built with multiple different BuildContext arguments over time if the widget is moved around the tree or if the widget is inserted into the tree in multiple places at once.
The implementation of this method must only depend on:
- the fields of the widget, which themselves must not change over time, and
- any ambient state obtained from the
context
using BuildContext.dependOnInheritedWidgetOfExactType.
If a widget's build method is to depend on anything else, use a StatefulWidget instead.
See also:
- StatelessWidget, which contains the discussion on performance considerations.
Implementation
@override
Widget build(BuildContext context) {
final abstractConfiguration = AbstractConfiguration.of(context);
final resultBuilder = AbstractStatefulBuilder(
initState: (context) {
if (!skipInitialOnInit) {
_onInit(context);
}
},
dispose: () {
try {
_refreshController.dispose();
} catch (e) {
debugPrint(
'There is an error while trying to dispose of _refreshController: $e');
}
},
builder: (context) {
return BlocConsumer<B, S>(
listener: (context, state) {
if (!_showBigLoader(context, state)) {
_refreshController.complete();
}
listener?.call(context, state);
if (state.resultStatus == ResultStatus.loaded) {
onLoaded?.call(context, state);
}
if (state.resultStatus == ResultStatus.loadedCached) {
onLoadedCached?.call(context, state);
}
if (state.resultStatus == ResultStatus.error) {
onError?.call(context, state);
}
},
builder: (context, state) {
final calculatedHeader =
header ?? headerBuilder?.call(context, state);
final calculatedFooter =
footer ?? footerBuilder?.call(context, state);
final child = () {
// Function to build a ListView with optional header and footer
buildMaybeWithHeaderAndFooter(Widget child) {
return ListView(
cacheExtent: cacheExtent,
physics: physics,
controller: controller,
padding: padding ?? EdgeInsets.zero,
children: [
if (headerScrollBehaviour ==
AbstractScrollBehaviour.scrollable &&
calculatedHeader != null)
calculatedHeader,
child,
if (footerScrollBehaviour ==
AbstractScrollBehaviour.scrollable &&
calculatedFooter != null)
calculatedFooter,
],
);
}
// Check if a custom builder is provided
if (builder != null) {
return buildMaybeWithHeaderAndFooter(builder!(context, state));
}
final calculatedShowBigLoader = _showBigLoader(context, state);
final calculatedShowEmptyContainer =
_showEmptyContainer(context, state);
final calculatedShowErrorContainer =
_showErrorContainer(context, state);
final shouldBuildTransitionItem = calculatedShowBigLoader ||
calculatedShowEmptyContainer ||
calculatedShowErrorContainer;
Widget transitionItemBuilder(BuildContext context) {
// Check if we need to show a big loader
if (calculatedShowBigLoader) {
return loaderBuilder?.call(context, state) ??
abstractConfiguration?.loaderBuilder?.call(context) ??
const Loader();
}
// Check if we need to show an empty state
if (calculatedShowEmptyContainer) {
return noDataBuilder?.call(
context, () => _onInit(context), state) ??
abstractConfiguration?.abstractListNoDataBuilder
?.call(context, () => _onInit(context)) ??
AbstractListNoDataContainer(
onInit: () => _onInit(context));
}
// Check if we need to show an error state
if (calculatedShowErrorContainer) {
return errorBuilder?.call(
context, () => _onInit(context), state) ??
abstractConfiguration?.abstractListErrorBuilder
?.call(context, () => _onInit(context)) ??
AbstractLisErrorContainer(onInit: () => _onInit(context));
}
return const SizedBox();
}
final shouldBuildHeader =
headerScrollBehaviour == AbstractScrollBehaviour.scrollable &&
calculatedHeader != null;
final shouldBuildFooter =
footerScrollBehaviour == AbstractScrollBehaviour.scrollable &&
calculatedFooter != null;
final resolvedItemCount = _itemCount(context, state);
final calculatedItemCount =
(shouldBuildTransitionItem ? 1 : resolvedItemCount) +
(shouldBuildHeader ? 1 : 0) +
(shouldBuildFooter ? 1 : 0);
final calculatedIndexOffset = shouldBuildHeader ? 1 : 0;
if (shouldBuildTransitionItem &&
(headerScrollBehaviour == AbstractScrollBehaviour.fixed ||
!shouldBuildHeader)) {
return transitionItemBuilder(context);
}
Widget? calculatedItemBuilder(BuildContext context, int index) {
if (shouldBuildHeader && index == 0) {
return calculatedHeader;
}
if (shouldBuildFooter && index == (calculatedItemCount - 1)) {
return calculatedFooter;
}
if (shouldBuildTransitionItem) {
return transitionItemBuilder(context);
}
return itemBuilder?.call(
context, state, index - calculatedIndexOffset);
}
Widget calculatedSeparatorBuilder(
BuildContext context, int index) {
if (shouldBuildHeader && index == 0) {
return const SizedBox();
}
if (shouldBuildFooter && index == (calculatedItemCount - 1)) {
return const SizedBox();
}
if (shouldBuildTransitionItem) {
return const SizedBox();
}
return separatorBuilder?.call(context, state, index) ??
const SizedBox();
}
// Determine the appropriate list view or grid view based on the columns property
if (columns <= 1) {
return ListView.separated(
cacheExtent: cacheExtent,
padding: padding ?? EdgeInsets.zero,
shrinkWrap: true,
scrollDirection: scrollDirection,
physics: physics,
controller: controller,
itemCount: calculatedItemCount,
itemBuilder: calculatedItemBuilder,
separatorBuilder: calculatedSeparatorBuilder,
);
}
return GridView.builder(
cacheExtent: cacheExtent,
padding: padding ?? EdgeInsets.zero,
shrinkWrap: true,
scrollDirection: scrollDirection,
physics: physics,
controller: controller,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: shouldBuildTransitionItem ? 1 : columns,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio:
shouldBuildTransitionItem ? 0.5 : childAspectRatio,
mainAxisExtent: mainAxisExtent,
),
itemCount: calculatedItemCount,
itemBuilder: calculatedItemBuilder,
);
}();
// Stack to include the SmartRefresher if enabled
final content = Stack(
children: [
() {
if (_useSmartRefresher()) {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: PointerDeviceKind.values.toSet(),
),
child: SmartRefresher(
cacheExtent: cacheExtent,
scrollDirection: scrollDirection,
controller: _refreshController,
enablePullDown: _enableRefresh(context, state),
enablePullUp: _enableLoadMore(context, state),
onRefresh: () => _onRefresh(context),
onLoading: () => _onLoadMore(context),
child: child,
),
);
}
return child; // Return plain child if no SmartRefresher is used
}(),
if (showCachedDataWarningIcon)
Positioned(
top: 0,
right: 0,
child: LoadInfoIcon(
isLoading: !_showBigLoader(context, state) &&
_isLoading(context, state) &&
_hasData(context, state),
isCached: _isCached(context, state),
onReload: (_) => _onInit(context),
),
),
],
);
// Wrap the final content with headers and footers based on configuration
final result = SizedBox(
height: heightBuilder?.call(context, state),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (headerScrollBehaviour == AbstractScrollBehaviour.fixed &&
calculatedHeader != null)
calculatedHeader,
Expanded(
child: content,
),
if (footerScrollBehaviour == AbstractScrollBehaviour.fixed &&
calculatedFooter != null)
calculatedFooter,
],
),
);
// Return additional customization if provided
return additionalBuilder?.call(context, state, result) ?? result;
},
);
},
);
// Determine how to provide the BLoC or Cubit to the widget tree
if (providerValue != null) {
return BlocProvider.value(
value: providerValue!,
child: resultBuilder,
);
}
if (provider != null) {
return BlocProvider<B>(
create: provider!,
child: resultBuilder,
);
}
if (providers.isNotNullOrEmpty) {
return MultiBlocProvider(
providers: providers!,
child: resultBuilder,
);
}
return resultBuilder; // No providers used, return main child directly
}