Flutter:FutureBuilder内setState不生效,无法按年份筛选文档
问题分析与解决方案
问题根源
- 选中状态无法持久化:
selectedYear列表定义在build方法内部,每次调用build(包括setState触发后)都会重新初始化,导致用户选择的年份状态被重置。 - 无持久化的文档数据源:仅在
FutureBuilder的builder回调中临时生成文档列表,未将原始数据保存到状态变量,无法在筛选时复用。 - 缺失年份筛选逻辑:ToggleButton点击后仅更新选中状态,未根据选中年份对文档列表进行过滤,页面内容自然不会变化。
修复方案
步骤1:调整状态变量至类级别
将选中状态、原始文档列表、过滤后列表定义为State类的成员变量:
class _ListOfDocumentsState extends State<ListOfDocuments> { final HttpService httpService = HttpService(); Future<List<Document>>? listadoDocumentos; String myLocale = Intl.getCurrentLocale(); // 类级别状态变量 final List<String> yearValues = ['all', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013']; List<bool> selectedYear = <bool>[true, false, false, false, false, false, false, false, false, false]; List<Document>? _allDocuments; List<Document>? _filteredDocuments; void cargarDocumentos() async { listadoDocumentos = httpService.getDocList( myLocale.toString(), widget.sectionRowName, widget.singleItem, ); } @override void initState() { super.initState(); cargarDocumentos(); }
步骤2:在FutureBuilder中保存原始数据
首次获取到文档数据后,将其存入状态变量并初始化过滤列表:
@override Widget build(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; List<Widget> years = <Widget>[ Padding( padding: const EdgeInsets.all(4.0), child: Text(S.of(context).all), ), ...yearValues.sublist(1).map((year) => Text(year)).toList(), ]; return Scaffold( backgroundColor: kPrimaryColor, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0.0, centerTitle: true, title: getTitle(widget.sectionRowName, widget.singleItem.name, context), ), body: FutureBuilder( future: listadoDocumentos, builder: (BuildContext context, AsyncSnapshot snapshotFromList) { if (snapshotFromList.hasData) { // 仅首次加载时保存原始数据 if (_allDocuments == null) { _allDocuments = List.generate( snapshotFromList.data.length, (index) => Document( id: snapshotFromList.data[index].id, fecha: snapshotFromList.data[index].fecha, titulo: snapshotFromList.data[index].titulo, ), ); _filteredDocuments = List.from(_allDocuments!); } return Column( children: [ SingleChildScrollView( scrollDirection: Axis.horizontal, child: Padding( padding: const EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0), child: ToggleButtons( direction: Axis.horizontal, textStyle: kAppBarStyle.copyWith(fontSize: 18.0), borderColor: Colors.yellow, borderWidth: 1.0, selectedBorderColor: Colors.yellowAccent, fillColor: Colors.yellowAccent.withOpacity(0.2), color: Colors.white, selectedColor: Colors.white, isSelected: selectedYear, borderRadius: const BorderRadius.all(Radius.circular(10.0)), onPressed: (int index) { setState(() { // 更新选中状态 for (int i = 0; i < selectedYear.length; i++) { selectedYear[i] = i == index; } // 执行筛选逻辑 if (index == 0) { _filteredDocuments = List.from(_allDocuments!); } else { String targetYear = yearValues[index]; _filteredDocuments = _allDocuments!.where((doc) { DateTime dateFormated = DateFormat('dd/MM/yyyy').parse(doc.fecha); return DateFormat("yyyy").format(dateFormated) == targetYear; }).toList(); } }); }, children: years, ), ), ), Expanded( child: Padding( padding: EdgeInsets.fromLTRB(0.0, 0.0, screenWidth * 0.05, 0.0), child: SingleChildScrollView( scrollDirection: Axis.vertical, child: ListView.separated( itemCount: _filteredDocuments?.length ?? 0, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), separatorBuilder: (BuildContext context, int index) { return const Divider( thickness: 2.0, color: Colors.white10, indent: 15.0, ); }, itemBuilder: (context, index) { final doc = _filteredDocuments![index]; DateTime dateFormated = DateFormat('dd/MM/yyyy').parse(doc.fecha); return ListTile( leading: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Text( DateFormat("dd").format(dateFormated), style: kDocTitleInList.copyWith( color: Colors.white70, ), ), Text( DateFormat("MMM") .format(dateFormated) .toUpperCase(), style: kDocTitleInList.copyWith( fontSize: 10.0, color: Colors.white70, ), ), Text( DateFormat("yyyy").format(dateFormated), style: kDocTitleInList.copyWith( fontSize: 14.0, color: Colors.white70, ), ), ], ), title: Text( doc.titulo, style: kDocTitleInList, ), trailing: const Icon( Icons.arrow_forward_ios_rounded, color: Colors.white, ), onTap: () { showDialog( context: context, builder: (BuildContext context) { return FutureBuilder( future: httpService.getDocument( snapshotFromList.data[index].id .toString()), builder: (BuildContext context, AsyncSnapshot snapshotFromDocument) { if (snapshotFromDocument.hasData) { return Dialog( backgroundColor: Colors.transparent, elevation: 0.0, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(30.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding( padding: const EdgeInsets.all( 15.0), child: Text( snapshotFromList .data[index].titulo, style: kSubTitleStyle .copyWith( color: Colors.black), ), ), Padding( padding: const EdgeInsets.all( 15.0), child: FlagChips( documento: snapshotFromDocument .data), ), GestureDetector( child: Text( '- ${S.of(context).cerrarVentana} -', style: kSubTitleStyle .copyWith( color: Colors.black), ), onTap: () { Navigator.of(context) .pop(); }, ), const SizedBox( height: 20.0, ), ], ), ), ); } else { return const Center( child: CircularProgressIndicator( color: Colors.white54, ), ); } }); }, ); }, ); }, ), ), ), ), ], ); } else { return const Center( child: CircularProgressIndicator( color: Colors.white54, ), ); } }, ), ); }
关键修改说明
- 将
selectedYear移至类级别,确保setState后状态能持久化; - 新增
_allDocuments保存原始数据,避免重复生成; - 添加
_filteredDocuments存储筛选结果,作为ListView的数据源; - 在ToggleButton的
onPressed回调中实现年份筛选逻辑,通过setState触发页面更新。
内容的提问来源于stack exchange,提问作者jRuMoL
相关产品推荐
相关产品推荐

