Flutter connecting with the STOMP client as many time as I reload - flutter

I'm using Flutter stomp_dart_client.
Everything works fine but the problem arises whenever I either hot reload or hot restart my program where if I press hot reload, the program will connect with my stomp client twice, if I hot reload/restart again, the program will connect with stomp client thrice and it just keeps adding up as many time as I reload.
My code:
StreamController streamController = StreamController();
var _listMessage = <String>[];
String? name;
void getName(String getName) {
name = getName;
}
String subDestination = "/topic/return-to";
void onConnect(StompFrame frame) {
print(frame.headers);
stompClient.subscribe(
destination: subDestination,
callback: (frame) {
Map<String, dynamic>? result = json.decode(frame.body!);
print(result);
print("Hi loser");
_listMessage.add(result?["name"] + " : " + result?["content"]);
streamController.sink.add(_listMessage);
},
);
stompClient.send(
destination: "/app/userData", body: json.encode({"name": name}));
}
final stompClient = StompClient(
config: StompConfig(
heartbeatOutgoing: Duration(microseconds: 1000),
url: "ws://localhost:9192/server",
onConnect: onConnect,
beforeConnect: () async {
print('waiting to connect...');
await Future.delayed(Duration(milliseconds: 200));
print('connecting...');
},
onWebSocketError: (dynamic error) =>
print("hot mess") //print(error.toString()),
),
);
List getMessages() {
return _listMessage;
}
class ChatApp extends StatefulWidget {
String name;
ChatApp({Key? key, required this.name}) : super(key: key);
// const ChatApp({Key? key}) : super(key: key);
#override
State<ChatApp> createState() => _ChatAppState();
}
class _ChatAppState extends State<ChatApp> {
TextEditingController msgController = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
getName(widget.name);
stompClient.activate();
streamController.add(getMessages());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Expanded(
child: StreamBuilder(
stream: streamController.stream,
builder: (context, snapshot){
if(snapshot.hasData){
var listMessage = snapshot.data as List<String>;
return ListView.builder(
itemCount: listMessage.length,
itemBuilder: (context, index){
List splitList = (listMessage[index].split(":"));
if(splitList[0].trim() == widget.name){
return Align(
alignment: Alignment.centerRight,
child: Text(
listMessage[index],
style: TextStyle(
color: Colors.blue,
),
),
);
}else {
return Text(listMessage[index]);
}
}
);
}
return CircularProgressIndicator();
}),flex: 5,
),
Expanded(
flex: 5,
child: Row(
children: [
SizedBox(
width: 200,
child: TextField(
controller: msgController,
decoration: InputDecoration(
hintText: "Chill",
),
),
),
IconButton(onPressed: (){
stompClient.send(destination: "/app/message",
body: json.encode({"content" : msgController.text,
"name" : widget.name})
);
},
icon: Icon(Icons.send))
],
),
)
],
),
),
);
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
stompClient.deactivate();
}
}
I tried connecting with my STOMP Client, it connects but when hot reloading, it end up connecting as many times as I hot restart/reload.

Related

Composing region changed by the framework. Restarting the input method in Flutter

