Flutter调用StatefulWidget方法触发setState() called in constructor错误求助
问题根因
- 你在
MainDrawer的操作方法中每次都会新建一个全新的MyWorldMap实例,该实例并未挂载到当前页面的组件树中,操作它完全不会影响页面上正在渲染的地图组件 - 你在
MyWorldMapWidget层直接调用createState()获取状态实例并执行setState,此时新生成的State还未完成挂载流程,所以直接触发setState() called in constructor报错
修复方案(改动最小,基于GlobalKey实现)
该方案不需要大改原有逻辑,仅需调整几处代码即可:
- 在共同父组件
HomePage中创建GlobalKey绑定MyWorldMap的状态 - 将操作方法通过参数透传给
MainDrawer,直接操作页面上已挂载的MyWorldMap实例 - 移除
MyWorldMapWidget层错误的直接调用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,避免跨组件直接访问的逻辑耦合
- 将
MyWorldMapState中的操作方法从私有改为公有,移除了Widget层错误的直接调用createState的逻辑 - 删去了
_onMapCreated中无意义的空setState调用
内容的提问来源于stack exchange,提问作者Roar Grønmo
相关产品推荐
相关产品推荐

