You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Riverpod问题:为何ref.refresh()在_page=1时无法重建组件?

问题描述

我是Flutter和Riverpod新手,正在实现一个从服务端获取数据的带分页和刷新功能的列表。分页功能正常,但刷新仅在_page>1时生效,当_page=1时组件不会重建。已排查数小时,能看到DriverRepository().driverJobList(tab, pages, filtering)已调用且返回新的JSON数据,但界面无变化。

import 'package:super_carga_mobileapp/styles.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../Driver/Jobs/model.dart';
import 'package:super_carga_mobileapp/utilis.dart';
import 'job_list_provider.dart';
import 'widgets/job_list_element.dart';


class JobListPage extends ConsumerWidget {
  List<JobListProperties> jobPropertiesList = [];
  late int _maxPages;
  int _page =1;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    late ScrollController _scrollController;

    final jobListAsyncValue = ref.watch(JobListResponseProvider);

    void _loadMore() {
      if (_scrollController.position.atEdge) {
        if (_scrollController.position.extentAfter < 500) {
          if (_page < _maxPages) {
            ref.read(JobListPaginationProvider.notifier).state = ++_page;
          }
        }
      }
    }

    _scrollController = ScrollController()..addListener(_loadMore);

    Widget _buildList(int length) {
      return RefreshIndicator(
        onRefresh: () async {
          jobPropertiesList.clear();
          if (_page > 1) {
            _page = 1;
            ref.read(JobListPaginationProvider.notifier).state = _page;
          } else {
            return await ref.refresh(JobListResponseProvider);
          }
        },
        child: Column(
          children: [
            Expanded(
              child: ListView.separated(
                controller: _scrollController,
                itemCount: length,
                itemBuilder: (context, index) {
                  return JobListElement(jobPropertiesList[index]);
                },
                separatorBuilder: (BuildContext context, int index) =>
                    const Seperator(),
              ),
            ),
          ],
        ),
      );
    }



    return jobListAsyncValue.when(
      data: (data) {
        if (data.data.isNotEmpty) {
          jobPropertiesList.addAll(data.data.toList());
          final length = jobPropertiesList.length;
          _maxPages = data.pages;
          return _buildList(length);
        } else {
          return Column(
            children: [
              SizedBox(
                height: 60,
              ),
              Text(
                "No results",
                style: titleStyle,
              ),
            ],
          );
        }
      },
      error: (error, stackTrace) => ErrorMessage(message: error.toString()),
      loading: () {
        if (jobPropertiesList.isNotEmpty) {
          return _buildList(jobPropertiesList.length);
        } else {
          return PageLoadingSpinnerWidget();
        }
      },
    );
  }
}

final JobListResponseProvider = FutureProvider.autoDispose<JobListResponse>((ref) async {
  final  pages = ref.watch(JobListPaginationProvider);
  final tab = ref.watch(MiddleScreenTabProvider);
 final  filtering = ref.watch(searchTextProvider);
  return await DriverRepository().driverJobList(tab,  pages, filtering);
});

final JobListPaginationProvider = StateProvider.autoDispose<int>((ref) {
  return 1;
});

final searchTextProvider = StateProvider<Filtering>((ref) {
  return Filtering("");
});
final bottomNavIndex = StateProvider<int>((ref) {
  return 0;
});


final DeliveryButtonProvider = StateProvider<bool>((ref) {
  return false;
});
final PickupButtonProvider = StateProvider<bool>((ref) {
  return false;
});
final MiddleScreenTabProvider = StateProvider<int>((ref) {
  return 1;
});
问题分析与修复

核心问题

  1. Widget本地状态未重置:jobPropertiesList、_page是JobListPage的成员变量,属于Widget实例状态。当_page=1时调用ref.refresh,Provider获取新数据后,data回调里用addAll会把新数据追加到旧列表后,而非替换,界面自然不会更新为全新数据。
  2. ScrollController重复创建:每次build都会生成新的ScrollController并添加监听,会导致内存泄漏,也可能干扰分页逻辑。
  3. 刷新逻辑分支冗余:分_page>1和_page=1处理刷新,导致逻辑不一致,且_page=1时仅刷新Provider但未重置本地列表。

