如何在Flutter中用BLoC Cubit实现分页?旧数据消失问题解决
Flutter分页加载后旧数据消失的问题排查与解决
我在Flutter应用中实现分页功能,定义了movies列表存储API获取的指定页码数据,用ListView展示。通过ScrollController监听滚动,滚动到底部时更改页码重新调用接口,打算把新数据追加到旧列表中,但加载新数据后旧数据消失了,请问问题出在哪?该怎么解决?
相关代码
UI组件代码
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:movie_app/res/constants/colors.dart'; import 'package:movie_app/ui/pages/home_page/home_cubit/home_cubit/top_rated_bloc/top_rated_cubit.dart'; import '../../../models/movies.dart'; import '../../bloc_cubit/movies_status.dart'; class TopRatedMovies extends StatefulWidget { const TopRatedMovies({Key? key}) : super(key: key); @override State<TopRatedMovies> createState() => _TopRatedMoviesState(); } class _TopRatedMoviesState extends State<TopRatedMovies> { ScrollController scrollController = ScrollController(); List<Movies> movies = []; @override void initState() { BlocProvider.of<TopRatedCubit>(context).getTopRatedMovies(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MyColor.primaryColor, appBar: AppBar( backgroundColor: MyColor.primaryColor, title: const Text('TOP RATED MOVIES'), ), body: BlocConsumer<TopRatedCubit, TopRatedState>( listener: (context, state){}, builder: (context, state){ var cubit = BlocProvider.of<TopRatedCubit>(context); movies = state.topRated; switch(state.status){ case MoviesStatus.loading: return const Center(child: CircularProgressIndicator()); case MoviesStatus.initial: case MoviesStatus.success: case MoviesStatus.failure: return ListView.separated( controller: scrollController..addListener(() { if(scrollController.position.pixels == scrollController.position.maxScrollExtent){ cubit.currentPage++; BlocProvider.of<TopRatedCubit>(context).getTopRatedMovies(); movies = [...movies, ...state.topRated]; } }), itemCount: movies.length, itemBuilder: (context, index){ return Text( '${movies[index].title}',style: const TextStyle(color: Colors.white), ); }, separatorBuilder: (context, index){ return const SizedBox(height: 40,); } ); } }, ), ); } }
Cubit代码
import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:meta/meta.dart'; import '../../../../../../models/movies.dart'; import '../../../../../../repositories/movies_repo.dart'; import '../../../../../bloc_cubit/movies_status.dart'; part 'top_rated_state.dart'; class TopRatedCubit extends Cubit<TopRatedState> { TopRatedCubit({required this.moviesRepo}) : super(const TopRatedState()); final MoviesRepo moviesRepo; int currentPage = 1; Future<List<Movies>> getTopRatedMovies() async{ emit( state.copyWith( status: MoviesStatus.loading, ) ); moviesRepo.getTopRatedMovies(page: currentPage).then((topRatedMovies) { emit( state.copyWith( status: MoviesStatus.success, topRated: topRatedMovies ), ); }).catchError((error){ emit( state.copyWith( status: MoviesStatus.failure, exception: error ) ); }); return []; } }
问题原因与解决方法
1. Cubit中未累加历史数据(核心问题)
每次调用getTopRatedMovies获取新页数据时,直接用新数据覆盖了state.topRated,没有把新数据追加到旧数据列表里,导致UI层拿到的永远是当前页的新数据,旧数据丢失。
修复Cubit代码:
moviesRepo.getTopRatedMovies(page: currentPage).then((topRatedMovies) { emit( state.copyWith( status: MoviesStatus.success, // 合并旧数据与新数据 topRated: [...state.topRated, ...topRatedMovies] ), ); })
2. UI层的movies赋值逻辑错误
在BlocConsumer的builder里直接movies = state.topRated会覆盖本地列表,且滚动监听里的movies = [...movies, ...state.topRated]无效——Bloc状态更新后会重新触发builder,该赋值会被覆盖。
修复UI层逻辑:
不需要维护本地movies列表,直接使用state.topRated:
builder: (context, state){ var cubit = BlocProvider.of<TopRatedCubit>(context); switch(state.status){ case MoviesStatus.loading: return const Center(child: CircularProgressIndicator()); case MoviesStatus.initial: case MoviesStatus.success: case MoviesStatus.failure: return ListView.separated( controller: scrollController, // 直接使用state中的数据 itemCount: state.topRated.length, itemBuilder: (context, index){ return Text( '${state.topRated[index].title}',style: const TextStyle(color: Colors.white), ); }, separatorBuilder: (context, index){ return const SizedBox(height: 40,); } ); } }
3. 滚动监听重复绑定问题
在ListView.separated的controller里每次builder都添加监听,会导致重复绑定多个监听,触发多次加载。应把监听添加到initState中:
修复滚动监听:
@override void initState() { super.initState(); BlocProvider.of<TopRatedCubit>(context).getTopRatedMovies(); // 仅在初始化时添加一次监听 scrollController.addListener(() { if(scrollController.position.pixels == scrollController.position.maxScrollExtent){ var cubit = BlocProvider.of<TopRatedCubit>(context); cubit.currentPage++; cubit.getTopRatedMovies(); } }); } // 销毁时释放controller资源 @override void dispose() { scrollController.dispose(); super.dispose(); }
4. 可选优化:避免重复触发加载
在Cubit中添加isLoading状态,防止滚动到底部时多次触发API请求:
class TopRatedCubit extends Cubit<TopRatedState> { TopRatedCubit({required this.moviesRepo}) : super(const TopRatedState()); final MoviesRepo moviesRepo; int currentPage = 1; bool isLoading = false; // 新增加载状态标记 Future<List<Movies>> getTopRatedMovies() async{ if(isLoading) return []; // 正在加载时直接返回,避免重复请求 isLoading = true; emit( state.copyWith( status: MoviesStatus.loading, ) ); moviesRepo.getTopRatedMovies(page: currentPage).then((topRatedMovies) { emit( state.copyWith( status: MoviesStatus.success, topRated: [...state.topRated, ...topRatedMovies] ), ); isLoading = false; }).catchError((error){ emit( state.copyWith( status: MoviesStatus.failure, exception: error ) ); isLoading = false; }); return []; } }
内容的提问来源于stack exchange,提问作者MhmD Yo
相关产品推荐
相关产品推荐

