简体   繁体   English

Flutter有状态的小部件状态未初始化

[英]Flutter Stateful Widget State not Initializing

I'm making a command and control application using Flutter, and have come across an odd problem. 我正在使用Flutter开发一个命令和控制应用程序,但遇到了一个奇怪的问题。 The main status page of the app shows a list of stateful widgets, which each own a WebSocket connection that streams state data from a connected robotic platform. 该应用程序的主要状态页面显示了有状态窗口小部件的列表,每个状态窗口小部件都具有一个WebSocket连接,该连接可从已连接的机器人平台流传输状态数据。 This worked well when the robots themselves were hardcoded in. However now that I'm adding them dynamically (via barcode scans), only the first widget is showing status. 当机器人本身被硬编码时,此方法效果很好。但是,由于我现在是动态添加它们(通过条形码扫描)的,因此只有第一个小部件显示状态。

Further investigation using the debugger shows that this is due to the fact that a state is only getting created for the first widget in the list. 使用调试器进行的进一步研究表明,这是由于针对列表中的第一个窗口小部件创建了一个状态。 Subsequently added widgets are successfully getting constructed, but are not getting a state. 随后添加的窗口小部件越来越成功构建,但没有得到一个状态。 Meaning that createState is not getting called for anything other than the very first widget added. 这意味着除了添加的第一个小部件外,不会调用createState进行任何其他操作。 I checked that the widgets themselves are indeed being added to the list and that they each have unique hash codes. 我检查了小部件本身是否确实已添加到列表中,并且它们每个都有唯一的哈希码。 Also, the IOWebSocketChannel's have unique hash codes, and all widget data is correct and unique for the different elements in the list. 另外,IOWebSocketChannel具有唯一的哈希码,并且所有小部件数据都是正确的,并且对于列表中的不同元素是唯一的。

Any ideas as to what could be causing this problem? 关于什么可能导致此问题的任何想法?

Code for the HomePageState: HomePageState的代码:

class HomePageState extends State<HomePage> {
  String submittedString = "";
  StateContainerState container;
  List<RobotSummary> robotList = [];
  List<String> robotIps = [];
  final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();

  void addRobotToList(String ipAddress) {
    var channel = new IOWebSocketChannel.connect('ws://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort);
    channel.sink.add("http://" + ipAddress);
    var newConnection = new RobotSummary(key: new UniqueKey(), channel: channel, ipAddress: ipAddress, state: -1, fullAddress: 'http://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort,);
    scaffoldKey.currentState.showSnackBar(new SnackBar(
      content: new Text("Adding robot..."), duration: Duration(seconds: 2),));
    setState(() {
      robotList.add(newConnection);
      robotIps.add(ipAddress);
      submittedString = ipAddress;
    });
  }

  void _onSubmit(String val) {

    // Determine the scan data that was entered
    if(Validator.isIP(val)) {
      if(ModalRoute.of(context).settings.name == '/') {
        if (!robotIps.contains(val)) {
          addRobotToList(val);
        }
        else {
          scaffoldKey.currentState.showSnackBar(new SnackBar(
            content: new Text("Robot already added..."), duration: Duration(seconds: 5),));
        }
      }
      else {
        setState(() {
          _showSnackbar("Robot scanned. Go to page?", '/');
        });
      }
    }
    else if(Validator.isSlotId(val)) {
      setState(() {
        _showSnackbar("Slot scanned. Go to page?", '/slots');
      });
    }
    else if(Validator.isUPC(val)) {
      setState(() {
        _showSnackbar("Product scanned. Go to page?", '/products');
      });
    }
    else if (Validator.isToteId(val)) {

    }
  }

  @override
  Widget build(BuildContext context) {
    container = StateContainer.of(context);
    return new Scaffold (
      key: scaffoldKey,
      drawer: Drawer(
        child: CategoryRoute(),
      ),
      appBar: AppBar(
        title: Text(widget.topText),  
      ),
      bottomNavigationBar: BottomAppBar(
        child: new Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
            IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
          ],
        ),
      ),
      body: robotList.length > 0 ? ListView(children: robotList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: Colors.blue),),),
    );
  }

  void _showModalSheet() {
    showModalBottomSheet(
        context: context,
        builder: (builder) {
          return _searchBar(context);
        });
  }

  void _showSnackbar(String message, String route) {
    scaffoldKey.currentState.showSnackBar(new SnackBar(
      content: new Text(message),
      action: SnackBarAction(
        label: 'Go?', 
        onPressed: () {
          if (route == '/') {
            Navigator.popUntil(context,ModalRoute.withName('/'));
          }
          else {
            Navigator.of(context).pushNamed(route); 
          }
        },),
      duration: Duration(seconds: 5),));
  }

  Widget _searchBar(BuildContext context) {
    return new Scaffold(
      body: Container(
      height: 75.0,
      color: iam_blue,
      child: Center(
      child: TextField(
        style: TextStyle (color: Colors.white, fontSize: 18.0),
        autofocus: true,
        keyboardType: TextInputType.number,
        onSubmitted: (String submittedStr) {
          Navigator.pop(context);
          _onSubmit(submittedStr);
        },
        decoration: new InputDecoration(
        border: InputBorder.none,
        hintText: 'Scan a tote, robot, UPC, or slot',
        hintStyle: TextStyle(color: Colors.white70),
        icon: const Icon(Icons.search, color: Colors.white70,)),
      ),
    )));
  }

  Future scan() async {
    try {
      String barcode = await BarcodeScanner.scan();
      setState(() => this._onSubmit(barcode));
    } on PlatformException catch (e) {
      if (e.code == BarcodeScanner.CameraAccessDenied) {
        setState(() {
          print('The user did not grant the camera permission!');
        });
      } else {
        setState(() => print('Unknown error: $e'));
      }
    } on FormatException{
      setState(() => print('null (User returned using the "back"-button before scanning anything. Result)'));
    } catch (e) {
      setState(() => print('Unknown error: $e'));
    }
  }
}

