Flutter:在构造函数中调用了 setState()
Flutter: setState() called in constructor
我是 Flutter 的新手,运行 在尝试显示地图和实现一些 tilelayers 时遇到问题。
该应用程序有一个 Drawer
实现,我想在其中 enable/disable 并清除图块缓存。
我已经获取了一些运行良好的示例,所以我知道 tiling
工作得很好,但是这里我 运行 遇到一个问题,我想调用 MyWorldMap 的成员函数来自抽屉小部件的有状态小部件,据我所知,我现在被 setState() called in constructor
错误消息所困扰。
你有什么建议可以帮助我或指导我走上正确的道路吗?
添加自己的MAP API KEY
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>{
@override
void initState(){
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(),
body: MyWorldMap(),
);
}
}
class MainDrawer extends StatefulWidget{
@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: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
MyWorldMap().addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
MyWorldMap().clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
MyWorldMap().removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({Key? key}) : super(key: key);
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
@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;
setState(() {
//Do stuff ?
});
}
@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: TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
setState((){ //The code fails here when pushing the 'Add tile overlay button' !!
_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);
}
}
似乎您在 build
方法完成构建小部件之前使用 setState
。我建议在构建完成后使用 setState
,这样:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
// do stuff;
});
});
并不是说我是一个真正的 flutter 专业人士,但我认为问题可能出在这里:
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
每次在 MyWorldMap
小部件中调用这些方法之一时,您都会创建一个新状态,我认为这不是正确的行为。
如果你想从另一个 Widget 编辑 Widget 状态,你应该尝试使用键:我认为任何有状态的 Widget 都可以在构造函数中使用 key
参数,可以依次使用它来改变它的来自其他小部件的状态。我会试着写一个简单的例子。
class Parent extends StatelessWidget {
final keyA = GlobalKey();
final keyB = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
ChildA(keyA),
ChildB(keyB, keyA),
]),
);
}
}
class ChildA extends StatefulWidget {
const ChildA(GlobalKey key) : super(key: key);
@override
State<StatefulWidget> createState() => ChildAState();
}
class ChildAState extends State<ChildA> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Text("Child A count: $counter");
}
void increaseCounter(){
setState(() {
counter++;
});
}
}
class ChildB extends StatefulWidget {
final GlobalKey childAKey;
const ChildB(GlobalKey key, this.childAKey) : super(key: key);
@override
State<StatefulWidget> createState() => ChildBState();
}
class ChildBState extends State<ChildB> {
@override
Widget build(BuildContext context) {
return TextButton(
child: const Text("Press here"),
onPressed: () {
(widget.childAKey.currentState as ChildAState).increaseCounter();
},
);
}
}
@il_boga 引导我找到答案后(所有功劳都归功于他),我将 post 工作代码放在这里:
我将 TileOverlay
创建移动到 _MyWorldMapState
class 的 initState
,并添加了缓冲 'layer' 以便我可以切换 on/off 通过在移除时将 _mapTileOverlay
设置为 null 并在添加叠加层时将 _bufferedMapTileOverlay
设置回 _bufferedMapTileOverlay
的图层。
此外,我创建了两个 GlobalKey(实际上我不知道为什么我需要 drawerKey
,因为我从来没有在任何地方主动引用过它...,mapKey
很明显)
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;
const HomePage({Key? key, required this.title}):super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
final drawerKey = GlobalKey();
final mapKey = GlobalKey();
@override
void initState(){
print("_HomePageState(): initState");
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(drawerKey: drawerKey, mapKey: mapKey,),
body: MyWorldMap(mapKey: mapKey,),
);
}
}
class MainDrawer extends StatefulWidget{
final GlobalKey mapKey;
const MainDrawer({required GlobalKey drawerKey, required this.mapKey}) : super(key: drawerKey);
@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: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
//MyWorldMap().addTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
//MyWorldMap().clearTileCache();
(widget.mapKey.currentState as _MyWorldMapState)._clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
//MyWorldMap().removeTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({required GlobalKey mapKey}) : super(key: mapKey);
//addTileOverlay() => createState()._addTileOverlay();
//removeTileOverlay() => createState()._removeTileOverlay();
//clearTileCache() => createState()._clearTileCache();
@override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _bufferedMapTileOverlay; //intermediate, which actually holds the overlay
TileOverlay? _mapTileOverlay; //value which connects to the map
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(){
print("_MyWordMapState(): initState");
super.initState();
final TileOverlay newMapTileOverlay = TileOverlay( //Inits the tileOverlay
tileOverlayId: const TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
_bufferedMapTileOverlay = newMapTileOverlay;
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
@override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{ //connect a set of overlays (here just one)
if(_mapTileOverlay != null) _mapTileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays, //connect to the set of overlays (I have only one (see above))
);
}
void _addTileOverlay()
{
setState((){
_mapTileOverlay = _bufferedMapTileOverlay;
});
}
void _clearTileCache()
{
if(_mapTileOverlay != null){
print("Clearing tile cache");
_mapController.clearTileCache(_mapTileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_mapTileOverlay = 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);
}
}
我是 Flutter 的新手,运行 在尝试显示地图和实现一些 tilelayers 时遇到问题。
该应用程序有一个 Drawer
实现,我想在其中 enable/disable 并清除图块缓存。
我已经获取了一些运行良好的示例,所以我知道 tiling
工作得很好,但是这里我 运行 遇到一个问题,我想调用 MyWorldMap 的成员函数来自抽屉小部件的有状态小部件,据我所知,我现在被 setState() called in constructor
错误消息所困扰。
你有什么建议可以帮助我或指导我走上正确的道路吗?
添加自己的MAP API KEYimport '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>{
@override
void initState(){
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(),
body: MyWorldMap(),
);
}
}
class MainDrawer extends StatefulWidget{
@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: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
MyWorldMap().addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
MyWorldMap().clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
MyWorldMap().removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({Key? key}) : super(key: key);
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
@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;
setState(() {
//Do stuff ?
});
}
@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: TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
setState((){ //The code fails here when pushing the 'Add tile overlay button' !!
_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);
}
}
似乎您在 build
方法完成构建小部件之前使用 setState
。我建议在构建完成后使用 setState
,这样:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
// do stuff;
});
});
并不是说我是一个真正的 flutter 专业人士,但我认为问题可能出在这里:
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
每次在 MyWorldMap
小部件中调用这些方法之一时,您都会创建一个新状态,我认为这不是正确的行为。
如果你想从另一个 Widget 编辑 Widget 状态,你应该尝试使用键:我认为任何有状态的 Widget 都可以在构造函数中使用 key
参数,可以依次使用它来改变它的来自其他小部件的状态。我会试着写一个简单的例子。
class Parent extends StatelessWidget {
final keyA = GlobalKey();
final keyB = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
ChildA(keyA),
ChildB(keyB, keyA),
]),
);
}
}
class ChildA extends StatefulWidget {
const ChildA(GlobalKey key) : super(key: key);
@override
State<StatefulWidget> createState() => ChildAState();
}
class ChildAState extends State<ChildA> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Text("Child A count: $counter");
}
void increaseCounter(){
setState(() {
counter++;
});
}
}
class ChildB extends StatefulWidget {
final GlobalKey childAKey;
const ChildB(GlobalKey key, this.childAKey) : super(key: key);
@override
State<StatefulWidget> createState() => ChildBState();
}
class ChildBState extends State<ChildB> {
@override
Widget build(BuildContext context) {
return TextButton(
child: const Text("Press here"),
onPressed: () {
(widget.childAKey.currentState as ChildAState).increaseCounter();
},
);
}
}
@il_boga 引导我找到答案后(所有功劳都归功于他),我将 post 工作代码放在这里:
我将 TileOverlay
创建移动到 _MyWorldMapState
class 的 initState
,并添加了缓冲 'layer' 以便我可以切换 on/off 通过在移除时将 _mapTileOverlay
设置为 null 并在添加叠加层时将 _bufferedMapTileOverlay
设置回 _bufferedMapTileOverlay
的图层。
此外,我创建了两个 GlobalKey(实际上我不知道为什么我需要 drawerKey
,因为我从来没有在任何地方主动引用过它...,mapKey
很明显)
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;
const HomePage({Key? key, required this.title}):super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
final drawerKey = GlobalKey();
final mapKey = GlobalKey();
@override
void initState(){
print("_HomePageState(): initState");
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(drawerKey: drawerKey, mapKey: mapKey,),
body: MyWorldMap(mapKey: mapKey,),
);
}
}
class MainDrawer extends StatefulWidget{
final GlobalKey mapKey;
const MainDrawer({required GlobalKey drawerKey, required this.mapKey}) : super(key: drawerKey);
@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: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
//MyWorldMap().addTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
//MyWorldMap().clearTileCache();
(widget.mapKey.currentState as _MyWorldMapState)._clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
//MyWorldMap().removeTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({required GlobalKey mapKey}) : super(key: mapKey);
//addTileOverlay() => createState()._addTileOverlay();
//removeTileOverlay() => createState()._removeTileOverlay();
//clearTileCache() => createState()._clearTileCache();
@override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _bufferedMapTileOverlay; //intermediate, which actually holds the overlay
TileOverlay? _mapTileOverlay; //value which connects to the map
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(){
print("_MyWordMapState(): initState");
super.initState();
final TileOverlay newMapTileOverlay = TileOverlay( //Inits the tileOverlay
tileOverlayId: const TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
_bufferedMapTileOverlay = newMapTileOverlay;
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
@override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{ //connect a set of overlays (here just one)
if(_mapTileOverlay != null) _mapTileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays, //connect to the set of overlays (I have only one (see above))
);
}
void _addTileOverlay()
{
setState((){
_mapTileOverlay = _bufferedMapTileOverlay;
});
}
void _clearTileCache()
{
if(_mapTileOverlay != null){
print("Clearing tile cache");
_mapController.clearTileCache(_mapTileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_mapTileOverlay = 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);
}
}