修复方案

1. 用Riverpod管理列表状态

创建专门的Notifier管理分页列表的状态,替代Widget本地变量:

class JobListStateNotifier extends Notifier<({List<JobListProperties> items, int currentPage, int maxPages})> {
  @override
  ({List<JobListProperties> items, int currentPage, int maxPages}) build() {
    return (items: [], currentPage: 1, maxPages: 1);
  }

  // 重置列表到初始状态
  void reset() {
    state = (items: [], currentPage: 1, maxPages: state.maxPages);
  }

  // 添加新页数据
  void addItems(List<JobListProperties> newItems, int maxPages) {
    state = (
      items: [...state.items, ...newItems],
      currentPage: state.currentPage + 1,
      maxPages: maxPages
    );
  }

  // 更新当前页码
  void setCurrentPage(int page) {
    state = (
      items: state.items,
      currentPage: page,
      maxPages: state.maxPages
    );
  }
}

final jobListStateProvider = NotifierProvider<JobListStateNotifier, ({List<JobListProperties> items, int currentPage, int maxPages})>(JobListStateNotifier.new);

2. 重构数据获取Provider

让jobListResponseProvider仅负责单页数据获取,列表拼接交给Notifier:

final jobListResponseProvider = FutureProvider.autoDispose<JobListResponse>((ref) async {
  final page = ref.watch(jobListStateProvider.select((state) => state.currentPage));
  final tab = ref.watch(MiddleScreenTabProvider);
  final filtering = ref.watch(searchTextProvider);
  return await DriverRepository().driverJobList(tab, page, filtering);
});

3. 重构JobListPage组件

改为ConsumerStatefulWidget管理ScrollController,统一刷新逻辑:

class JobListPage extends ConsumerStatefulWidget {
  const JobListPage({super.key});

  @override
  ConsumerState<JobListPage> createState() => _JobListPageState();
}

class _JobListPageState extends ConsumerState<JobListPage> {
  late final ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController()..addListener(_loadMore);
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  void _loadMore() {
    final state = ref.read(jobListStateProvider);
    if (_scrollController.position.atEdge && _scrollController.position.extentAfter < 500) {
      if (state.currentPage < state.maxPages) {
        ref.read(jobListStateProvider.notifier).setCurrentPage(state.currentPage + 1);
        ref.invalidate(jobListResponseProvider);
      }
    }
  }

  Widget _buildList(List<JobListProperties> items) {
    return RefreshIndicator(
      onRefresh: () async {
        ref.read(jobListStateProvider.notifier).reset();
        await ref.refresh(jobListResponseProvider);
      },
      child: ListView.separated(
        controller: _scrollController,
        itemCount: items.length,
        itemBuilder: (context, index) => JobListElement(items[index]),
        separatorBuilder: (_, __) => const Seperator(),
      ),
    );
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听数据变化,自动添加到列表
    ref.listen<AsyncValue<JobListResponse>>(jobListResponseProvider, (previous, next) {
      next.whenData((data) {
        if (data.data.isNotEmpty) {
          ref.read(jobListStateProvider.notifier).addItems(data.data.toList(), data.pages);
        }
      });
    });

    final jobListState = ref.watch(jobListStateProvider);
    final jobListAsyncValue = ref.watch(jobListResponseProvider);

    return jobListAsyncValue.when(
      data: (_) {
        return jobListState.items.isNotEmpty
            ? _buildList(jobListState.items)
            : Column(
                children: [
                  const SizedBox(height: 60),
                  Text("No results", style: titleStyle),
                ],
              );
      },
      error: (error, _) => ErrorMessage(message: error.toString()),
      loading: () {
        return jobListState.items.isNotEmpty
            ? _buildList(jobListState.items)
            : const PageLoadingSpinnerWidget();
      },
    );
  }
}

4. 额外优化

  • 所有Provider命名改为小驼峰(如jobListResponseProvider),符合Dart规范
  • 移除冗余的JobListPaginationProvider,分页状态由jobListStateProvider统一管理
  • 刷新逻辑统一,无需分页码处理,重置状态后直接刷新第一页数据

内容的提问来源于stack exchange,提问作者user1618809

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:07:59