First time I'm able to send the message and get the response but second time it's not giving me the response as showing error as Composing region changed by the framework. Restarting the input method. Also sometimes not even the single time showing me the response.
It is working on the web perfectly but the issue on the we is it is giving me response once in the first time, twice in second time, thrice in third time.
import 'chatmessage.dart';
import 'threedots.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
#override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final TextEditingController _controller = TextEditingController();
final List<ChatMessage> _messages = [];
Chat? chat;
bool _isImageSearch = false;
StreamSubscription? _subscription;
bool _isTyping = false;
#override
void initState() {
super.initState();
chat = Chat.instance.builder(
"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",
);
}
#override
void dispose() {
chat!.genImgClose();
_subscription?.cancel();
super.dispose();
}
void _sendMessage() {
if (_controller.text.isEmpty) return;
ChatMessage message = ChatMessage(
text: _controller.text,
sender: "Me",
isImage: false,
);
setState(() {
_messages.insert(0, message);
_isTyping = true;
});
_controller.clear();
if (_isImageSearch) {
final request = GenerateImage(message.text, 1, size: "256x256");
_subscription = chat!
.generateImageStream(request)
.asBroadcastStream()
.listen((response) {
Vx.log(response.data!.last!.url!);
insertNewData(response.data!.last!.url!, isImage: true);
});
} else {
final request = CompleteReq(
prompt: message.text, model: kTranslateModelV3, max_tokens: 200);
_subscription = chat!
.onCompleteStream(request: request)
.asBroadcastStream()
.listen((response) {
Vx.log(response!.choices[0].text);
insertNewData(response.choices[0].text, isImage: false);
});
}
}
void insertNewData(String response, {bool isImage = false}) {
ChatMessage botMessage = ChatMessage(
text: response,
sender: "AI Bot",
isImage: isImage,
);
setState(() {
_isTyping = false;
_messages.insert(0, botMessage);
});
}
Widget _buildTextComposer() {
return Row(
children: [
Expanded(
child: TextField(
controller: _controller,
onSubmitted: (value) => _sendMessage(),
decoration: const InputDecoration.collapsed(
hintText: "Question/Description"),
),
),
ButtonBar(
children: [
IconButton(
icon: const Icon(Icons.send),
onPressed: () {
_isImageSearch = false;
_sendMessage();
},
),
// TextButton(
// onPressed: () {
// _isImageSearch = true;
// _sendMessage();
// },
// child: const Text("Generate Image"))
],
),
],
).px16();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
"Welcome To AI Bot",
textAlign: TextAlign.center,
),
backgroundColor: Color.fromARGB(202, 245, 235, 104),
),
body: SafeArea(
child: Column(
children: [
Flexible(
child: ListView.builder(
reverse: true,
padding: Vx.m8,
itemCount: _messages.length,
itemBuilder: (context, index) {
return _messages[index];
},
)),
if (_isTyping) const ThreeDots(),
const Divider(
height: 1.0,
),
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.lightGreen,
),
child: _buildTextComposer(),
)
],
),
));
}
}

local state variable not updating

