Flutter:setState() 在 dispose() 错误后调用

Flutter: setState() called after dispose() error

这实际上是一个带有 google 地点搜索功能的 Google 地图。问题是在处理这个有状态的小部件时,我收到了这个错误:

E/flutter (6017): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] 未处理的异常:setState() 在 dispose() 之后调用:_MapScreenState#79287(生命周期状态:defunct,不是已安装,代码:跟踪 0 个代码) E/flutter (6017):如果您为不再出现在小部件树中的小部件(例如,其父小部件不再在其构建中包含该小部件)的 State 对象调用 setState(),则会发生此错误。当代码从计时器或动画回调调用 setState() 时,可能会发生此错误。 E/flutter ( 6017 ): 首选方案是在dispose()回调中取消定时器或者停止监听动画。另一种解决方案是在调用 setState() 之前检查此对象的“已安装”属性,以确保该对象仍在树中。 E/flutter (6017):如果正在调用 setState(),则此错误可能表示内存泄漏,因为另一个对象在从树中删除后保留对此 State 对象的引用。为避免内存泄漏,请考虑在 dispose() 期间中断对该对象的引用。

有人可以帮我确定我错过了什么吗?顺便说一句,这是我的代码片段:

import 'package:CaterChive/model/configKey.dart';
import 'package:CaterChive/services/hiveController.dart';
import 'package:CaterChive/style/textStyles.dart';
import 'package:CaterChive/view/homeScreen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:geocoder/geocoder.dart';
import 'package:geocoder/model.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:hive/hive.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:search_map_place_v2/search_map_place_v2.dart';

