我很难理解在一些特定情况下如何处理Flutter 的状态.

例如,我需要一个页面,在该页面中,只需点击一个按钮即可从API中获取数据.这样的请求可能需要时间,也可能会发生任何类型的问题.出于这个原因,当请求经历各种"状态"(如loadingdonefailed等)时,我可能会使用阻塞模式来适当地通知用户.

Now,假设我有一个使用Timer的页面定期(每隔1秒)用Stopwatch的新elapsed time值更新Text小部件.一旦定时器不再使用,则需要适当地停止(timer.cancel())定时器.出于这个原因,我将使用Stateful小部件并将计时器直接停止在dispose状态.

However,当他们有一个页面既有以下内容时,应该怎么办:

  • 从需要正确处理状态的API和/或其他服务获取数据.
  • 使用Timer或任何需要适当取消/关闭/处置...的内容(STREAMS?)

Currently,我有一个进行API调用的页面,also包含这样的Timer.页面是以块模式处理的,Timer的存在已经让整个过程有点乏味了.事实上,创建一个"仅仅"更新Timer的区块感觉有点矫枉过正.

我正面临着一个问题.如果用户不按"常规"方式浏览页面,而决定使用手机的"后退键":我永远不会取消Timer.这又将抛出以下错误:Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.

事实上,即使在前一页有pop()页之后,Timer仍然在运行,并试图完成每1秒的任务:

Timer.periodic(
      const Duration(seconds: 1),
      (Timer t) {
        context.read<TimerBloc>().add(const Update());
      },
    );
  }

有没有人可以向我解释一下,这种"特定"的情况应该如何处理?一定有一些东西,一个我不完全理解的很小的概念,我有时能感觉到它在拖慢我的脚步.

事先非常感谢您的帮助.

推荐答案

首先,这是固执己见的.

尽管您已经描述了很多,但要了解您的 case 以及您是如何(具体地)实现它的,还是有些棘手.但我会试着描述一下需要考虑的事情.

有几种方法可以处理这一问题.我会试着回答你们的问题.

  1. 始终拥有或仅拥有无状态的小部件和区块并没有什么错.
  2. 组合有状态窗口小部件和区块并没有什么错.
  3. 考虑具有阻止和例如更新该页面上的特定文本字段的定时器的页面的情况.为什么一个小部件要同时处理这两个问题?听起来页面可能是无状态的(使用块),其中包含文本字段,但该文本字段可以/应该是一个仅保存计时器的单独的StatefulWidget,或等效项.这意味着有时人们把很多责任放在一个巨大的小部件上,而实际上它应该被分成几个较小的小部件.
  4. 我不明白你为什么会面临这个错误,在一个有状态的小部件中同时拥有一个块和一个计时器是没有问题的,通过适当的处理和重置计时器来弹出和使用Back按钮.请参见下面的完整代码示例.
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const FirstPage(),
    );
  }
}

class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('First page'),
      ),
      body: Center(
        child: ElevatedButton(
            onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => BlocProvider<FetcherCubit>(
                    create: (context) => FetcherCubit(),
                    child: const SecondPage(),
                  ),
                )),
            child: const Text('Second page')),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  const SecondPage({super.key});

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  late final Timer myTimer;
  int value = 0;

  @override
  void initState() {
    super.initState();
    myTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        value++;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Timer value: ${value.toString()}'),
            ElevatedButton(
              onPressed: () => context.read<FetcherCubit>().fetch(),
              child: const Text('Fetch!'),
            ),
            BlocBuilder<FetcherCubit, FetcherState>(
              builder: (context, state) {
                late final String text;
                if (state is FetcherInitial) {
                  text = 'Initial';
                } else if (state is FetcherLoading) {
                  text = 'Loading';
                } else {
                  text = 'Completed';
                }
                return Text(text);
              },
            )
          ],
        ),
      ),
    );
  }
}

class FetcherCubit extends Cubit<FetcherState> {
  FetcherCubit() : super(FetcherInitial());

  Future<void> fetch() async {
    emit(FetcherLoading());
    await Future.delayed(const Duration(seconds: 3));
    emit(FetcherCompleted());
  }
}

@immutable
abstract class FetcherState {}

class FetcherInitial extends FetcherState {}

class FetcherLoading extends FetcherState {}

class FetcherCompleted extends FetcherState {}

如果你建造它,结果是:

enter image description here

Flutter相关问答推荐

如何在Flutter 屏幕中添加箭头形状

如何在Firebase项目中更改包名称和包ID

在Android Studio的新用户界面中未找到热重新加载

在flutter web上渲染多个Youtube视频

FittedBox叠加技术在Flutter 中的应用

如何在点击查看措辞下方添加句子?

Flutter - Riverpod 2.0 - 如何访问 onDispose 回调?

Flutter Riverpod 没有捕获 List 的嵌套值变化

如何修改来自合并 list 的 Android list 权限?

使用Flutter项目设置Firebase遇到困难?这些解决方案或许能帮到你!

文本溢出文本框 - Flutter

Firestore 使用 Flutter 以奇怪的格式保存数据

Flutter 如何要求用户在应用程序中设置电话密码?

如何防止卡子在底部溢出?

Flutter:如何将列表转换为字符串?

如何将变量翻译成其他语言

在 FAB 上添加表格小部件单击 FLUTTER

我们什么时候在flutter中初始化一个provider?

Firebase Firestore 查询

Flutter Firebase 可以在真实设备上运行,而不是在 Android 模拟器上运行