For some weird reason, my local state variable "_jobApplicationState" is not updating.
I see that it is updated in the database, but its not updating on my page.
If I leave the record and come back, everything works as expected.
I am driving this functionality by pressing the button 'Send inquiry'.
I took out a bunch of code to make it easy to read.
I got this to work for a minute at somepoint. but I forgot to save:(
class JobApplicationView extends StatefulWidget {
const JobApplicationView({Key? key}) : super(key: key);
#override
_JobApplicationViewState createState() => _JobApplicationViewState();
}
// https://youtu.be/VPvVD8t02U8?t=90350
class _JobApplicationViewState extends State<JobApplicationView> {
CloudJobApplication? _jobApplication;
final _formKey = GlobalKey<FormState>();
final currentUser = AuthService.firebase().currentUser!;
late final FirebaseCloudStorage _firebaseService;
//
late String _jobApplicationState;
//
late DateTime _jobApplicationStartDate;
late DateTime _jobApplicationEndDate;
//
bool? isJobCreatorSameAsJobApplicator;
String? _jobCreatorId;
String? _jobApplicatorId;
String? _jobDescription;
List? _jobUserData;
String? _jobAddress;
String? _jobType;
//
#override
void initState() {
super.initState();
_jobApplicationStartDate = DateTime.now();
_jobApplicationEndDate = DateTime.now();
_firebaseService = FirebaseCloudStorage();
// _jobDescriptionController = TextEditingController();
// _jobAreaCodeController = TextEditingController();
// _jobApplicationStateController = TextEditingController();
}
//Future<CloudJobApplication>
createOrGetExistingJob(BuildContext context) async {
final widgetJobApplication = context.getArgument<CloudJobApplication>();
if (widgetJobApplication != null) {
_jobApplication = widgetJobApplication;
_jobApplicationState = widgetJobApplication.jobApplicationState;
_jobApplicatorId = widgetJobApplication.jobApplicatorId;
_jobCreatorId = widgetJobApplication.jobCreatorId;
_jobDescription = widgetJobApplication.jobApplicationDescription;
return widgetJobApplication;
}
print('ELSE TRIGGERED!');
return widgetJobApplication;
}
void _updateJobField(localStateField, jobColumn, jobColumnValue) async {
//* localStateField: local field to update so that the build context is refreshed
//* jobColumn: the name of the column in the db
//* jobColumnValue: the value for the jobColumn
setState(() {
if (localStateField == '_jobApplicationState') {
_jobApplicationState = jobColumnValue;
}
});
await _firebaseService.updateJobApplicationColumn(
documentId: _jobApplication?.documentId as String,
fieldNameColumn: jobColumn,
fieldNameColumnValue: jobColumnValue,
);
}
sendInqury() {
print('setting job applications state!');
print('_jobApplicationState b4:: $_jobApplicationState');
_updateJobField(_jobApplicationState, jobApplicationStateColumn,
jobApplicationStateOpen);
print('_jobApplicationState after:: $_jobApplicationState');
}
#override
void dispose() {
//_deleteJobIfTextIsEmpty();
// _jobDescriptionController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('update job application'),
actions: [],
),
body: FutureBuilder(
future: createOrGetExistingJob(context),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(32.0),
children: [
//getStateChevrons(_jobApplicationState),
const Divider(
height: 20,
thickness: 5,
indent: 0,
endIndent: 0,
color: Colors.blue,
),
Text(_jobApplicationState),
TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding: const EdgeInsets.all(16.0),
textStyle: const TextStyle(fontSize: 20),
),
onPressed: sendInqury,
child: const Text('Send inquiry'),
)
],
),
);
default:
return const CircularProgressIndicator();
}
},
),
);
}
}
I figured out the answer, here is the answer code:
import 'dart:developer';
import 'package:flutter/material.dart';
import '../../services/cloud/cloud_job_application.dart';
import '/services/auth/auth_service.dart';
import '/utilities/generics/get_arguments.dart';
import '/services/cloud/firebase_cloud_storage.dart';
class JobApplicationView extends StatefulWidget {
const JobApplicationView({Key? key}) : super(key: key);
#override
_JobApplicationViewState createState() => _JobApplicationViewState();
}
// https://youtu.be/VPvVD8t02U8?t=90350
class _JobApplicationViewState extends State<JobApplicationView> {
CloudJobApplication? _jobApplication;
late final FirebaseCloudStorage cloudFunctions;
final _formKey = GlobalKey<FormState>();
final currentUser = AuthService.firebase().currentUser!;
// state varibles
String _jobApplicationState = 'default';
String _jobApplicationSubState = 'default';
late final TextEditingController _jobDescriptionController;
#override
void initState() {
super.initState();
cloudFunctions = FirebaseCloudStorage();
_jobDescriptionController = TextEditingController();
}
//Future<CloudJobApplication>
getExistingJobApplication(BuildContext context) async {
log('getExistingJobApplication()');
if (_jobApplicationState == 'default') {
var widgetJobApplication = context.getArgument<CloudJobApplication>();
log('first time openning job application, returning server data');
_jobApplication = widgetJobApplication;
_jobApplicationState =
widgetJobApplication?.jobApplicationState as String;
_jobDescriptionController.text =
widgetJobApplication?.jobApplicationDescription as String;
return widgetJobApplication;
} else {
log('job application has been updated, returnnig local data');
return cloudFunctions.getJobApplication(_jobApplication!.documentId);
}
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('update job application'),
actions: [],
),
body: FutureBuilder(
future: getExistingJobApplication(context),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return Form(
key: _formKey,
child: ListView(padding: const EdgeInsets.all(32.0), children: [
Text(_jobApplicationState),
Text(_jobDescriptionController.text),
const Divider(
height: 20,
thickness: 5,
indent: 0,
endIndent: 0,
color: Colors.blue,
),
TextFormField(
controller: _jobDescriptionController,
maxLines: 5,
decoration: InputDecoration(
// enabled: _jobState == jobStateNew ? true : false,
hintText: "The toilet wont flush",
filled: true,
// fillColor: _jobState == jobStateNew ? Colors.white : Colors.grey,
label: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
color: Colors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Padding(padding: EdgeInsets.only(left: 8.0)),
Icon(Icons.info_outline),
Padding(
padding: EdgeInsets.only(left: 8.0, right: 8.0),
child: Text("Job description"),
),
],
),
),
),
validator: (str) =>
str == '' ? "Job description can't be empty" : null,
),
TextButton(
onPressed: () async {
setState(() {
_jobApplicationState = 'Open';
});
await cloudFunctions.updateJobApplication(
documentId: _jobApplication?.documentId as String,
jobDescription: _jobDescriptionController.text,
jobApplicationState: 'Open',
);
},
child: const Text('update state')),
//
]),
);
default:
return const CircularProgressIndicator();
}
},
),
);
}
}
You should separate the UI and logic -> create a jobApplication Model.
Pack all your logic into a ChangeNotifier and notifyListeners on change.
This is also better for performance because it only rebuilds needed parts of the UI.
I can recommend using a ChangeNotifierProvider.
class JobApplicationProvider extends ChangeNotifier {
JobApplication jobapplication = BasicParam.standard;
void setJobApplication(json) async {
jobapplication = JobApplication.fromJson(json);
notifyListeners();
}
}
And in the build Method use it like this:
Widget build(BuildContext context) {
JobApplicationProvider jobApplication= Provider.of(context);
return Text(jobApplication.state);
}

Stream.periodic clock is updating other StreamBuilder

This is my code:
class MobileHomePage extends StatefulWidget {
const MobileHomePage({Key? key}) : super(key: key);
#override
State<MobileHomePage> createState() => _MobileHomePageState();
}
class _MobileHomePageState extends State<MobileHomePage> {
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
int? second;
#override
void initState() {
second = int.parse(DateFormat.s().format(DateTime.now()));
Timer.periodic(const Duration(seconds: 1), (timer) => getCurrentTime());
super.initState();
}
void getCurrentTime() {
setState(() {
second = int.parse(DateFormat.s().format(DateTime.now()));
});
}
User user = FirebaseAuth.instance.currentUser!;
final Stream<QuerySnapshot> _mainSubjectStream = FirebaseFirestore.instance
.collection('systems')
.orderBy('system_name', descending: false)
.snapshots();
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
final GlobalKey<ScaffoldState> _key = GlobalKey();
return Scaffold(
key: _key,
body: SafeArea(
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Container(
color: Colors.transparent,
child: StreamBuilder<QuerySnapshot>(
stream: _mainSubjectStream,
builder: (context, mainSubjectSnapshot) {
if (mainSubjectSnapshot.hasError) {
return const Center(child: Text('Error));
}
if (mainSubjectSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
var length = mainSubjectSnapshot.data!.docs.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: length,
itemBuilder: (context, i) {
var output = mainSubjectSnapshot.data!.docs[i];
return Text(output['name']);
});
},
),
)),
const Spacer(flex: 3),
Expanded(
flex: 5,
child: DatePicker(
DateTime.now(),
initialSelectedDate: _date,
daysCount: 8,
locale: 'pt',
selectionColor: const Color.fromRGBO(67, 97, 238, 1),
selectedTextColor: Colors.white,
onDateChange: (date){
setState(() {
_date = date;
});
},
),
),
Expanded(
flex: 1,
child: Text(second.toString()
),
],
),
));
}
}
When I try to display the clock (as in second.toString()), the StreamBuilder is also updated, resulting in a new progress indicator each second. What is breaking my head is that I just copied the same strutted that I used in other code – which works fine. Did I do something wrong? Why is it influencing the other stream?
EDIT
Have just found out that the error is related to the key in the scaffold. When I deleted it, it worked fine. The thing is that I need I as I have a custom button to open the drawer. 1st question is how does it influences it. 2nd question is how to solve it?

