如何使用 BLoC 以编程方式关闭 CupertinoActionSheet?

How to close the CupertinoActionSheet programatically with BLoC?

我正在构建一个扫描二维码的 2FA 应用程序。然后它解析 QR 码中的 uri 和 returns 一个 totp 代码。但是,我在使用 Navigator.of(context).pop().

关闭 CupertinoActionSheet 时遇到问题

我希望它像那样工作:

  1. 当用户点击“添加”按钮时,会显示操作 Sheet。
  2. 当用户点击“扫描二维码”时,操作 Sheet 必须关闭然后继续扫描。

它是这样的:

这是当我有以下代码时出现的错误:“查找已停用的小部件的祖先是不安全的。 此时小部件的元素树的状态不再稳定。 要在其 dispose() 方法中安全地引用小部件的祖先,请通过在小部件的 didChangeDependencies() 方法中调用 dependOnInheritedWidgetOfExactType() 来保存对祖先的引用。"

代码:

home_screen.dart

import 'dart:io';

import 'package:duckie/blocs/manual_input/manual_input_bloc.dart';
import 'package:duckie/blocs/qr_code_scanner/qr_code_scanner_bloc.dart';
import 'package:duckie/screens/manual_input/manual_input_screen.dart';
import 'package:duckie/screens/widgets/alert_dialog.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';

import 'package:duckie/shared/text_styles.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'title',
          style: TextStyles.appBarText,
        ).tr(),
        centerTitle: false,
        elevation: 0.0,
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
              if (Platform.isAndroid) {
                showAndroidModalBottomSheet(context);
              } else if (Platform.isIOS) {
                showIosActionSheet(context);
              }
            },
          ),
        ],
      ),
      body: BlocConsumer<QrCodeScannerBloc, QrCodeScannerState>(
        listener: (context, state) {
          if (state is QrCodeScannerError) {
            Platform.isAndroid
                ? CustomAlertDialog.showAndroidAlertDialog(context,
                    state.alertDialogErrorTitle, state.alertDialogErrorContent)
                : CustomAlertDialog.showIosAlertDialog(context,
                    state.alertDialogErrorTitle, state.alertDialogErrorContent);
          }
        },
        builder: (context, state) {
          if (state is QrCodeScannerFinal) {
            return Column(
              children: [
                Text(state.accountName),
                Text(state.issuer),
                Text(state.otp),
              ],
            );
          }
          return Container();
        },
      ),
    );
  }
}

void showAndroidModalBottomSheet(BuildContext context) {
  showModalBottomSheet(
    context: context,
    builder: (BuildContext context) {
      return ListView(
        shrinkWrap: true,
        children: [
          ListTile(
            onTap: () {},
            leading: Icon(Icons.qr_code),
            title: Text('scan-qr-code').tr(),
          ),
          ListTile(
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).pushNamed('/manual-input');
            },
            leading: Icon(Icons.keyboard),
            title: Text('manual-input').tr(),
          ),
          ListTile(
            onTap: () {
              Navigator.of(context).pop();
            },
            leading: Icon(Icons.cancel),
            title: Text('cancel').tr(),
          )
        ],
      );
    },
  );
}

void showIosActionSheet(BuildContext context) {
  showCupertinoModalPopup(
    context: context,
    builder: (BuildContext context) {
      return CupertinoActionSheet(
        title: Text('action-sheet-title').tr(),
        message: Text('action-sheet-message').tr(),
        actions: [
          CupertinoActionSheetAction(
            onPressed: () async {
              Navigator.pop(context);

              final String qrCodeResponse =
                  await FlutterBarcodeScanner.scanBarcode(
                      '#FF6666', 'cancel'.tr(), true, ScanMode.QR);

              BlocProvider.of<QrCodeScannerBloc>(context).add(
                GetQrCodeResponseEvent(qrCodeResponse),
              );

              // final String uri =
              //     'otpauth://totp/Karol%27s%20Nextcloud%3Aszakes1%40drive.karolzientek.tech?secret=KVZKSQ74SSAVCR27&issuer=Karol%27s%20Nextcloud';
            },
            child: Text('scan-qr-code').tr(),
          ),
          CupertinoActionSheetAction(
            onPressed: () {
              Navigator.of(context).pop();
              Navigator.of(context).pushNamed('/manual-input');
            },
            child: Text('manual-input').tr(),
          ),
          CupertinoActionSheetAction(
            isDestructiveAction: true,
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: Text('cancel').tr(),
          )
        ],
      );
    },
  );
}

我认为问题出在这段代码中:

onPressed: () async {
  Navigator.pop(context);

  final String qrCodeResponse =
      await FlutterBarcodeScanner.scanBarcode(
          '#FF6666', 'cancel'.tr(), true, ScanMode.QR);

  BlocProvider.of<QrCodeScannerBloc>(context).add(
    GetQrCodeResponseEvent(qrCodeResponse),
  );
  …
},

你正在做的是:

  1. 从导航堆栈中弹出 CupertinoActionSheet。
  2. 做一些异步计算。
  3. 使用context访问QrCodeScannerBloc。

但是,在 1 之后,context(== 支持 CupertinoActionSheet 的元素)不再处于活动状态 - 它已被处理掉。以后无法使用它,因为它与树分离。

要避免这种情况,您可以先存储对 QrCodeScannerBloc 的引用,然后再使用它,如下所示:

onPressed: () async {
  final qrCodeScannerBloc = BlocProvider.of<QrCodeScannerBloc>(context);

  Navigator.pop(context);

  final String qrCodeResponse =
      await FlutterBarcodeScanner.scanBarcode(
          '#FF6666', 'cancel'.tr(), true, ScanMode.QR);

  qrCodeScannerBloc.add(GetQrCodeResponseEvent(qrCodeResponse));
  …
},