flutter - 我几乎每次都将 Stateless Widget 与 BLoC 一起使用。我错了吗?

标签 flutter timer bloc statefulwidget statelesswidget

我很难理解如何使用 Flutter 处理某些特定情况下的状态。

例如,假设我需要一个页面,单击按钮即可从 API 获取数据。这样的请求可能需要时间或者可能发生任何类型的问题。因此,我可能会使用 BLoC 模式在请求经历各种“状态”时正确通知用户,例如 loadingdonefailed 等等。

现在,假设我有一个页面,它使用 Timer 定期(每 1 秒)用新的 更新 Text 小部件秒表的>耗时值。一旦不再使用计时器,就需要正确停止它(timer.cancel())。因此,我将使用 Stateful Widget 并直接在 dispose 状态下停止计时器。

但是,当一个页面同时满足以下条件时应该怎么做:

  • 从 API 和/或其他需要正确处理状态的服务获取数据。
  • 使用计时器或任何需要正确取消/关闭/处置...的东西(流?)

目前,我有一个进行 API 调用的页面,并且拥有这样一个计时器。该页面使用 BLoC 模式进行处理,并且 Timer 的存在已经使整个事情变得有点乏味。事实上,“仅仅”创建一个 BLoC 来更新 Timer 感觉有点矫枉过正。

但是现在,我遇到了一个问题。如果用户不以“常规”方式浏览页面并决定使用手机的“后退按钮”:我永远无法取消计时器。反过来,这将引发以下错误:未处理的异常:查找已停用的小部件的祖先是不安全的。

事实上,即使在使用 pop() 到上一页之后,Timer 仍在运行并尝试完成其每 1 秒的任务:

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

有人可以向我解释一下这种“特定”情况应该如何处理吗?一定有什么东西,一个我不完全理解的小概念,我能感觉到它有时会减慢我的速度。

提前非常感谢您的帮助。

最佳答案

首先,这是固执己见的。

尽管您已经描述了很多,但跟踪您的案例以及您(具体)如何实现它还是有点棘手。但我会尝试描述一些需要考虑的事情。

有几种方法可以处理这个问题。我会尽力回答您的问题。

  1. 始终或仅拥有无状态小部件和 block 并没有什么问题。
  2. 将有状态小部件和 Bloc 结合起来并没有什么问题。
  3. 考虑页面同时包含 bloc 和 e.g. 的情况。更新该页面上特定文本字段的计时器。为什么一个小部件应该同时处理这两种情况?听起来该页面可能是无状态的(使用 block ),其中包含文本字段,但该文本字段可以/应该是一个单独的 StatefulWidget,仅保存计时器或等效项。这意味着有时人们会在一个巨大的小部件中承担很多责任,而实际上它应该分为几个较小的小部件。
  4. 我不明白为什么你会遇到这个错误,在有状态的小部件中同时拥有 block 和计时器是没有问题的,弹出并使用后退按钮并进行适当的处​​理和重置计时器。请参阅下面的完整代码示例。
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 - 我几乎每次都将 Stateless Widget 与 BLoC 一起使用。我错了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74465273/

相关文章:

dart - Flutter:我应该每页使用 BloC 类(UI/View)吗?

firebase - 最好使用哪种登录方法?

android-studio - Flutter - 如何更改 TextField 边框颜色?

java - 为什么我的 Android 倒计时器这么慢?

java - 如果我们不生成窗口,为什么 Timer 不工作?

javascript - Jquery 设置计数器,但在调用函数时重置

flutter - 使用flutter_bloc时清除文本字段

flutter - 内置在不同文件/类中的访问变量

flutter - Dart:警告 “info: This function has a return type of ' int',但不以return语句结尾”)

尝试从 youtube-search-tutorial 实现 BLoC 时发生 Flutter 错误