FLUTTER: как полностью избавиться от виджета с отслеживанием состояния?

На самом деле это рабочая карта Google с поиском места в Google. Проблема заключается в удалении этого виджета с отслеживанием состояния, я получаю эту ошибку:

E / flutter (6017): [ОШИБКА: flutter / lib / ui / ui_dart_state.cc (177)] Необработанное исключение: setState () вызывается после dispose (): _MapScreenState # 79287 (состояние жизненного цикла: не функционирует, не смонтировано, тикеры: отслеживание 0 тикеров) E / flutter (6017): эта ошибка возникает, если вы вызываете setState () для объекта State для виджета, который больше не отображается в дереве виджетов (например, чей родительский виджет больше не включает виджет в свою сборку). Эта ошибка может возникать, когда код вызывает 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();
  }
}

person Jang Delos Santos    schedule 23.02.2021    source источник


Ответы (1)


Эта ошибка означает, что вы вызываете setState (() {}), когда виджет больше не находится в дереве.

Я не проверял весь ваш код, чтобы понять, в чем именно проблема, но вы можете добавить это перед вызовом setState (() {}):

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

Обычно это происходит, когда есть какие-то Futures, слушатели, http-вызовы или что-то подобное, которые у вас есть.

person leb1755    schedule 23.02.2021
comment
Вау, это сработало! Большое спасибо! - person Jang Delos Santos; 24.02.2021