0

原始答案

我在 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 也会返回响应,并且该响应带有来自 videoPlayerController(video_player 插件实例)启动的视频的 url。

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

作为一种解决方法并考虑将良好实践应用于代码,我按照 GetX 规则进行了重构以仅使用无状态小部件。我解决了这个问题,但我必须将Future转换为Stream

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

4

2 回答 2

0

一种解决方案可能是将您的 setState 与

    if(mounted){
          setState(() {});
    }
于 2021-03-01T21:03:26.273 回答
0

GetBuilder + 更新()

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


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

(在第 2 个屏幕上,快速单击 Go Back! 按钮以模拟已部署的 StatefulWidget。)


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

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

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

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

update() / setState()因此,如果该小部件已被释放,则 GetBuilder/StatefulWidget 将永远不会被调用,因为这些调用的侦听器已被释放。因此,缓慢返回的 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(),
            ),
          ],
        ),
      ),
    );
  }
}
于 2021-03-01T22:18:13.663 回答