Got NoSuchMethodError (NoSuchMethodError: The method 'call' was called on null. error after setState in flutter

I have Form screen that contains a form widget.
After changing state (now with bloc but I tested with setState, no different) I got following error:
The following NoSuchMethodError was thrown while handling a gesture:
The method 'call' was called on null.
Receiver: null
Tried calling: call()
This only happened when I change state (if I don't yield new state, or setState it works without error).
but after changing state and probably rebuilding widget I got error:
This is main screen:
class _AuthScreenState extends State<AuthScreen> {
final AuthRepository repository = AuthRepository();
PageController controller;
Bloc _bloc;
#override
void initState() {
controller = PageController(initialPage: widget.page);
super.initState();
}
void changePage(int page) {
controller.animateToPage(
page,
curve: Curves.ease,
duration: Duration(milliseconds: 300),
);
}
void onSubmit(AuthType authType, AuthReq req) {
if (authType == AuthType.LOGIN) {
_bloc.add(LoginEvent(req: req));
} else {
_bloc.add(RegisterEvent(req: req));
}
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => AuthBloc(repository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
_bloc = context.bloc<AuthBloc>();
return ScreenContainer(
loading: state is LoadingState,
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: controller,
physics: NeverScrollableScrollPhysics(),
itemCount: 2,
itemBuilder: (context, position) {
return position == 0
? LoginPage(
onPageChange: () => changePage(1),
onSubmit: (req) => onSubmit(AuthType.LOGIN, req),
)
: RegisterPage(
onPageChange: () => changePage(0),
onSubmit: (req) => onSubmit(AuthType.REGISTER, req),
);
},
),
),
],
),
),
);
},
),
);
}
}
class LoginPage extends StatelessWidget {
final VoidCallback onPageChange;
final void Function(AuthReq req) onSubmit;
final FormController controller = FormController();
LoginPage({
#required this.onPageChange,
#required this.onSubmit,
});
void submit() {
var values = controller?.submit();
if (values.isNull) {
return;
}
onSubmit(AuthReq(password: values['password'], username: values['email']));
}
#override
Widget build(BuildContext context) {
var authType = AuthType.LOGIN;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: hP),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormWrapper(
inputs: loginFields,
controller: controller,
),
submitButton(context, authType, submit),
],
),
),
],
),
),
),
],
);
}
}
class FormController {
Map Function() submit;
}
class FormWrapper extends StatefulWidget {
final List<InputProps> inputs;
final FormController controller;
FormWrapper({
#required this.inputs,
this.controller,
});
#override
_FormWrapperState createState() => _FormWrapperState(controller);
}
class _FormWrapperState extends State<FormWrapper> {
final _formKey = GlobalKey<FormState>();
_FormWrapperState(FormController _controller) {
_controller.submit = submit;
}
bool _autoValidation = false;
Map values = {};
void setValue(String key, dynamic value) {
values[key] = value;
}
Map submit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return values;
} else {
setState(() {
_autoValidation = true;
});
return null;
}
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
autovalidate: _autoValidation,
key: _formKey,
child: Column(
children: widget.inputs
.map(
(e) => Container(
margin: EdgeInsets.only(bottom: e.isLast ? 0 : 24.0 - 7.0),
child: RoundedInput(
inputProps: e,
onChange: (value) => setValue(e.label, value),
),
),
)
.toList(),
),
),
);
}
}
I found solution, I post answer instead of deleting question for may help others in future :)
I override didUpdateWidget to reinitialize variable in _FormWrapperState:
#override
void didUpdateWidget(oldWidget) {
widget.controller.submit = submit;
super.didUpdateWidget(oldWidget);
}

Flutter Stateful Widget State not Initializing

I'm making a command and control application using Flutter, and have come across an odd problem. 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. 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. 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.
Any ideas as to what could be causing this problem?
Code for the 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:
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:
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 StatefulWidgets between build calls, so their state is always the same. Try separating RobotSummary business logic from the view logic. 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.
},
),
);
}