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

为何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的工作机制:

  1. build方法的执行特性:当组件状态变化(比如你这里placesProvider的state更新)、父组件重建、窗口尺寸变化等情况发生时,build方法都会重新执行。

  2. Code 2的问题循环:

    • 每次build执行时,ref.read(placesProvider.notifier).getData()都会被调用一次,创建一个全新的Future实例。
    • FutureBuilder会认为这是一个新的未完成的异步任务,因此重新进入ConnectionState.waiting状态。
    • 而getData()方法最终会更新placesProvider的state,这又会触发build方法再次执行,再次创建新的Future,形成无限循环,导致永远停留在等待状态。
  3. Code 1的正确逻辑:

    • initState只会在组件初始化时执行一次,在这里调用getData()并保存返回的Future到_placesFuture变量中。
    • 后续无论build方法执行多少次,FutureBuilder使用的都是同一个Future实例。
    • 当这个Future完成后,FutureBuilder就会切换到完成状态,结合placesProvider的state渲染正确内容,不会再重复触发异步任务。

简单来说:必须保证FutureBuilder的future参数是一个稳定的、不会在每次build时重新创建的Future,否则就会陷入无限等待的循环。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:00:38