You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter更新依赖后出现_lifecycleState != defunct断言错误求助

解决Flutter蓝牙打印机页面切换时的_lifecycleState != _ElementLifecycle.defunct崩溃问题

更新旧Flutter项目依赖后,测试蓝牙热敏打印机连接页面时,每次打开或关闭页面都会触发崩溃,报错信息如下:

'package:flutter/src/widgets/framework.dart': Failed assertion: line 5004 pos 12: '_lifecycleState != _ElementLifecycle.defunct': is not true.

页面代码示例:

import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:blue_thermal_printer/blue_thermal_printer.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:ploans_mobile/models/settings/printerSettings.dart';
import 'package:ploans_mobile/services/testPrinter.dart';

class PrinterSettingsPage extends StatefulWidget {
  @override
  _PrinterSettingsPageState createState() => new _PrinterSettingsPageState();
}

class _PrinterSettingsPageState extends State<PrinterSettingsPage> {
  BlueThermalPrinter bluetooth = BlueThermalPrinter.instance;

  List<BluetoothDevice> _devices = [];
  BluetoothDevice? _device;
  bool _connected = false;
  bool _pressed = false;
  String pathImage = '';
  late TestPrint testPrint;

  @override
  void initState() {
    super.initState();
    initPlatformState();
    initSavetoPath();
    testPrint = TestPrint();
  }

  @override
  void dispose() {
    _disconnect();
    _connected = false;
    super.dispose();
  }

  initSavetoPath() async {
    //read and write
    //image max 300px X 300px
    final filename = 'yourlogo.png';
    var bytes = await rootBundle.load("lib/Assets/images/yourlogo.png");
    String dir = (await getApplicationDocumentsDirectory()).path;
    writeToFile(bytes, '$dir/$filename');
    setState(() {
      pathImage = '$dir/$filename';
    });
  }

  void _showToast(String text) {
    Fluttertoast.showToast(
        msg: text, toastLength: Toast.LENGTH_SHORT, timeInSecForIosWeb: 3);
  }

  Future<void> initPlatformState() async {
    List<BluetoothDevice> devices = [];

    try {
      devices = await bluetooth.getBondedDevices();
    } on PlatformException {
      // Ok
      _showToast('Error getting list of devices');
    }

    bluetooth.onStateChanged().listen((state) {
      switch (state) {
        case BlueThermalPrinter.CONNECTED:
          setState(() {
            _connected = true;
            _pressed = false;
          });
          break;
        case BlueThermalPrinter.DISCONNECTED:
          setState(() {
            _connected = false;
            _pressed = false;
          });
          break;
        default:
          print(state);
          break;
      }
    });

    if (!mounted) return;
    setState(() {
      _devices = devices;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        foregroundColor: Colors.white,
        title: Text('Thermal Printer'),
        backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
      ),
      body: Container(
        child: ListView(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Text(
                    'Device:',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  DropdownButton(
                    items: _getDeviceItems(),
                    onChanged: (value) => setState(() {
                      _device = value as BluetoothDevice;
                    }),
                    value: _device,
                  ),
                  ElevatedButton(
                    onPressed: _pressed
                        ? null
                        : _connected
                            ? _disconnect
                            : _connect,
                    child: Text(_connected ? 'Disconnect' : 'Connect'),
                  ),
                ],
              ),
            ),
            Padding(
                padding:
                    const EdgeInsets.only(left: 10.0, right: 10.0, top: 50),
                child: Container(
                  decoration: BoxDecoration(
                    color: Color.fromRGBO(58, 66, 86, 1.0),
                    borderRadius: BorderRadius.only(
                        topRight: Radius.circular(32),
                        bottomRight: Radius.circular(32)),
                  ),
                  height: 55,
                  child: ListTile(
                    contentPadding: EdgeInsets.symmetric(
                      horizontal: 5.0,
                    ),
                    trailing: Container(
                      height: 40,
                      width: 40,
                      decoration: BoxDecoration(
                        color: Color.fromRGBO(253, 253, 253, 1.0),
                        borderRadius: BorderRadius.all(Radius.circular(32)),
                      ),
                      child: Icon(
                        Icons.print,
                        color: Color.fromRGBO(58, 66, 86, 1.0),
                        size: 25,
                      ),
                    ),
                    onTap: () {
                      testPrint.sample(pathImage);
                    },
                  ),
                )),
            Padding(
                padding:
                    const EdgeInsets.only(left: 10.0, right: 10.0, top: 50),
                child: Container(
                  decoration: BoxDecoration(
                    color: Color.fromRGBO(58, 66, 86, 1.0),
                    borderRadius: BorderRadius.only(
                        topRight: Radius.circular(32),
                        bottomRight: Radius.circular(32)),
                  ),
                  height: 55,
                  child: ListTile(
                    contentPadding: EdgeInsets.symmetric(
                      horizontal: 5.0,
                    ),
                    trailing: Container(
                      height: 40,
                      width: 40,
                      decoration: BoxDecoration(
                        color: Color.fromRGBO(253, 253, 253, 1.0),
                        borderRadius: BorderRadius.all(Radius.circular(32)),
                      ),
                      child: Icon(
                        Icons.save,
                        color: Color.fromRGBO(58, 66, 86, 1.0),
                        size: 25,
                      ),
                    ),
                    onTap: () {
                      _submit();
                    },
                  ),
                )),
          ],
        ),
      ),
    );
  }

  List<DropdownMenuItem<BluetoothDevice>> _getDeviceItems() {
    List<DropdownMenuItem<BluetoothDevice>> items = [];
    if (_devices.isEmpty) {
      items.add(DropdownMenuItem(
        child: Text('NONE'),
      ));
    } else {
      _devices.forEach((device) {
        items.add(DropdownMenuItem(
          child: Text(device.name!),
          value: device,
        ));
      });
    }
    return items;
  }

  void _connect() {
    if (_device == null) {
      show('No device selected.');
    } else {
      bluetooth.isConnected.then((isConnected) {
        if (!isConnected!) {
          bluetooth.connect(_device!).catchError((error) {
            setState(() => _pressed = false);
          });
          setState(() => _pressed = true);
        }
      });
    }
  }

  void _disconnect() {
    if (_connected) {
      bluetooth.disconnect();
    }
    setState(() => _pressed = true);
  }

  void _submit() {
    if (_connected == true) {
      var connection = PrinterSettings();
      connection.name = _device!.name!;
      connection.address = _device!.address!;

      connection.savePrinterSettings(connection);
      _disconnect();
      Navigator.of(context).pop();
    } else {
      _showToast("Pleased set a printer first!");
      return null;
    }
  }

  //write to app path
  Future<void> writeToFile(ByteData data, String path) {
    final buffer = data.buffer;
    return new File(path).writeAsBytes(
        buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
  }

  Future show(
    String message, {
    Duration duration = const Duration(seconds: 3),
  }) async {
    await new Future.delayed(new Duration(milliseconds: 100));
    ScaffoldMessenger.of(context).showSnackBar(
      new SnackBar(
        content: new Text(
          message,
          style: new TextStyle(
            color: Colors.white,
          ),
        ),
        duration: duration,
      ),
    );
  }
}

