首页 > 解决方案 > Flutter - 未处理的异常:在 dispose() 之后调用 setState()

问题描述

我试图为颤振应用程序制作指南针。

这个想法是将指南针的图像放在相机预览上。

结果似乎还可以:

首先,用户使用指南针预览相机:

指南针在相机预览

可以添加房间名称。

当用户拍照时,它会制作一个可以共享的屏幕截图:

在此处输入图像描述

即使它似乎工作正常,我在 Android Studio 上也有以下错误消息:

    E/flutter (29454): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: setState() called after dispose(): _CompassState#46249(lifecycle state: defunct, not mounted)
E/flutter (29454): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (29454): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter (29454): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
E/flutter (29454): #0      State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1204:9)
E/flutter (29454): #1      State.setState (package:flutter/src/widgets/framework.dart:1239:6)
E/flutter (29454): #2      _CompassState._onData (package:franck_ehrhart/seller/compass.dart:28:29)
E/flutter (29454): #3      _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter (29454): #4      _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (29454): #5      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (29454): #6      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
E/flutter (29454): #7      _DelayedData.perform (dart:async/stream_impl.dart:611:14)
E/flutter (29454): #8      _StreamImplEvents.handleNext (dart:async/stream_impl.dart:730:11)
E/flutter (29454): #9      _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:687:7)
E/flutter (29454): #10     _rootRun (dart:async/zone.dart:1182:47)
E/flutter (29454): #11     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (29454): #12     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (29454): #13     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (29454): #14     _rootRun (dart:async/zone.dart:1190:13)
E/flutter (29454): #15     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter (29454): #16     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter (29454): #17     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter (29454): #18     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter (29454): #19     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter (29454): 

这是指南针的源代码:

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_compass/flutter_compass.dart';



class Compass extends StatefulWidget {

  Compass({Key key}) : super(key: key);

  @override
  _CompassState createState() => _CompassState();
}

class _CompassState extends State<Compass> {

  double _heading = 0;

  String get _readout => _heading.toStringAsFixed(0) + '°';

  @override
  void initState() {

    super.initState();
    FlutterCompass.events.listen(_onData);
  }

  void _onData(double x) => setState(() { _heading = x; });

  final TextStyle _style = TextStyle(
    color: Colors.red,
    fontSize: 32,
    fontWeight: FontWeight.bold,
    backgroundColor: Colors.white.withOpacity(0.5) ,
  );

  @override
  Widget build(BuildContext context) {

    return CustomPaint(
        foregroundPainter: CompassPainter(angle: _heading),
        child: Center(child: Text(_readout, style: _style))
    );
  }
}

class CompassPainter extends CustomPainter {

  CompassPainter({ @required this.angle }) : super();

  final double angle;
  double get rotation => -2 * pi * (angle / 360);

  Paint get _brush => new Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = 4.0;

  @override
  void paint(Canvas canvas, Size size) {



    Paint circle = _brush
      ..color = Colors.blue.shade800.withOpacity(0.6);

    Paint needle = _brush
      ..color = Colors.lightBlueAccent;

    double radius = min(size.width / 2.2, size.height / 2.2);
    Offset center = Offset(size.width / 2, size.height / 2);
    Offset start = Offset.lerp(Offset(center.dx, radius), center, .4);
    Offset end = Offset.lerp(Offset(center.dx, radius), center, -2);

    canvas.translate(center.dx, center.dy);
    canvas.rotate(rotation);
    canvas.translate(-center.dx, -center.dy);
    canvas.drawLine(start, end, needle);
    canvas.drawCircle(center, radius, circle);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

这是显示相机预览和指南针的页面的代码: 指南针在相机预览

import 'package:camera/camera.dart';
import 'package:flutter/cupertino.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import'package:franck_ehrhart/camera/preview_compass.dart';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:image_picker_saver/image_picker_saver.dart';
import 'package:flutter/services.dart';

class CompassTool extends StatefulWidget {

  // Screenshot
  CompassTool({Key key}) : super(key: key);

  @override
  _CompassToolState createState() => _CompassToolState();
}

class _CompassToolState extends State<CompassTool> {

  // Screenshot
  static GlobalKey screen = new GlobalKey();

  // Camera

  CameraController controller;
  List cameras;
  int selectedCameraIndex;
  String imgPath;
  String compassPath;



  // Textinput name of room

  TextEditingController nameController = TextEditingController();
  String roomName = '';

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

    // Camera

    availableCameras().then((availableCameras) {
      cameras = availableCameras;

      if (cameras.length > 0) {
        setState(() {
          selectedCameraIndex = 0;
        });
        _initCameraController(cameras[selectedCameraIndex]).then((void v) {});
      } else {
        print('No camera available');
      }
    }).catchError((err) {
      print('Error :${err.code}Error message : ${err.message}');
    });

  }

  Future _initCameraController(CameraDescription cameraDescription) async {
    if (controller != null) {
      await controller.dispose();
    }
    controller = CameraController(cameraDescription, ResolutionPreset.medium);

    controller.addListener(() {
      if (mounted) {
        setState(() {});
      }

      if (controller.value.hasError) {
        print('Camera error ${controller.value.errorDescription}');
      }
    });

    try {
      await controller.initialize();
    } on CameraException catch (e) {
      _showCameraException(e);
    }
    if (mounted) {
      setState(() {});
    }
  }




  @override
  Widget build(BuildContext context) {

    return Scaffold(
      backgroundColor: Colors.blueAccent,
      appBar: AppBar(
        title: Text("ORIENTATION PIECES"),
        centerTitle: true,
        backgroundColor: Colors.blueAccent.shade700,
      ),
      //   body: Compass()
      body:

          Container(

          child: SafeArea(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[

                  Expanded(
                    flex: 1,
                    child:
                    _cameraPreviewWidget(),
                  ),

                  Align(
                    alignment: Alignment.bottomCenter,
                    child: Container(
                      height: 80,
                      width: double.infinity,
                      padding: EdgeInsets.all(15),
                      color: Colors.blueAccent,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: <Widget>[
                          _cameraToggleRowWidget(),
                          _cameraControlWidget(context),
                          Spacer()
                        ],
                      ),
                    ),
                  ),
                  Card(
                    margin: EdgeInsets.symmetric( horizontal: 10.0, vertical :5.0),
                    child:  TextField(
                      controller: nameController,
                      decoration: InputDecoration(
                        hintText: 'Nom de la pièce',
                        border: OutlineInputBorder(),
                        labelText: 'Nom de la pièce',
                      ),
                      onChanged: (text) {
                        setState(() {
                          roomName = text;
                          //you can access nameController in its scope to get
                          // the value of text entered as shown below
                          //fullName = nameController.text;
                        }
                        );
                      },
                    ),
                  ),
                ]
            ),
          ),
        ),

    );


  }

  /// Display the control bar with buttons to take pictures
  Widget _cameraControlWidget(context) {
    return Expanded(
      child: Align(
        alignment: Alignment.center,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            FloatingActionButton(
              child: Icon(
                Icons.camera,
                color: Colors.black,
              ),
              backgroundColor: Colors.white,
              onPressed: () {
                _onCapturePressed(context);
              },
            ),
          ],
        ),
      ),
    );
  }

  /// Display Camera preview.
  Widget _cameraPreviewWidget() {
    if (controller == null || !controller.value.isInitialized) {
      return const Text(
        'Loading',
        style: TextStyle(
          color: Colors.white,
          fontSize: 20.0,
          fontWeight: FontWeight.w900,
        ),
      );
    }
    return
      //screenshot
      RepaintBoundary(
        key: screen,
     child: Stack(
      alignment: FractionalOffset.center,
      children: <Widget>[
        new Positioned.fill(
          child: new AspectRatio(
              aspectRatio: controller.value.aspectRatio,
              child: new CameraPreview(controller)),
        ),
        new Positioned.fill(
            child: Compass(
            )
        ),
      ],
    ),
      );
  }





  /// Display a row of toggle to select the camera (or a message if no camera is available).
  Widget _cameraToggleRowWidget() {
    if (cameras == null || cameras.isEmpty) {
      return Spacer();
    }
    CameraDescription selectedCamera = cameras[selectedCameraIndex];
    CameraLensDirection lensDirection = selectedCamera.lensDirection;

    return Expanded(
      child: Align(
        alignment: Alignment.centerLeft,
        child: FlatButton.icon(
          onPressed: _onSwitchCamera,
          icon: Icon(
            _getCameraLensIcon(lensDirection),
            color: Colors.white,
            size: 24,
          ),
          label: Text(
            '${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1).toUpperCase()}',
            style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.w500
            ),),
        ),
      ),
    );
  }

  // Camera

  IconData _getCameraLensIcon(CameraLensDirection direction) {
    switch (direction) {
      case CameraLensDirection.back:
        return CupertinoIcons.switch_camera;
      case CameraLensDirection.front:
        return CupertinoIcons.switch_camera_solid;
      case CameraLensDirection.external:
        return Icons.camera;
      default:
        return Icons.device_unknown;
    }
  }

  void _showCameraException(CameraException e) {
    String errorText = 'Error:${e.code}\nError message : ${e.description}';
    print(errorText);
  }

  void _onCapturePressed(context) async {
    try {
      // Compass
      RenderRepaintBoundary boundary = screen.currentContext.findRenderObject();
      ui.Image image = await boundary.toImage();
      ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
      final compassPath =
      await ImagePickerSaver.saveFile(
          fileData:byteData.buffer.asUint8List() );
      // Camera
      final path =
      join((await getTemporaryDirectory()).path, '${DateTime.now()}.png');
      await controller.takePicture(path);

      Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => PreviewCompass(
              imgPath: path,
              compassPath: compassPath,
              roomName: roomName,
            )
        ),
      );

    } catch (e) {
      _showCameraException(e);
    }
  }


  void _onSwitchCamera() {
    selectedCameraIndex =
    selectedCameraIndex < cameras.length - 1 ? selectedCameraIndex + 1 : 0;
    CameraDescription selectedCamera = cameras[selectedCameraIndex];
    _initCameraController(selectedCamera);
  }




} 

以下是显示前一屏幕截图的页面代码:

在此处输入图像描述

import 'dart:io';
import 'dart:typed_data';

import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

// packages screenshot
import 'package:flutter/rendering.dart';
import 'package:screenshot/screenshot.dart';
import 'package:path_provider/path_provider.dart';

import 'package:flutter_icons/flutter_icons.dart';

// Compass



class PreviewCompass extends StatefulWidget{
  final String imgPath;
  final String compassPath;
  final String roomName;

  const PreviewCompass({Key key, this.imgPath, this.compassPath, this.roomName}) : super(key: key);

  //PreviewCompass({this.imgPath,this.roomName});


  @override
  _PreviewCompassState createState() => _PreviewCompassState();

}
class _PreviewCompassState extends State<PreviewCompass>{

  // Screenshot

  File _imageFile;
  ScreenshotController screenshotController = ScreenshotController();





  @override
  Widget build(BuildContext context) {
    return Screenshot(
      controller: screenshotController,
      child:
      Scaffold(
        backgroundColor: Colors.blueAccent,
        appBar: AppBar(
          automaticallyImplyLeading: true,
          title: Text("ORIENTATION PIECE"),
          centerTitle: true,
          backgroundColor: Colors.blueAccent.shade700,
        ),
        body: Container(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
            Expanded(
            flex: 2,
        child: Stack(
          alignment: FractionalOffset.center,
          children: <Widget>[

        new Positioned.fill(child:
        Image.file(File(widget.imgPath),fit: BoxFit.fill,),),

            new Positioned.fill(child:
            Image.file(File(widget.compassPath),fit: BoxFit.fill,),),
              /*
              Expanded(
               // flex: 1,
                child:
                Image.file(File(widget.imgPath),fit: BoxFit.fill,),
              ),

              Align(
               // alignment: Alignment.bottomCenter,
                child:  Image.file(File(widget.compassPath),fit: BoxFit.fill,),
              ),*/
],
          ),
            ),
              Card(
                  margin: EdgeInsets.symmetric( horizontal: 10.0, vertical :5.0),
                  child: ListTile (
                    leading: Icon(
                      FontAwesome.compass,
                      color: Colors.blue.shade900,
                    ),
                    title: (
                        Text (widget?.roomName,
                            style: TextStyle(
                              fontSize: 15.0,
                              fontFamily: 'Source Sans Pro',
                              color: Colors.blue.shade900,
                            )
                        )
                    ),
                  )
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: Container(
                  width: double.infinity,
                  height: 60.0,
                  color: Colors.black,
                  child: Center(
                    child: IconButton(
                      icon: Icon(Icons.share,color: Colors.white,),
                      onPressed: () async {
                        _takeScreenshotandShare();
                      },
                    ),
                  ),
                ),
              ),

            ],
          ),
        ),
      ),
    );
  }


  // Screenshot

  _takeScreenshotandShare() async {
    _imageFile = null;
    screenshotController
        .capture(delay: Duration(milliseconds: 10), pixelRatio: 2.0)
        .then((File image) async {
      setState(() {
        _imageFile = image;
      });
      final directory = (await getApplicationDocumentsDirectory()).path;
      Uint8List pngBytes = _imageFile.readAsBytesSync();
      File imgFile = new File('$directory/screenshot.png');
      imgFile.writeAsBytes(pngBytes);
      print("File Saved to Gallery");
      await Share.file('Screenshot', 'screenshot.png', pngBytes, 'image/png');
    })
        .catchError((onError) {
      print(onError);
    });
  }



}

你能帮我解决这个错误吗?

非常感谢。

标签: flutter

解决方案


您的错误来自这里:

FlutterCompass.events.listen(_onData);

您应该将该流订阅存储在一个变量中并在 dispose 方法中取消它:


  StreamSubscription<double> stream;

  @override
  void initState() {

    super.initState();
    stream = FlutterCompass.events.listen(_onData);
  }

  @override
  void dispose() {
    stream.cancel();
    super.dispose();
  }


推荐阅读