Code snippet for the RobotSummary class: RobotSummary类的代码段:

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:test_app/genericStateSummary_static.dart';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:test_app/StateDecodeJsonFull.dart';
import 'dart:async';
import 'package:test_app/dataValidation.dart';

class RobotSummary extends StatefulWidget {
  final String ipAddress;
  final String _port = '5000';
  final int state;
  final String fullAddress;
  final WebSocketChannel channel;

  RobotSummary({
    Key key,
    @required this.ipAddress,
    @required this.channel,
    this.state = -1,
    this.fullAddress = "http://10.1.10.200:5000",
  }) :  assert(Validator.isIP(ipAddress)),
        super(key: key);

  @override
  _RobotSummaryState createState() => new _RobotSummaryState();
}

class _RobotSummaryState extends State<RobotSummary> {
  StreamController<StateDecodeJsonFull> streamController;

  @override
  void initState() {
    super.initState();
    streamController = StreamController.broadcast();
  }

  @override
  Widget build(BuildContext context) {

    return new Padding(
      padding: const EdgeInsets.all(20.0),
      child: new StreamBuilder(
        stream: widget.channel.stream,
        builder: (context, snapshot) {
          //streamController.sink.add('{"autonomyControllerState" : 3,  "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82,   "pickUpcCode" : "00814638", "robotName" : "Adam"}');
          return getStateWidget(snapshot);
        },
      ),
    );
  }

  @override
  void dispose() {
    streamController.sink.close();
    super.dispose();
  }
}

Based on what Jacob said in his initial comments, I came up with a solution that works and is a combination of his suggestions. 根据雅各布在最初的评论中所说的,我想出了一个可行的解决方案,是他的建议的结合。 The code solution he proposed above can't be implemented (see my comment), but perhaps a modification can be attempted that takes elements of it. 他上面提出的代码解决方案无法实现(请参阅我的评论),但是也许可以尝试进行修改以使其具有要素。 For the solution I'm working with now, the builder call for HomePageState becomes as follows: 对于我现在使用的解决方案,对HomePageState的构建器调用如下所示:

Widget build(BuildContext context) {
    List<RobotSummary> tempList = [];
    if (robotList.length > 0) {
      tempList.addAll(robotList);
    }
    container = StateContainer.of(context);
    return new Scaffold (
      key: scaffoldKey,
      drawer: Drawer(
        child: CategoryRoute(),
      ),
      appBar: AppBar(
        title: Text(widget.topText),  
      ),
      bottomNavigationBar: BottomAppBar(
        child: new Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
            IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
          ],
        ),
      ),
      body: robotList.length > 0 ? ListView(children: tempList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: iam_blue),),),
    );
  }

The problem is you are holding on to the StatefulWidget s between build calls, so their state is always the same. 问题在于您在build调用之间坚持使用StatefulWidget ,因此它们的状态始终相同。 Try separating RobotSummary business logic from the view logic. 尝试将RobotSummary业务逻辑与视图逻辑分开。 Something like 就像是

class RobotSummary {
  final String ipAddress;
  final String _port = '5000';
  final int state;
  final String fullAddress;
  final WebSocketChannel channel;
  StreamController<StateDecodeJsonFull> streamController;

  RobotSummary({
    @required this.ipAddress,
    @required this.channel,
    this.state = -1,
    this.fullAddress = "http://10.1.10.200:5000",
  }) :  assert(Validator.isIP(ipAddress));

  void init() => streamController = StreamController.broadcast();
  void dispose() => streamController.sink.close();
}

And then in your Scaffold body: 然后在您的脚手架体内:

...

body: ListView.builder(itemCount: robotList.length, itemBuilder: _buildItem)

...

Widget _buildItem(BuildContext context, int index) {
  return new Padding(
      padding: const EdgeInsets.all(20.0),
      child: new StreamBuilder(
        stream: robotList[index].channel.stream,
        builder: (context, snapshot) {
          //streamController.sink.add('{"autonomyControllerState" : 3,  "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82,   "pickUpcCode" : "00814638", "robotName" : "Adam"}');
          return getStateWidget(snapshot); // not sure how to change this.
        },
      ),
    );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM