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

Flutter调用StatefulWidget方法触发setState() called in constructor错误求助

问题根因
  • 你在MainDrawer的操作方法中每次都会新建一个全新的MyWorldMap实例,该实例并未挂载到当前页面的组件树中,操作它完全不会影响页面上正在渲染的地图组件
  • 你在MyWorldMap Widget层直接调用createState()获取状态实例并执行setState,此时新生成的State还未完成挂载流程,所以直接触发setState() called in constructor报错
修复方案(改动最小,基于GlobalKey实现)

该方案不需要大改原有逻辑,仅需调整几处代码即可:

  1. 在共同父组件HomePage中创建GlobalKey绑定MyWorldMap的状态
  2. 将操作方法通过参数透传给MainDrawer,直接操作页面上已挂载的MyWorldMap实例
  3. 移除MyWorldMap Widget层错误的直接调用createState的方法

修复后完整代码

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget{
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title:"My App test",
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(title: "My World Map")
    );
  }
}

class HomePage extends StatefulWidget{
  final String title;
  HomePage({Key? key, required this.title}):super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>{
  // 定义GlobalKey绑定MyWorldMap的状态
  final GlobalKey<_MyWorldMapState> _mapKey = GlobalKey();

  @override
  void initState(){
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      // 将操作方法透传给Drawer
      drawer: MainDrawer(
        onAddTile: () => _mapKey.currentState?.addTileOverlay(),
        onClearCache: () => _mapKey.currentState?.clearTileCache(),
        onRemoveTile: () => _mapKey.currentState?.removeTileOverlay(),
      ),
      // 把key传给MyWorldMap实例
      body: MyWorldMap(key: _mapKey),
    );
  }
}

class MainDrawer extends StatefulWidget{
  // 定义回调参数
  final VoidCallback onAddTile;
  final VoidCallback onClearCache;
  final VoidCallback onRemoveTile;

  const MainDrawer({
    Key? key,
    required this.onAddTile,
    required this.onClearCache,
    required this.onRemoveTile
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => MainDrawerState();
}

class MainDrawerState extends State<MainDrawer>{
  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: <Widget>[
          const DrawerHeader(
            decoration: BoxDecoration(color: Colors.blue),
            child: Text("My World Map"),
          ),
          ListTile(
            title: const Text ("Add tile overlay"),
            onTap: () => widget.onAddTile(),
          ),
          ListTile(
            title: const Text ("Clear tile overlay cache"),
            onTap: () => widget.onClearCache(),
          ),
          ListTile(
            title: const Text ("Remove tile overlay"),
            onTap: () => widget.onRemoveTile(),
          ),
        ],
      ),
    );
  }
}

class MyWorldMap extends StatefulWidget{
  const MyWorldMap({Key? key}) : super(key: key);

  @override
  _MyWorldMapState createState() => _MyWorldMapState();
}

class _MyWorldMapState extends State<MyWorldMap>
{
  TileOverlay? _tileOverlay;
  late GoogleMapController _mapController;
  final LatLng _initialCameraPosition = const LatLng(61.9026,6.7003); //Change with your location

  //You need to change maps API key in AndroidManifest.xml

  @override
  void initState(){
    super.initState();
  }

  Future<void> _onMapCreated(GoogleMapController controller) async {
    _mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    Set<TileOverlay> overlays = <TileOverlay>{
      if(_tileOverlay != null) _tileOverlay!,
    };

    return GoogleMap(
      onMapCreated: _onMapCreated,
      initialCameraPosition: CameraPosition(
        target: _initialCameraPosition,
        zoom:15,
      ),
      myLocationEnabled: false,
      tileOverlays: overlays,
    );
  }

  // 把原来的私有方法改成公有,给外部调用
  void addTileOverlay()
  {
    final TileOverlay tileOverlay = TileOverlay(
      tileOverlayId: const TileOverlayId("My World Map Overlay"),
      tileProvider: MyWorldMapTileProvider(),
    );
    setState((){
      _tileOverlay = tileOverlay;
    });
  }

  void clearTileCache()
  {
    if(_tileOverlay != null){
      _mapController.clearTileCache(_tileOverlay!.tileOverlayId);
    }
  }

  void removeTileOverlay()
  {
    setState(() {
      _tileOverlay = null;
    });
  }
}

class MyWorldMapTileProvider implements TileProvider {
  @override
  Future<Tile> getTile(int x, int y, int? zoom) async {
    String path = 'https://maptiles1.finncdn.no/tileService/1.0.1/norortho/$zoom/$x/$y.png';
    http.Response response = await http.get(
      Uri.parse(path)
    );
    return Tile(x,y,response.bodyBytes);
  }
}
核心改动说明
  • 新增了GlobalKey来关联页面上实际渲染的MyWorldMap实例,保证操作的是同一个组件
  • 通过回调参数把操作方法从父组件透传给Drawer,避免跨组件直接访问的逻辑耦合
  • 将MyWorldMap State中的操作方法从私有改为公有,移除了Widget层错误的直接调用createState的逻辑
  • 删去了_onMapCreated中无意义的空setState调用

内容的提问来源于stack exchange,提问作者Roar Grønmo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 02:45:03