Skip to content

Issue: skipLoadingOnRefresh and skipLoadingOnReload parameters not working correctly in AsyncValue.when() #4670

@zmtzawqlp

Description

@zmtzawqlp

Issue: skipLoadingOnRefresh and skipLoadingOnReload parameters not working correctly in AsyncValue.when()

Problem Description

The skipLoadingOnRefresh and skipLoadingOnReload parameters in AsyncValue.when() are not functioning as expected. When triggering a refresh operation, it also triggers a reload, causing skipLoadingOnRefresh to be ineffective.

Current Behavior

When using either of the following methods to refresh a provider:

  • ref.invalidateSelf()
  • ref.refresh(controllerProvider.future)

The refresh operation also triggers a reload, which means:

  1. skipLoadingOnRefresh: true does not prevent the loading state from being shown during refresh
  2. The distinction between refresh and reload operations is not properly maintained

Expected Behavior

  1. Refresh operations (via ref.invalidateSelf() or ref.refresh()) should:

    • Respect the skipLoadingOnRefresh parameter
    • When skipLoadingOnRefresh: true, show cached data instead of the loading state during refresh
    • Only trigger refresh-related behavior, not reload behavior
  2. Reload operations (via ref.invalidateSelf(asReload: true)) should:

    • Respect the skipLoadingOnReload parameter
    • When skipLoadingOnReload: true, show cached data instead of the loading state during reload
    • Only trigger reload-related behavior, not refresh behavior
  3. Clear separation: Refresh and reload should be independent operations that don't interfere with each other.

Steps to Reproduce

  1. Create a simple AsyncNotifier provider:
@riverpod
class UserData extends _$UserData {
  @override
  Future<String> build() async {
    // Simulate API call
    await Future.delayed(Duration(seconds: 1));
    return 'User Data';
  }

  Future<void> refresh() async {
    ref.invalidateSelf(); // Should trigger refresh only
  }

  Future<void> reload() async {
    ref.invalidateSelf(asReload: true); // Should trigger reload only
  }
}
  1. Create a widget that uses AsyncValue.when() with skipLoadingOnRefresh: true:
class UserDataWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncData = ref.watch(userDataProvider);
    
    return asyncData.when(
      skipLoadingOnRefresh: true,  // Should skip loading on refresh
      skipLoadingOnReload: false,   // Should show loading on reload
      loading: () => Text('Loading...'),
      data: (data) => Text('Data: $data'),
      error: (error, stack) => Text('Error: $error'),
    );
  }
}
  1. Trigger refresh operation:
// In a button or pull-to-refresh handler
ref.read(userDataProvider.notifier).refresh();
// OR
await ref.refresh(userDataProvider.future);
  1. Observed Behavior:

    • The loading state is shown even though skipLoadingOnRefresh: true is set
    • The refresh operation appears to also trigger reload behavior
  2. Expected Behavior:

    • When skipLoadingOnRefresh: true, the cached data should remain visible during refresh
    • Only the reload operation (via ref.invalidateSelf(asReload: true)) should show loading state when skipLoadingOnReload: false

Minimal Reproducible Example

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'example.g.dart';

// Provider
@riverpod
class ExampleData extends _$ExampleData {
  @override
  Future<String> build() async {
    await Future.delayed(Duration(seconds: 1));
    return 'Initial Data';
  }

  Future<void> refresh() async {
    ref.invalidateSelf(); // Should only trigger refresh
  }

  Future<void> reload() async {
    ref.invalidateSelf(asReload: true); // Should only trigger reload
  }
}

// Widget
class ExampleWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final asyncData = ref.watch(exampleDataProvider);
    
    return Column(
      children: [
        // Display with skipLoadingOnRefresh: true
        asyncData.when(
          skipLoadingOnRefresh: true,  // Problem: This doesn't work
          skipLoadingOnReload: false,
          loading: () => Text('Loading...'), // Shows even on refresh
          data: (data) => Text('Data: $data'),
          error: (error, _) => Text('Error: $error'),
        ),
        
        ElevatedButton(
          onPressed: () {
            // This should NOT show loading due to skipLoadingOnRefresh: true
            ref.read(exampleDataProvider.notifier).refresh();
          },
          child: Text('Refresh'),
        ),
        
        ElevatedButton(
          onPressed: () {
            // This SHOULD show loading due to skipLoadingOnReload: false
            ref.read(exampleDataProvider.notifier).reload();
          },
          child: Text('Reload'),
        ),
      ],
    );
  }
}

Expected vs Actual:

  • Refresh button: Should keep showing cached data (skipLoadingOnRefresh: true)
  • Reload button: Should show loading state (skipLoadingOnReload: false)
  • Actual: Both buttons show loading state, indicating refresh is triggering reload behavior

Environment

  • Riverpod version: 3.0.3
  • Flutter version: >=3.35.0
  • Dart version: >=3.8.0 <4.0.0

Additional Context

This issue significantly impacts user experience in common scenarios:

  1. Pull-to-refresh: Users expect to see cached data while new data loads in the background
  2. Background updates: Periodic data refreshes should not interrupt the UI with loading states
  3. Different refresh strategies: The distinction between "refresh" (update existing data) and "reload" (completely reset) is important for UX

The current behavior forces developers to implement workarounds, such as:

  • Manually tracking refresh state
  • Using custom loading flags
  • Implementing separate refresh/reload logic outside of Riverpod's built-in mechanisms

This adds unnecessary complexity and potential for bugs.

Related Code

The issue appears to be in async_value.dart around lines 243-244, where the skipLoadingOnReload and skipLoadingOnRefresh parameters are defined but may not be properly checked when determining whether to show the loading state.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions