flutter - 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);
});
}
}
你能帮我解决这个错误吗?
非常感谢。
解决方案
您的错误来自这里:
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();
}
推荐阅读
- angular - Angular [scrollTop] 导致不需要的行为
- css - 我可以删除 Buefy-Bulma 布局组件中的填充吗?
- c - 在堆栈和堆上分配内存的 Eratosthenes 筛的内存错误
- sql-server - 使用 CTE 进行递归查询所需的帮助/建议
- c# - 如何从继承的 FromBody 模型中获得正确的类型?
- javascript - 我可以使用 fs 中的 createReadStream() 和 base64 图像而不是路径吗?
- ag-grid - Ag-Grid:使用 css 添加列标题图标,破坏排序功能
- c# - c# 到 Node.js - 刷新命名管道写入缓冲区不会结束流
- python - 如何使用 Sympy (Python) 在 for 循环中生成符号变量
- unity3d - Unity Streaming-assets 内容未在 Oculus Quest/Go 中读取