【问题标题】:Flutter: Dipose HTTP request on close controllerFlutter:在关闭控制器上处理 HTTP 请求
【发布时间】:2021-05-31 09:50:44
【问题描述】:

原答案

我在 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()),
  ];
}

为了防止控制器在释放之前执行操作,我必须创建一个 Stream 并在控制器释放时取消它。

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 应该有更好的方法来做到这一点,没有我做的这些变通办法。如果有人有更好的方法或提示,我愿意接受建议。

【问题讨论】:

    标签: flutter asynchronous flutter-getx


    【解决方案1】:

    GetBuilder + 更新()

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


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

    (在第二个屏幕上,快速单击“返回!”按钮以模拟已释放的 StatefulWidget。)


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

    GetBuilder 将侦听器添加到您传递给它的 Controller,通过 init: 构造函数 arg 或通过 GetBuilder&lt;Type&gt; 参数(如果 Controller 已在其他地方/之前初始化)。

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

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

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

    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!'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              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(),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • 在重构我的代码以仅使用 StatelessWidgets 作为您的示例后,我认为我找出了问题所在。我的“HttpX”控制器使用 Get.lazyPut(HttpX) 放入内存,因为我使用依赖项注入,并且我还需要根据我的架构在另一个控制器中调用此控制器方法。当我将“HttpX”放入内存时,页面被释放后,控制器仍然处于活动状态,导致问题。您对我继续使用依赖注入并仍然处理异步回调有什么建议吗?
    • 你在哪里使用Get.lazyPut?请复制/粘贴代码,以便我查看周围的代码并更好地了解它的使用方式。
    • 我编辑主​​要问题以提供其他详细信息。我解决了这个问题,但我不确定这个解决方案是不是最好的选择。有任何疑问,我随时为您提供帮助。
    【解决方案2】:

    一种解决方案可能是将您的 setState 包装为

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

    【讨论】:

      猜你喜欢
      • 2019-01-17
      • 1970-01-01
      • 1970-01-01
      • 2017-03-22
      • 1970-01-01
      • 1970-01-01
      • 2020-07-27
      • 1970-01-01
      • 2016-01-27
      相关资源
      最近更新 更多