为何Flutter中需在initState初始化Future后传入FutureBuilder?
问题:FutureBuilder传入直接调用的Future为何一直处于等待状态?
我是一名学习Flutter约4个月的开发者,遇到了一个无法理解的问题:
以下两段代码中,只有Code 1可以正常运行,Code 2会一直处于ConnectionState.waiting状态。我不清楚为何必须先在initState中初始化_placesFuture,再将其传入FutureBuilder的future参数,而不能直接将ref.read(placesProvider.notifier).getData()放在该参数中?
Code 1(正常运行)
late Future _placesFuture; @override void initState() { _placesFuture = ref.read(placesProvider.notifier).getData(); super.initState(); } FutureBuilder( future: _placesFuture, builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting ? const Center( child: CircularProgressIndicator(), ) : places.isEmpty ? const Center( child: Text('No item here.'), ) : ListView.builder( itemBuilder: (context, index) => PlaceItem(places[index]), itemCount: places.length, ), )
Code 2(一直处于等待状态)
FutureBuilder( future: ref.read(placesProvider.notifier).getData(), builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting ? const Center( child: CircularProgressIndicator(), ) : places.isEmpty ? const Center( child: Text('No item here.'), ) : ListView.builder( itemBuilder: (context, index) => PlaceItem(places[index]), itemCount: places.length, ), )
完整代码如下:
places_provider.dart
import 'dart:io'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as path; import 'package:sqflite/sqflite.dart' as sql; import '../models/place.dart'; Future<sql.Database> _getDb() async { final dbPath = await sql.getDatabasesPath(); final db = await sql.openDatabase( path.join(dbPath, 'places.db'), onCreate: (db, version) { return db.execute('CREATE TABLE places(id TEXT PRIMARY KEY, title TEXT, image TEXT, lat REAL, lng REAL, address TEXT)'); }, version: 1, ); return db; } class PlacesNotifier extends StateNotifier<List<Place>> { PlacesNotifier() : super([]); Future<void> removePlace(Place place) async { final db = await _getDb(); db.delete('places', where: 'id = ?', whereArgs: [place.id]); if (state.contains(place)) { state = state.where((element) => element.id != place.id).toList(); } } Future<void> addPlace(Place place) async { final db = await _getDb(); db.insert('places', { 'id': place.id, 'title': place.title, 'image': place.image.path, 'lat': place.location.lat, 'lng': place.location.lng, 'address': place.location.address, }); state = [place, ...state]; } Future<void> getData() async { final db = await _getDb(); final data = await db.query('places'); final p = data .map( (e) => Place( id: e['id'] as String, title: e['title'] as String, image: File(e['image'] as String), location: PlaceLocation( lat: e['lat'] as double, lng: e['lng'] as double, address: e['address'] as String, ), ), ) .toList(); state = p; } } final placesProvider = StateNotifierProvider<PlacesNotifier, List<Place>>( (ref) { return PlacesNotifier(); }, );
places_screen.dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/places_provider.dart'; import '../models/place.dart'; import '../widgets/place_item.dart'; import './new_place_screen.dart'; class PlacesScreen extends ConsumerStatefulWidget { const PlacesScreen({super.key}); @override ConsumerState<PlacesScreen> createState() => _PlacesScreenState(); } class _PlacesScreenState extends ConsumerState<PlacesScreen> { late Future _placesFuture; @override void initState() { _placesFuture = ref.read(placesProvider.notifier).getData(); super.initState(); } @override Widget build(BuildContext context) { final places = ref.watch(placesProvider); return Scaffold( appBar: AppBar( title: const Text('我的所有秘密基地'), actions: [ IconButton( onPressed: () async { final p = await Navigator.of(context).push<Place>(MaterialPageRoute( builder: (context) => const NewPlaceScreen(), )); ref.read(placesProvider.notifier).addPlace(p!); }, icon: const Icon(Icons.add)) ], ), body: FutureBuilder( future: _placesFuture, builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting ? const Center( child: CircularProgressIndicator(), ) : places.isEmpty ? const Center( child: Text('尚未添加任何地點。'), ) : ListView.builder( itemBuilder: (context, index) => PlaceItem(places[index]), itemCount: places.length, ), ), ); } }
解答
核心原因在于Flutter的build方法会被多次触发,以及FutureBuilder的工作机制:
build方法的执行特性:当组件状态变化(比如你这里placesProvider的state更新)、父组件重建、窗口尺寸变化等情况发生时,build方法都会重新执行。Code 2的问题循环:
- 每次
build执行时,ref.read(placesProvider.notifier).getData()都会被调用一次,创建一个全新的Future实例。 FutureBuilder会认为这是一个新的未完成的异步任务,因此重新进入ConnectionState.waiting状态。- 而
getData()方法最终会更新placesProvider的state,这又会触发build方法再次执行,再次创建新的Future,形成无限循环,导致永远停留在等待状态。
- 每次
Code 1的正确逻辑:
initState只会在组件初始化时执行一次,在这里调用getData()并保存返回的Future到_placesFuture变量中。- 后续无论
build方法执行多少次,FutureBuilder使用的都是同一个Future实例。 - 当这个Future完成后,
FutureBuilder就会切换到完成状态,结合placesProvider的state渲染正确内容,不会再重复触发异步任务。
简单来说:必须保证FutureBuilder的future参数是一个稳定的、不会在每次build时重新创建的Future,否则就会陷入无限等待的循环。
内容的提问来源于stack exchange,提问作者WangXuan
相关产品推荐
相关产品推荐

