FLUTTER:如何完全处置有状态的小部件?

问题描述

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

E/Flutter (6017): [ERROR:Flutter/lib/ui/ui_dart_state.cc(177)] 未处理的异常: setState() 在 dispose() 之后调用: _MapScreenState#79287(lifecycle state: defunct,not installed,股票代码:跟踪0个股票代码) E/Flutter (6017):如果您对不再出现在小部件树中的小部件(例如,其父小部件不再包含其构建中的小部件)的 State 对象调用 setState() ,则会发生此错误。当代码从计时器或动画回调调用 setState() 时,可能会发生此错误。 E/Flutter(6017):首选的解决方案是在dispose()回调中取消定时器或停止监听动画。另一种解决方案是在调用 setState() 之前检查此对象的“mounted”属性,以确保该对象仍在树中。 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,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,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,)
                                : Icon(Icons.search,],)),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,locBusy ? -0.1 : -0.06),child: SvgPicture.asset(
              "assets/images/ccGPS.svg",width: 28,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,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)),);
              }),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 调用或类似的东西时,就会发生这种情况。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...