Flatter Stateful Widget State не инициализируется - PullRequest
0 голосов
/ 27 июня 2018

Я создаю приложение для командования и управления, используя Flutter, и столкнулся со странной проблемой. На главной странице состояния приложения отображается список виджетов с отслеживанием состояния, каждый из которых имеет соединение WebSocket, которое передает данные о состоянии с подключенной роботизированной платформы. Это работало хорошо, когда сами роботы были жестко запрограммированы. Однако теперь, когда я добавляю их динамически (посредством сканирования штрих-кода), только первый виджет показывает статус.

Дальнейшие исследования с использованием отладчика показывают, что это связано с тем, что создается состояние only для первого виджета в списке. Впоследствии добавленные виджеты успешно создаются, но не получают состояние. Это означает, что createState не вызывается ни для чего, кроме самого первого добавленного виджета. Я проверил, что сами виджеты действительно добавляются в список и что у каждого из них есть уникальные хэш-коды. Кроме того, у IOWebSocketChannel есть уникальные хеш-коды, и все данные виджета являются правильными и уникальными для различных элементов в списке.

Есть идеи о том, что может быть причиной этой проблемы?

Код для 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'));
    }
  }
}

Фрагмент кода для класса 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();
  }
}

Ответы [ 2 ]

0 голосов
/ 27 июня 2018

Основываясь на том, что сказал Джейкоб в своих первоначальных комментариях, я нашел решение, которое работает и представляет собой комбинацию его предложений. Кодовое решение, которое он предложил выше, не может быть реализовано (см. Мой комментарий), но, возможно, можно попробовать модификацию, которая использует его элементы. Для решения, с которым я сейчас работаю, вызов конструктора для 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),),),
    );
  }
0 голосов
/ 27 июня 2018

Проблема в том, что вы удерживаете StatefulWidget s между build вызовами, поэтому их состояние всегда одинаково. Попробуйте отделить RobotSummary бизнес-логику от логики представления. Что-то вроде

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();
}

А потом в вашем теле Эшафот:

...

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.
        },
      ),
    );
}
...