build method

  1. @override
Widget build(
  1. BuildContext context,
  2. WidgetRef ref
)

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, WidgetRef ref) {
  final swaps = ref.watch(walletInFlightSwapsProvider);
  final l10n = WalletLocalizations.of(context);
  final textTheme = Theme.of(context).textTheme;
  final colors = WalletColors.of(context);

  return swaps.when(
    // `skipLoadingOnReload` keeps a populated section visible while an
    // invalidate (sync-edge / resume / dismiss) re-pulls — no per-refresh
    // blink. The cold first load renders nothing (nothing to re-attach yet).
    skipLoadingOnReload: true,
    loading: () => const SizedBox.shrink(),
    // A read failure is surfaced honestly (an in-flight swap is money in
    // motion and this is its only wallet-side witness) — never a silent hide
    // — AND recoverable in place. The home has no pull-to-refresh, so an
    // inline retry is the only way back from a transient read fault short of a
    // full app resume (UX+reliability MED). liveRegion announces the
    // loading→error transition to a screen reader in place (a11y).
    error: (_, _) {
      // While the tapped re-pull is in flight, `skipLoadingOnReload` keeps
      // THIS arm rendered and the AsyncValue reports isLoading — reflect it
      // (spinner + disabled) so a tap on a persistently-failing store is
      // never a MUTE no-op (review, converged UX+reliability MED): the
      // sighted user sees work happen, and the label swap below re-fires the
      // live region when the identical error re-lands (#407 R5 — the flag had
      // to move INSIDE for that claim to be true).
      final refreshing = swaps.isLoading;
      return Padding(
        padding: const EdgeInsets.only(bottom: 28),
        child: Semantics(
          container: true,
          liveRegion: true,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                l10n.walletSwapsInFlightError,
                style: textTheme.bodyMedium?.copyWith(
                  color: colors.textMuted,
                ),
              ),
              // Start-aligned by the Column's crossAxisAlignment (RTL-correct).
              TextButton(
                key: const Key('swaps-in-flight-error-retry'),
                // Re-pull the identity-scoped reader (the same provider the
                // dismiss / resume paths invalidate) → the section re-runs
                // loading→data, recovering without a full app resume.
                onPressed: refreshing
                    ? null
                    : () => ref.invalidate(walletInFlightSwapsReadProvider),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    if (refreshing) ...[
                      const SizedBox(
                        width: 16,
                        height: 16,
                        child: CircularProgressIndicator.adaptive(
                          strokeWidth: 2,
                        ),
                      ),
                      const SizedBox(width: 8),
                    ],
                    // Flexible + an INNER live region, the twin of the parked
                    // section's retry (#407 R5 / #401 R8a): a Row child gets
                    // unbounded width, and a liveRegion on the outer container
                    // cannot re-announce because that node's data never
                    // changes — the label that SWAPS has to carry the flag.
                    Flexible(
                      child: Semantics(
                        liveRegion: refreshing,
                        child: Text(
                          refreshing
                              ? l10n.walletSwapsInFlightRetryInProgress
                              : l10n.walletSwapsInFlightRetry,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    },
    data: (records) {
      if (records.isEmpty) return const SizedBox.shrink();
      // The S12 look (S13 Build B): the Activity section's header and ONE
      // group with hairlines inset to the rows' text.
      return Padding(
        padding: const EdgeInsets.only(bottom: 16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsetsDirectional.only(start: 4),
              child: Text(
                l10n.walletSwapsInFlightTitle(records.length),
                // The Activity header's role, for parity with the Parked +
                // Activity sections (UX NIT; S12 C5).
                style: textTheme.titleLarge,
              ),
            ),
            const SizedBox(height: 12),
            WalletGroup(
              key: const ValueKey('wallet-swaps-group'),
              dividerIndent: _SwapRow.textInset,
              children: [
                for (final record in records) _SwapRow(record: record),
              ],
            ),
          ],
        ),
      );
    },
  );
}