如果StreamBuilder中的流返回null,该怎么办?

时间:2019-10-18 03:29:55

标签: flutter dart

我正在尝试将Websocket与该软件包websocket一起使用,一切都很好,直到我意识到如果通道第一次无法与服务器连接,流将为空,并且出现此错误:

  

getter'stream'在null上被调用

我将频道设置为Singleton类,因此我可以在应用程序中调用close或在我想要添加的任何位置添加内容:

class WebSocket {
  static IOWebSocketChannel channel;
  static init() async {
    try {
      String macAddress = await getMacAddress();
      channel = IOWebSocketChannel.connect("ws://172.16.0.39:8001/ws/fsmart-door/$macAddress");
    } catch (e) {
      debugPrint(e.toString());
    }
  }
}

我在主目录中调用Websocket.init()以便通道可以进行流传输,但是如果IOWebSocketChannel.connect无法连接,则应用程序也将卡住。

这是我的StreamBuilder:

StreamBuilder(
  stream:  WebSocket.channel.stream,
  builder: (context, snapshot) {
    if (snapshot.hasData) {

    }
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 24.0),
      child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
    );
 },

1 个答案:

答案 0 :(得分:1)

问题是当连接失败时,channelnull,因此您无法进行channel.stream并且StreamBuilder不能拥有Stream 。因此,您可以采取的一种方法是在Stream中放置一个WebSocket来处理重试,并在channel准备就绪时,将来自channel.stream的数据传递给WebSocket Stream

WebSocket可能是这样:

class WebSocket {
  static WebSocket _instance;
  final _streamController = StreamController.broadcast();
  IOWebSocketChannel channel;

  static WebSocket get instance {
    if (_instance == null) {
      _instance = WebSocket();
    }
    return _instance;
  }

  Stream get channelStream => _streamController.stream;

  Future init(int retries) async {
    if (channel == null) {
      await _tryToConnect(retries);
      if (channel != null) {
        _streamController.sink.add(null);
        channel.stream.listen((value) {
          _streamController.sink.add(value);
        });
      } else {
        _streamController.sink.addError("Could not connect");
      }
    }
  }

  Future _tryToConnect(int retries) async {
    if (channel == null) {
      try {
        String macAddress = await getMacAddress();
        // This is to throw exception if it can't connect (https://github.com/dart-lang/web_socket_channel/issues/38#issuecomment-450383558)
        final socket = await WebSocket.connect(
                "ws://172.16.0.39:8001/ws/fsmart-door/$macAddress")
            .timeout(_webSocketConnectionTimeout);
        channel = IOWebSocketChannel(socket);
      } catch (e) {
        debugPrint(e.toString());
      }
      if (channel == null && retries > 0) {
        _streamController.sink.addError("Retries left: ${retries - 1}");
        await _tryToConnect(retries - 1);
      }
    }
  }

  void close() => _streamController.close();
}

这将是StreamBuilder

StreamBuilder(
  stream: WebSocket.instance.channelStream,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('Data: ${snapshot.data}');
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    return Text('Loading...');
  },
)

如果要对此解决方案进行快速测试,请为此替换try catch中的两行:

await Future.delayed(Duration(seconds: 3));
if (retries == 2) channel = IOWebSocketChannel();

使用此IOWebSocketChannel

class IOWebSocketChannel {
  var _streamController = StreamController<String>.broadcast();

  IOWebSocketChannel() {
    init();
  }

  Stream get stream => _streamController.stream;

  Future init() async {
    await Future.delayed(Duration(seconds: 3));
    _streamController.sink.add("1");
    await Future.delayed(Duration(seconds: 3));
    _streamController.sink.add("2");
  }

  void close() => _streamController.close();
}

并在StatefulWidget中使用此示例:

@override
void initState() {
  super.initState();
  WebSocket.instance.init(5);
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: StreamBuilder(
        stream: WebSocket.instance.channelStream,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return Text('Data: ${snapshot.data}');
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          }
          return Text('Loading...');
        },
      ),
    ),
  );
}