import 'package:http/http.dart' as http;
import 'dart:convert';

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> with TickerProviderStateMixin {
  GoogleMapController _mapController;
  bool isMapCreated = false;
  String address = Hive.box('userLocation').get(1).userAddress;
  double mapLatitude = Hive.box('userLocation').get(1).addressLatitude;
  double mapLongitude = Hive.box('userLocation').get(1).addressLongitude;

  String selectedAddress;

  bool _hasClearButton = true;

  LatLng mapLocation = LatLng(Hive.box('userLocation').get(1).addressLatitude,
      Hive.box('userLocation').get(1).addressLongitude);

  int mapRadius = 30000;
  bool strictBounds = false;
  PlaceType mapPlaceType = PlaceType.address;
  String language = 'en';

  final _textEditingController = TextEditingController();
  AnimationController _animationController;
  // SearchContainer height.
  // Animation _containerHeight;
  // Place options opacity.
  Animation _listOpacity;

  List<dynamic> _placePredictions = [];
  bool _isEditing = false;
  Geocoding geocode;

  String _tempInput = '';
  String _currentInput = '';

  final _fn = FocusNode();

  CrossFadeState _crossFadeState;

  bool locBusy = false;
  LatLng newLocation;
  String newLocationText;

  mapStyle() {
    getJsonFile('assets/json/mapStyle.json').then(setMapStyle);
  }

  Future<String> getJsonFile(String path) async {
    return await rootBundle.loadString(path);
  }

  void setMapStyle(String mapStyle) {
    _mapController.setMapStyle(mapStyle);
  }

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

  void fetchData() {
    print(mapLatitude);
    print(mapLongitude);
    if (isMapCreated) {
      mapStyle();
    }

    geocode = Geocoding(apiKey: mapKey, language: 'en');
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
    // _containerHeight = Tween<double>(begin: 0, end: 364).animate(
    //   CurvedAnimation(
    //     curve: const Interval(0.0, 0.5, curve: Curves.easeInOut),
    //     parent: _animationController,
    //   ),
    // );
    _listOpacity = Tween<double>(
      begin: 0,
      end: 1,
    ).animate(
      CurvedAnimation(
        curve: const Interval(0.5, 1.0, curve: Curves.easeInOut),
        parent: _animationController,
      ),
    );

    _textEditingController.addListener(_autocompletePlace);
    customListener();

    if (_hasClearButton) {
      _fn.addListener(() async {
        if (_fn.hasFocus) {
          setState(() => _crossFadeState = CrossFadeState.showSecond);
        } else {
          setState(() => _crossFadeState = CrossFadeState.showFirst);
        }
      });
      _crossFadeState = CrossFadeState.showFirst;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      resizeToAvoidBottomPadding: false,
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Color(0xff009245),
        title: Row(
          children: [
            GestureDetector(
                onTap: () {
                  Get.off(HomeScreen());
                },
                child: Icon(Icons.arrow_back)),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15.0),
                child: Container(
                    height: 40,
                    decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius:
                            const BorderRadius.all(Radius.circular(6.0)),
                        boxShadow: [
                          const BoxShadow(
                              color: Colors.black12,
                              blurRadius: 20,
                              spreadRadius: 10)
                        ]),
                    child: Row(
                      children: [
                        Expanded(
                          child: TextField(
                            style: TextStyle(fontWeight: FontWeight.bold),
                            onTap: _textEditingController.text ==
                                    'Selected Location'
                                ? () {
                                    _textEditingController.clear();
                                  }
                                : () {},
                            controller: _textEditingController,
                            onSubmitted: (_) => _selectPlace(),
                            onEditingComplete: _selectPlace,
                            autofocus: false,
                            focusNode: _fn,
                            decoration: InputDecoration(
                                hintText: Hive.box('userCredentials')
                                            .get(1)
                                            .acctType ==
                                        1
                                    ? 'Set Delivery Address'
                                    : 'Set Location',
                                border: InputBorder.none,
                                contentPadding:
                                    const EdgeInsets.fromLTRB(10, 0, 0, 5),
                                hintStyle: TextStyle(
                                    color: Colors.grey[500],
                                    fontWeight: FontWeight.normal)),
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 8.0),
                          child: Container(
                            child: _hasClearButton
                                ? GestureDetector(
                                    onTap: () {
                                      if (_crossFadeState ==
                                          CrossFadeState.showSecond) {
                                        _textEditingController.clear();
                                      }
                                    },
                                    // child: Icon(_inputIcon, color: this.widget.iconColor),
                                    child: AnimatedCrossFade(
                                      crossFadeState: _crossFadeState,
                                      duration:
                                          const Duration(milliseconds: 300),
                                      firstChild: Icon(Icons.search,
                                          color: Color(0xff009245)),
                                      secondChild: Icon(Icons.clear,
                                          color: Color(0xff009245)),
                                    ),
                                  )
                                : Icon(Icons.search, color: Color(0xff009245)),
                          ),
                        ),
                      ],
                    )),
              ),
            ),
            GestureDetector(
                onTap: () {
                  checkLocationPermission();
                  print('tapped gps');
                },
                child: Icon(Icons.gps_fixed)),
          ],
        ),
      ),
      body: Stack(
        children: [
          GestureDetector(
            onTap: () {
              print('hello');
            },
            child: Container(
              height: double.infinity,
              width: double.infinity,
              child: GoogleMap(
                onCameraMoveStarted: _onCameraMoveStarted,
                onCameraMove: _onCameraMove,
                onCameraIdle: _onCameraIdle,
                onTap: _mapTap,
                onLongPress: _mapLongPress,
                zoomControlsEnabled: false,
                onMapCreated: (GoogleMapController googleMapController) {
                  _mapController = googleMapController;
                  isMapCreated = true;
                  mapStyle();
                  setState(() {});
                },
                initialCameraPosition: CameraPosition(
                    zoom: 18.0, target: LatLng(mapLatitude, mapLongitude)),
              ),
            ),
          ),

          Container(
            alignment: Alignment(0, 0),
            child: SvgPicture.asset(
              "assets/images/ccGPSshadow.svg",
              width: locBusy ? 15 : 23,
              fit: BoxFit.cover,
              alignment: Alignment.center,
            ),
          ),

          Container(
            alignment: Alignment(0, locBusy ? -0.1 : -0.06),
            child: SvgPicture.asset(
              "assets/images/ccGPS.svg",
              fit: BoxFit.cover,
              width: 28,
              alignment: Alignment.center,
            ),
          ),

          AnimatedBuilder(
              animation: _animationController,
              builder: (context, _) {
                return Container(
                  height: 65.0 * _placePredictions.length,
                  alignment: Alignment(0, -1),
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: const BorderRadius.only(
                          bottomLeft: Radius.circular(6.0),
                          bottomRight: Radius.circular(6.0)),
                      boxShadow: [
                        const BoxShadow(
                            color: Colors.black12,
                            blurRadius: 20,
                            spreadRadius: 10)
                      ]),
                  child: Column(
                    children: <Widget>[
                      // Padding(
                      //   padding: const EdgeInsets.only(
                      //       left: 12.0, right: 12.0, top: 4),
                      //   child: child,
                      // ),
                      // if (_placePredictions.isEmpty)
                      Opacity(
                        opacity: _listOpacity.value,
                        child: Column(
                          children: <Widget>[
                            for (var prediction in _placePredictions)
                              _placeOption(Place.fromJSON(prediction, geocode)),
                          ],
                        ),
                      ),
                    ],
                  ),
                );
              }),

          Container(
            alignment: Alignment(0, 0.9),
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: MaterialButton(
                minWidth: double.infinity,
                onPressed: locBusy
                    ? () {}
                    : () async {
                        if (mapLocation.isNullOrBlank) {
                          print('empty');
                          Get.off(HomeScreen());
                        } else {
                          // convert coordinates to valid address string
                          final coordinates = Coordinates(
                              mapLocation.latitude, mapLocation.longitude);

                          var addresses = await Geocoder.local
                              .findAddressesFromCoordinates(coordinates);
                          address = addresses.first.addressLine;
                          print(address);
                          await HiveController().putLocationOnHive(address,
                              mapLocation.latitude, mapLocation.longitude);
                          Get.off(HomeScreen());
                        }
                      },
                child: Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Text(
                      locBusy ? 'Loading...' : 'Find Nearby Services',
                      style: lblBtn,
                    )),
                color: locBusy ? Colors.grey : Color(0xff009245),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
            ),
          ),

          // Container(
          //   alignment: Alignment(0, -0.9),
          //   child: Padding(
          //     padding: const EdgeInsets.only(top: 15),
          //     child: Column(
          //       children: [
          //         SearchMapPlaceWidget(
          //           apiKey: mapKey,
          //           hasClearButton: true,
          //           location: LatLng(mapLatitude, mapLongitude),
          //           placeType: PlaceType.address,
          //           radius: 30000,
          //           placeholder: 'Set delivery address',
          //           onSelected: (Place place) async {
          // Geolocation geolocation = await place.geolocation;
          // _mapController.animateCamera(
          //     CameraUpdate.newLatLng(geolocation.coordinates));
          // _mapController.animateCamera(
          //     CameraUpdate.newLatLngBounds(geolocation.bounds, 0));

          // print(place.description);

          // var newLoc = await Geocoder.local
          //     .findAddressesFromQuery(place.description);
          // print(geolocation.coordinates);

          // mapLatitude = newLoc.first.coordinates.latitude;
          // mapLongitude = newLoc.first.coordinates.longitude;

          // print(mapLatitude);
          // print(mapLongitude);
          //           },
          //         ),
          //         FlatButton(
          //           child: Text(
          //             "Use Location",
          //             style: TextStyle(color: Colors.white),
          //           ),
          //           onPressed: () {
          //             print('Hi');
          //           },
          //         ),
          //       ],
          //     ),
          //   ),
          // ),
        ],
      ),
    );
  }

  /*
  WIDGETS
  */

  Widget _placeOption(Place prediction) {
    final place = prediction.description;

    return MaterialButton(
      padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
      onPressed: () => _selectPlace(prediction: prediction),
      child: ListTile(
        title: Text(
          place.length < 45
              ? '$place'
              : '${place.replaceRange(45, place.length, "")} ...',
          style: TextStyle(
            fontSize: MediaQuery.of(context).size.width * 0.04,
            // color: widget.darkMode ? Colors.grey[100] : Colors.grey[850],
          ),
          maxLines: 1,
        ),
        contentPadding: const EdgeInsets.symmetric(
          horizontal: 10,
          vertical: 0,
        ),
      ),
    );
  }

  /*
  METHODS
  */

  /// Will be called everytime the input changes. Making callbacks to the Places
  /// Api and giving the user Place options

  void _onCameraMoveStarted() {
    _fn.unfocus();
    _closeSearch();
    locBusy = true;
  }

  void _onCameraMove(CameraPosition position) {
    _closeSearch();
    newLocation = position.target;
    locBusy = true;

    _textEditingController.text = 'Selected Location';
    _fn.unfocus();
  }

  void _onCameraIdle() {
    if (newLocationText != '') _textEditingController.text = newLocationText;
    mapLocation = newLocation;
    print(mapLocation);
    locBusy = false;
    newLocationText = '';
  }

  void _mapTap(LatLng latlng) {
    _fn.unfocus();
    _closeSearch();
    print(latlng);

    _mapController.animateCamera(CameraUpdate.newLatLng(latlng));
    // _mapController.animateCamera(CameraUpdate.newLatLngBounds(
    //     LatLngBounds(
    //       southwest: latlng,
    //       northeast: latlng,
    //     ),
    //     100));
  }

  void _mapLongPress(LatLng latlng) {
    _fn.unfocus();
    _closeSearch();
    print(latlng);
  }

  void _autocompletePlace() async {
    if (_fn.hasFocus) {
      setState(() {
        _currentInput = _textEditingController.text;
        _isEditing = true;
      });

      _textEditingController.removeListener(_autocompletePlace);

      if (_currentInput.isEmpty) {
        // if (_currentInput != "Selected Location") _closeSearch();
        _textEditingController.addListener(_autocompletePlace);
        return;
      }

      if (_currentInput == _tempInput) {
        final predictions = await _makeRequest(_currentInput);
        await _animationController.animateTo(0.5);
        setState(() => _placePredictions = predictions);
        await _animationController.forward();

        _textEditingController.addListener(_autocompletePlace);
        return;
      }

      Future.delayed(const Duration(milliseconds: 500), () {
        _textEditingController.addListener(_autocompletePlace);
        if (_isEditing == true) _autocompletePlace();
      });
    }
  }

  Future<void> checkLocationPermission() async {
    var locationPermission = Permission.location;

    if (await locationPermission.request().isGranted) {
      _getCurrentLocation();
    } else if (await locationPermission.request().isDenied) {
      Fluttertoast.showToast(
        gravity: ToastGravity.CENTER,
        msg: "Location Permission Denied",
      );
    } else if (await locationPermission.request().isPermanentlyDenied) {
      Fluttertoast.showToast(
        gravity: ToastGravity.CENTER,
        msg:
            "Location Permission Permanently Denied, Cannot Request Permission",
      );
    } else {
      Fluttertoast.showToast(
        gravity: ToastGravity.CENTER,
        msg: "Please check location permission",
      );
    }
  }

  void _getCurrentLocation() async {
    // GoogleMapController _mapController;
    // Position position;

    // Position res = await Geolocator.getCurrentPosition();

    // position = res;

    Position geoposition = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);

    if (await Permission.locationWhenInUse.serviceStatus.isEnabled) {
      print('GPS enabled');

      print(geoposition.latitude);
      print(geoposition.longitude);

      final coordinates =
          Coordinates(geoposition.latitude, geoposition.longitude);

      print(coordinates);

      var addresses =
          await Geocoder.local.findAddressesFromCoordinates(coordinates);

      selectedAddress = addresses.first.addressLine;
      mapLatitude = geoposition.latitude;
      mapLongitude = geoposition.longitude;

      newLocationText = selectedAddress;

      _mapController.animateCamera(CameraUpdate.newLatLng(
          LatLng(geoposition.latitude, geoposition.longitude)));
      // _mapController.animateCamera(CameraUpdate.newLatLngBounds(
      //     LatLngBounds(
      //       southwest: LatLng(
      //           geoposition.latitude - 0.0002, geoposition.longitude - 0.0002),
      //       northeast: LatLng(
      //           geoposition.latitude + 0.0002, geoposition.longitude + 0.0002),
      //     ),
      //     100));

      // readOnly.value = false;
      // tapped.value = true;
    } else {
      Fluttertoast.showToast(
        gravity: ToastGravity.CENTER,
        msg: "Please turn on your GPS",
      );
    }
  }

  /// API request function. Returns the Predictions
  Future<dynamic> _makeRequest(input) async {
    var url =
        // ignore: unnecessary_brace_in_string_interps
        'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&key=${mapKey}&language=${language}';
    if (mapLocation != null && mapRadius != null) {
      url +=
          // ignore: unnecessary_brace_in_string_interps
          '&location=${mapLocation.latitude},${mapLocation.longitude}&radius=${mapRadius}';
      if (strictBounds) {
        url += '&strictbounds';
      }
      if (mapPlaceType != null) {
        url += '&types=${mapPlaceType.apiString}';
      }
    }

    final response = await http.get(url);
    final json = jsonDecode(response.body);

    if (json['error_message'] != null) {
      var error = json['error_message'];
      if (error == 'This API project is not authorized to use this API.') {
        error +=
            ' Make sure the Places API is activated on your Google Cloud Platform';
      }

      throw Exception(error);
    } else {
      final predictions = json['predictions'];
      return predictions;
    }
  }

  /// Will be called when a user selects one of the Place options
  void _selectPlace({Place prediction}) async {
    if (prediction != null) {
      // _textEditingController.value = TextEditingValue(
      //   text: prediction.description,
      //   selection: TextSelection.collapsed(
      //     offset: prediction.description.length,
      //   ),
      // );
      newLocationText = prediction.description;
    } else {
      await Future.delayed(const Duration(milliseconds: 500));
    }

    // Makes animation
    _closeSearch();

    // Calls the `onSelected` callback
    if (prediction is Place) onSelected(prediction);
  }

  onSelected(Place place) async {
    Geolocation geolocation = await place.geolocation;
    _mapController
        .animateCamera(CameraUpdate.newLatLng(geolocation.coordinates));
    // _mapController
    //     .animateCamera(CameraUpdate.newLatLngBounds(geolocation.bounds, 0));

    print(place.description);

    var newLoc = await Geocoder.local.findAddressesFromQuery(place.description);
    print(geolocation.coordinates);

    mapLatitude = newLoc.first.coordinates.latitude;
    mapLongitude = newLoc.first.coordinates.longitude;

    print(mapLatitude);
    print(mapLongitude);
  }

  /// Closes the expanded search box with predictions
  void _closeSearch() async {
    if (!_animationController.isDismissed) {
      await _animationController.animateTo(0.5);
    }

    _fn.unfocus();
    setState(() {
      _placePredictions = [];
      _isEditing = false;
    });
    await _animationController.reverse();
    _textEditingController.addListener(_autocompletePlace);
  }

  /// Will listen for input changes every 0.5 seconds, allowing us to make API requests only when the user stops typing.
  void customListener() {
    if (!mounted) {
      return;
    }
    Future.delayed(const Duration(milliseconds: 500), () {
      setState(() => _tempInput = _textEditingController.text);
      customListener();
    });
  }

  @override
  void dispose() {
    _mapController.dispose();
    _animationController.dispose();
    _textEditingController.dispose();
    _fn.dispose();
    super.dispose();
  }
}

此错误意味着当小部件不再在树中时您正在调用 setState((){})。

我没有检查你的整个代码来确定问题出在哪里,但你可以在调用 setState((){}) 之前添加它:

if(mounted){
  // mounted returns true only if the widget is in the tree
  setState((){
    // do your stuff
  });
}

通常,当您有一些 Futures、侦听器、http 调用或类似的东西时,就会发生这种情况。