崩溃原因分析

核心问题是页面销毁后,蓝牙状态监听的回调仍触发setState:

  • 在initPlatformState中订阅了bluetooth.onStateChanged(),但未在页面销毁时取消订阅
  • dispose方法仅调用了断开蓝牙,未处理监听的取消
  • 页面关闭时,蓝牙断开的异步回调可能延迟触发,此时State对象已处于defunct状态,无法更新UI

修复步骤

  1. 保存蓝牙状态监听的Subscription对象
    在State类中添加变量:

    StreamSubscription? _bluetoothStateSubscription;
    
  2. 订阅时保存Subscription并添加mounted判断
    修改initPlatformState中的监听逻辑:

    _bluetoothStateSubscription = bluetooth.onStateChanged().listen((state) {
      switch (state) {
        case BlueThermalPrinter.CONNECTED:
          if (mounted) {
            setState(() {
              _connected = true;
              _pressed = false;
            });
          }
          break;
        case BlueThermalPrinter.DISCONNECTED:
          if (mounted) {
            setState(() {
              _connected = false;
              _pressed = false;
            });
          }
          break;
        default:
          print(state);
          break;
      }
    });
    
  3. 在dispose中取消订阅
    更新dispose方法:

    @override
    void dispose() {
      _bluetoothStateSubscription?.cancel();
      _disconnect();
      super.dispose();
    }
    
  4. 全局添加mounted判断
    所有异步回调中的setState都需先检查组件状态:

    // 修改initSavetoPath中的setState
    if (mounted) {
      setState(() {
        pathImage = '$dir/$filename';
      });
    }
    
    // 修改_connect中的错误回调
    bluetooth.connect(_device!).catchError((error) {
      if (mounted) {
        setState(() => _pressed = false);
      }
    });
    
  5. 优化_submit中的导航逻辑
    等待蓝牙断开完成后再执行页面导航:

    void _submit() async {
      if (_connected == true) {
        var connection = PrinterSettings();
        connection.name = _device!.name!;
        connection.address = _device!.address!;
    
        connection.savePrinterSettings(connection);
        await bluetooth.disconnect();
        if (mounted) {
          Navigator.of(context).pop();
        }
      } else {
        _showToast("请先设置打印机!");
      }
    }
    

内容的提问来源于stack exchange,提问作者Fermin Salcedo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:14:57