Flutter:在关闭控制器上处理 HTTP 请求 GetBuilder + 更新()

如何解决Flutter:在关闭控制器上处理 HTTP 请求 GetBuilder + 更新()

原始答案

我在 Flutter 上使用 Getx 状态管理。

尽可能简化:

我构建了一个 GetxController 来控制我的页面,并且在这个控制器中,我有一个 StatefulWidget 实例来激发 http 请求。

class MyController extends GetxController {
  Player player;
}   

class Player extends StatefulWidget {
  PlayerState state;

  @override
  PlayerState createState() {
    state = PlayerState();
    return state;
  }
}

class PlayerState extends State<Player> {
  void methodName async() {
    futureRequest().then((data) {
      // when the error ocurrs
      setState(() {});
    });
  }
}

当用户在请求结束之前关闭移动页面,触发控制器的 close 方法时,就会出现问题。

这样,当 setState 被触发时,页面实例就没有了,错误就发生了。

我认为解决方案是在调用控制器关闭方法时中断与此 GetxController 相关的所有请求并“删除”此 StatefulWidget 实例。

我不知道这是否正确,也不知道该怎么做..

================================================ ====================

更新答案

主要问题是 getDetails() 方法中的异步请求,即使在控制器被释放后也返回响应,甚至使用 GetBuilder,并且此响应携带来自启动视频的 url通过 videoPlayerController(一个 video_player 插件实例)。

因此,用户在另一个屏幕上,但继续收听在后台播放的视频。

作为将良好实践应用于代码的解决方法和思考,我进行了重构以仅使用无状态小部件,遵循 GetX 规则。我解决了这个问题,但我不得不将 Future's 转换为 Stream's

正在使用 Get.lazyPut() 创建绑定以执行依赖项注入:

class Binding implements Bindings {
  Get.lazyPut<PlayerController>(() {
    return PlayerController(videoRepository: VideoRepository(VideoProvider(Dio())));
  });
}

此绑定链接到页面路由器,基于 GetX 文档。

class AppPages {
  static final routes = [
    GetPage(name: Routes.MyRoute,page: () => MyPage(),binding: MyBinding()),];
}

为了防止控制器在处理之前进行操作,我必须创建一个流并在控制器处理时取消它。

class MyController extends GetxController {
  
  MyController({@required this.repository}) : assert(repository != null);
  StreamSubscription<bool> stream;
  // Instance of plugin video_player 
  VideoPlayerController videoPlayerController;

  @override
  void onClose() {
    if (streamGetVideo != null) streamGetVideo.cancel();
    super.onClose();
    if (videoPlayerController != null) videoPlayerController?.dispose();
  }

  // This is the method called by the user on screen
  void loadVideo() {
    stream = getDetails().asStream().listen((bool response) {
      // This code is canceled on onClose() method by the stream
      if (response) update();
    });
  }

  Future<bool> getDetails() async {
    return await repository.getDetails().then((data) async {
      videoPlayerController = VideoPlayerController.network(data);
      initFuture = videoPlayerController.initialize();
      await initFuture.whenComplete(() { return true; });
    });
  }
}

我认为 Flutter/GetX 应该有更好的方法来做到这一点,而不需要我做的这些变通方法。如果有人有更好的方法或提示,我愿意接受建议。

解决方法

一种解决方案可能是将您的 setState 包裹起来

    if(mounted){
          setState(() {});
    }
,

GetBuilder + 更新()

GetX 中使用 GetBuilderupdate() 负责生命周期检查/处理,因此您不必这样做。


以下是在 HTTP 调用完成和调用 setState() 之前关闭屏幕/路由的示例,没有抛出异常。

(在第二个屏幕上,快速点击 Go Back! 按钮来模拟一个已经释放的 StatefulWidget。)


下面,update() 调用用于更新屏幕,而不是 setState(),但它们在 GetBuilder 中是相同的。 GetBuilder 是(扩展)一个 StatefulWidget。

GetBuilder 通过 init: 构造函数 arg 或 GetBuilder<Type> 参数(如果 Controller 在别处/更早的时候初始化)向您传递它的 Controller 添加侦听器。

如果 StatefulWidget(即 GetBuilder)被释放,该监听器将被释放。

(请参阅 GetBuilderdispose() 函数以了解一些魔法。添加侦听器时,添加该侦听器的返回值是一个处理/取消订阅该侦听的函数。非常聪明。)

因此,如果该小部件已被释放,则 GetBuilder/StatefulWidget 将永远不会调用其 update() / setState(),因为这些调用的侦听器已被释放。因此,缓慢返回的 HTTP 调用不会尝试更新/设置小部件树中不再存在的小部件。

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

class HttpX extends GetxController {

  String slowValue = 'loading...';

  @override
  void onInit() {
    slowCall();
  }

  /// Simulate a slow,long running HTTP call
  Future<void> slowCall() async {
    slowValue = 'Slow call STARTED!';
    print(slowValue);
    update(); // update the screen to show started message
    await Future.delayed(Duration(seconds: 5),() {
      slowValue = 'Slow call FINISHED!';
      print(slowValue);
      update(); // won't call setState() if GetBuilder is disposed
    });
  }
}

class GetXDisposePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Dispose'),),body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,children: [
            Text('awaiting http call to finish'),RaisedButton(
              child: Text('Go Call Page'),onPressed: () => Get.to(SlowCallPage()),// using Get.to ↑ requires GetMaterialApp in place of MaterialApp in MyApp
            )
          ],);
  }
}

class SlowCallPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Dispose - Go Back!'),children: [
            GetBuilder<HttpX>(
              init: HttpX(),// fake slow http call starts on init
              builder: (hx) => Text(hx.slowValue),RaisedButton(
              child: Text('Go Back!'),onPressed: () => Get.back(),],);
  }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-