callWithLoadingAfterTimeout<T> function

Future<T> callWithLoadingAfterTimeout<T>(
  1. Future<T> fn(), {
  2. void onError(
    1. dynamic error
    )?,
  3. int? timeoutBeforeLoadingMill,
  4. LoadingStartCallback? onLoadingStart,
  5. LoadingFinishCallback? onLoadingFinish,
})

Calls the given function and shows a loading overlay after a timeout. The loading overlay is shown only if the function call takes longer than the given timeout. The T type is the return type of the function.

Implementation

Future<T> callWithLoadingAfterTimeout<T>(
  Future<T> Function() fn, {
  void Function(dynamic error)? onError,
  int? timeoutBeforeLoadingMill,
  LoadingStartCallback? onLoadingStart,
  LoadingFinishCallback? onLoadingFinish,
}) async {
  bool isCompleted = false;
  bool loadingWasShown = false;
  Timer? timer;

  // Create timer with proper error handling
  timer = Timer(
    Duration(
      milliseconds: timeoutBeforeLoadingMill ?? AppConfigBase.timeoutBeforeShowingLoadingMill,
    ),
    () {
      try {
        // Double-check that the function hasn't completed yet
        if (!isCompleted) {
          loadingWasShown = true;
          _activeLoadingCallsCount++;
          logd('Loading started, active count: $_activeLoadingCallsCount');
          (onLoadingStart ?? _defaultLoadingStart)();
        }
      } catch (e) {
        loge('Error in loading start callback: $e');
      }
    },
  );

  try {
    final result = await fn();
    return result;
  } catch (error) {
    onError?.call(error);
    rethrow;
  } finally {
    // Mark as completed first to prevent race conditions
    isCompleted = true;

    // Cancel timer to prevent it from firing after completion
    timer.cancel();

    // Only decrement and potentially hide loading if we actually showed it
    if (loadingWasShown) {
      _activeLoadingCallsCount--;
      logd('Loading finished, active count: $_activeLoadingCallsCount');

      // Ensure counter doesn't go negative (defensive programming)
      if (_activeLoadingCallsCount < 0) {
        logw('Loading counter went negative, resetting to 0');
        _activeLoadingCallsCount = 0;
      }

      // Only call finish loading when this is the last active loading call
      if (_activeLoadingCallsCount == 0) {
        try {
          (onLoadingFinish ?? _defaultLoadingFinish)();
        } catch (e) {
          loge('Error in loading finish callback: $e');
        }
      }
    }
  }
}