build method

  1. @override
Widget build(
  1. BuildContext context
)
override

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:

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
}