I am developing an application at the moment where each StateFull widget is listening on a Stream within it's initState function. The data received from the Stream is then updated in the widgets state.
The problem is, after 1min or less, the graphical interface no longer updates. However, the stream is still receiving data, which I can tell by putting a print function in the listen function.
Please note, the stream is from StreamController().stream.asBroadcastStream() as I need to listen to the same streams from multiple widgets. If it helps, Here is the API to the data I am receiving, along with their timings. Each packet is in it's own stream.
Edit: For example,
class DeltaInfo extends StatefulWidget{
DeltaInfoState createState()=> DeltaInfoState();
}
class DeltaInfoState extends State<DeltaInfo> {
LapData playerCarLapData;
LapData carBehindLapData;
LapData carInFrontLapData;
int carInFrontIndex;
int carBehindIndex;
int numberOfRacers = 20;
int session = SessionStatus.raceOne;
#override initState(){
super.initState();
sessionStream.asBroadcastStream().listen((PacketSessionInfo packet){
session = packet.sessionType;
sessionStream.drain();
});
lapDataStream.asBroadcastStream().listen((PacketLapData packet){
if(SessionStatus.isRace(sessionType: session)){
numberOfRacers = 0;
playerCarLapData = packet.lapData[packet.headder.playerCarIndex];
int i = 0;
carInFrontLapData = null;
carBehindLapData = null;
carInFrontIndex = null;
carBehindIndex = null;
packet.lapData.forEach((LapData car){
if(car == null) return;
if(ResultStatus.isInRace(resultStatus: car.resultStatus)){
this.numberOfRacers++;
}
if(car.carPosistion == playerCarLapData.carPosistion - 1){
carInFrontIndex = i;
carInFrontLapData = car;
}
if(car.carPosistion == playerCarLapData.carPosistion + 1) {
carBehindIndex = i;
carBehindLapData = car;
}
i++;
});
carTelemtryStream.asBroadcastStream().listen((PacketCarTelemtryData packet){
CarTelemtryData playersCar = packet.carTelemtryData[packet.headder.playerCarIndex];
if(carInFrontIndex != null){
CarTelemtryData carInFront = packet.carTelemtryData[carInFrontIndex];
double time = calculateDelta(
carALapData: carInFrontLapData,
carBLapData: playerCarLapData,
carATelemtry: carInFront,
carBTelemtry: playersCar,
carAInfront: true
);
setState((){
inFrontValue = "+ ${time.toStringAsFixed(3)}";
});
} else {
setState((){
inFrontValue = "In lead";
});
}
if(carBehindIndex != null){
CarTelemtryData carBehind = packet.carTelemtryData[carBehindIndex];
setState((){
double time = calculateDelta(
carALapData: carBehindLapData,
carBLapData: playerCarLapData,
carATelemtry: carBehind,
carBTelemtry: playersCar,
carAInfront: false
);
behindValue = "- ${time.toStringAsFixed(3)}";
});
} else {
setState((){
behindValue = "Last Place";
});
}
});
} else {
LapData player = packet.lapData[packet.headder.playerCarIndex];
Map<String, int> currentTime = timeInfoFromSeconds(player.currentLapTime);
int mins = currentTime["mins"];
String seconds = currentTime["seconds"].toString().padLeft(2, '0');
// String ms = (player.currentLapTime % 1).toString().substring(2, 5);
String ms = "000";
setState((){
inFrontValue = "$mins:$seconds.$ms";
});
Map<String, int> bestTime = timeInfoFromSeconds(player.bestLapTime);
mins = bestTime["mins"];
seconds = bestTime["seconds"].toString().padLeft(2, '0');
ms = (player.bestLapTime % 1).toString().substring(2, 5);
setState((){
behindValue = "$mins:$seconds.$ms";
});
}
lapDataStream.drain();
});
}
String inFrontValue = "A";
String behindValue = "A";
#override Widget build(BuildContext context){
return Expanded(
child: Column(
children: [
Expanded(
child: FittedBox(
child: Text("$inFrontValue"),
),
),
Expanded(
child: FittedBox(
child: Text("$behindValue"),
),
),
]
)
);
}
}
double calculateDelta(
{#required LapData carALapData,
#required LapData carBLapData,
#required CarTelemtryData carATelemtry,
#required CarTelemtryData carBTelemtry,
#required bool carAInfront}
){
if(carAInfront){
double distanceDifference = carALapData.totalDistance - carBLapData.totalDistance;
double carBSpeed = kphToMs(carBTelemtry.speed);
return distanceDifference/carBSpeed;
} else {
double distanceDifference = carBLapData.totalDistance - carALapData.totalDistance;
double carASpeed = kphToMs(carATelemtry.speed);
return distanceDifference/carASpeed;
}
}
double kphToMs(int kph){
return kph*0.2777778;
}
And then the stream is define as the following
StreamController<PacketLapData> _lapDataStream = StreamController<PacketLapData>();
Stream<PacketLapData> lapDataStream = _lapDataStream.stream.asBroadcastStream();
In the end, Instead of listening to the stream on initState, I used a StreamBuilder in the build function instead. This seams to have stopped the lag/freezing.
Related
I am currently making a music player, and I made PiP mode widget.
the position of the widget appears to be in the right place,
but when I actually tab buttons they don't work properly.
the clickable area is like down half of the widget.
just like the image above, those 3 buttons only work when I tab the area in the red square.
and the code looks like this:
class AudioTitleOverlayWidget extends StatefulWidget {
final Function onClear;
final Widget widget;
const AudioTitleOverlayWidget(
{Key? key, required this.onClear, required this.widget})
: super(key: key);
#override
_AudioTitleOverlayWidgetState createState() =>
_AudioTitleOverlayWidgetState();
}
class _AudioTitleOverlayWidgetState extends State<AudioTitleOverlayWidget> {
late double width;
late double oldWidth;
late double oldHeight;
late double height;
bool isInPipMode = false;
Offset offset = const Offset(0, 500);
_onExitPipMode() {
Future.microtask(() {
setState(() {
isInPipMode = false;
width = oldWidth;
height = oldHeight;
offset = const Offset(0, 0);
});
});
Future.delayed(const Duration(milliseconds: 250), () {
Get.find<OverlayHandler>().disablePip();
});
}
_onPipMode() {
Future.delayed(const Duration(milliseconds: 100), () {
setState(() {
isInPipMode = true;
width = oldWidth;
height = Constants.VIDEO_TITLE_HEIGHT_PIP - 6.0;
// height = 20;
// offset = Offset(0, oldHeight - height - Constants.BOTTOM_PADDING_PIP);
offset = Offset(0, height * 7.55);
});
});
}
#override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
oldWidth = width = Get.width;
oldHeight = height = Get.height;
final overlayController = Get.put(OverlayHandler());
final hasBottomTab = overlayController.hasBottomTab;
return GetBuilder<OverlayHandler>(builder: (context) {
if (Get.find<OverlayHandler>().inPipMode != isInPipMode) {
isInPipMode = Get.find<OverlayHandler>().inPipMode;
if (isInPipMode) {
_onPipMode();
} else {
_onExitPipMode();
}
}
if(Get.currentRoute == '/home'){
overlayController.hasBottomTab.value = true;
} else{
overlayController.hasBottomTab.value = false;
}
return Obx(()=>Positioned(
// duration: const Duration(milliseconds: 200),
// curve: Curves.fastOutSlowIn,
left:
Get.isBottomSheetOpen == true
? 10000 //final bottomPadding = MediaQuery.of(context).padding.bottom;
: offset.dx,
top: (isInPipMode == true && hasBottomTab.value == false)
? Get.height - 80 - bottomPadding
: (isInPipMode == true && hasBottomTab.value == true)
? Get.height - 160 - bottomPadding
: (isInPipMode == false && hasBottomTab.value == true)
? bottomPadding
: bottomPadding,
bottom: (isInPipMode == true && hasBottomTab.value == false)
? bottomPadding
: (isInPipMode == true && hasBottomTab.value == true)
? 80 + bottomPadding
: (isInPipMode == false && hasBottomTab.value == true)
? bottomPadding
: bottomPadding,
child: SizedBox(
height: height,
width: width,
child: widget.widget, // the code of the widget is below:
),
));
});
}
}
and the code of the overlay widget in Pip mode:
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xff1c1c1e),
body: GetBuilder<OverlayHandler>(
builder: (getContext) {
// return audioPlayer.playerState(
// builder: (context, realtimePlayingInfos) {
if (!Get.find<OverlayHandler>().inPipMode != true) {
return Stack(
children: [
Positioned(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {Get.find<OverlayHandler>().disablePip();},
child: Column(
children: [
Container(
width: Get.width,
height: 80,
.......
)
]
)
)
)
.....
I have tried in several devices, and on android they don't seem to have this issue,
and iphone with home button also don't seem to have this issue.
iphones without home button(iphone x ~) seem to have this issue.
anyone knows about this? any idea how to handle this?
this part was the issue:
the audioPlayer widget returns Scaffold for both PiP widget and the full screen player widget. I have low understanding level of Flutter widgets but returning Scaffold for PiP player was the reason.
the code should be like this:
#override
Widget build(BuildContext context) {
return
!Get.find<OverlayHandler>().inPipMode
? Scaffold(
.... // full screen player
: Stack(
.... // PiP player, not returning Scaffold
I want to listen to firebase upload success event, so that I can give a confirmation to the user about the completion of the particular event.
How to listen success event?
In debug console I'm getting the below information.
I/flutter (22734): EVENT StorageTaskEventType.progress
I/flutter (22734): EVENT StorageTaskEventType.progress
I/flutter (22734): EVENT StorageTaskEventType.progress
I/flutter (22734): EVENT StorageTaskEventType.success
My upload code looks like this.
final StorageReference firebaseStoragereference =
FirebaseStorage
.instance
.ref()
.child('images/myImage1.jpg' );
final StorageUploadTask task = firebaseStoragereference.putFile(sampleImage);
You can do it this way:
final StorageUploadTask uploadTask = ref.putFile(file);
StorageTaskSnapshot taskSnapshot = await uploadTask.onComplete;
String downloadUrl = await taskSnapshot.ref.getDownloadURL();
In Order to Listen to event, you Need to listen to the Events Streams & you Can Use StreamBuilder to Show the Status.
An Minimal E.g: You Modify to As per your Own Need like- show Progress Bar. In your Build Method where you want show the Status call the Function - _uploadStatus(task);
String _bytesTransferred(StorageTaskSnapshot snapshot) {
double res = snapshot.bytesTransferred / 1024.0;
double res2 = snapshot.totalByteCount / 1024.0;
return '${res.truncate().toString()}/${res2.truncate().toString()}';
}
Widget _uploadStatus(StorageUploadTask task) {
return StreamBuilder(
stream: task.events,
builder: (BuildContext context, snapshot) {
Widget subtitle;
if (snapshot.hasData) {
final StorageTaskEvent event = snapshot.data;
final StorageTaskSnapshot snap = event.snapshot;
subtitle = Text('${_bytesTransferred(snap)} KB sent');
} else {
subtitle = const Text('Starting...');
}
return ListTile(
title: s.isComplete && s.isSuccessful
? Text(
'Done',
style: detailStyle,
)
: Text(
'Uploading',
style: detailStyle,
),
subtitle: subtitle,
);
},
);
}
#anmol.majhail I improved your calc-function like this:
String _bytesTransferred(StorageTaskSnapshot snapshot) {
double res = (snapshot.bytesTransferred / 1024.0) / 1000;
double res2 = (snapshot.totalByteCount / 1024.0) / 1000;
return '${res.toStringAsFixed(2)}/${res2.toStringAsFixed(2)}';
}
With this it will look like this:
3,75/30,32 MB sent
Here for Flutter-Web:
String _bytesTransferred(fb.UploadTaskSnapshot snapshot) {
double res = (snapshot.bytesTransferred / 1024.0) / 1000;
double res2 = (snapshot.totalBytes / 1024.0) / 1000;
return '${res.toStringAsFixed(2)}/${res2.toStringAsFixed(2)}';
}
Widget _uploadStatus(fb.UploadTask task, String startingText, String doneText,
String uploadingText) {
return StreamBuilder<fb.UploadTaskSnapshot>(
stream: task.onStateChanged,
builder: (BuildContext context, snapshot) {
Widget subtitle;
if (snapshot.hasData) {
final fb.UploadTaskSnapshot snap = snapshot.data;
subtitle = Text('${_bytesTransferred(snap)} MB');
} else {
subtitle = Text(startingText);
}
return ListTile(
title: snapshot.data.state == fb.TaskState.SUCCESS
? Text(doneText)
: Text(uploadingText),
subtitle: subtitle,
);
},
);
}
I'm having trouble canceling a stream that is created using the Stream.periodic constructor. Below is my attempt at canceling the stream. However, I'm having a hard time extracting out the 'count' variable from the internal scope. Therefore, I can't cancel the subscription.
import 'dart:async';
void main() {
int count = 0;
final Stream newsStream = new Stream.periodic(Duration(seconds: 2), (_) {
return _;
});
StreamSubscription mySubscribedStream = newsStream.map((e) {
count = e;
print(count);
return 'stuff $e';
}).listen((e) {
print(e);
});
// count = 0 here because count is scoped inside mySubscribedStream
// How do I extract out 'count', so I can cancel the stream?
if (count > 5) {
mySubscribedStream.cancel();
mySubscribedStream = null;
}
}
I'd rather use take(5) instead of checking > 5 and then cancel
final Stream newsStream = new Stream.periodic(Duration(seconds: 2), (_) => count++);
newsStream.map((e) {
count = e;
print(count);
return 'stuff $e';
}).take(5).forEach((e) {
print(e);
});
I'm trying to add a date mask to a textField since I did not like the Date Picker because for date of birth, for example, it is not as nimble.
After that, converting from string to datetime, I believe I can continue the project,
Thanks in advance.
static final TextEditingController _birthDate = new TextEditingController();
new TextFormField(
controller: _birthDate,
maxLength: 10,
keyboardType: TextInputType.datetime,
validator: _validateDate
), String _validateDate(String value) {
if(value.isEmpty)
return null;
if(value.length != 10)
return 'Enter date in DD / MM / YYYY format';
return null;
}
I modified some things and managed to get the expected result.
I created this class to define the variable
static final _UsNumberTextInputFormatter _birthDate = new _UsNumberTextInputFormatter();
class _UsNumberTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue ) {
final int newTextLength = newValue.text.length;
int selectionIndex = newValue.selection.end;
int usedSubstringIndex = 0;
final StringBuffer newText = new StringBuffer();
if (newTextLength >= 3) {
newText.write(newValue.text.substring(0, usedSubstringIndex = 2) + '/');
if (newValue.selection.end >= 2)
selectionIndex ++;
}
if (newTextLength >= 5) {
newText.write(newValue.text.substring(2, usedSubstringIndex = 4) + '/');
if (newValue.selection.end >= 4)
selectionIndex++;
}
if (newTextLength >= 9) {
newText.write(newValue.text.substring(4, usedSubstringIndex = 8));
if (newValue.selection.end >= 8)
selectionIndex++;
}
// Dump the rest.
if (newTextLength >= usedSubstringIndex)
newText.write(newValue.text.substring(usedSubstringIndex));
return new TextEditingValue(
text: newText.toString(),
selection: new TextSelection.collapsed(offset: selectionIndex),
);
}
}
And finally I added an inputformat to the textfield
new TextFormField(
maxLength: 10,
keyboardType: TextInputType.datetime,
validator: _validateDate,
decoration: const InputDecoration(
hintText: 'Digite sua data de nascimento',
labelText: 'Data de Nascimento',
),
inputFormatters: <TextInputFormatter> [
WhitelistingTextInputFormatter.digitsOnly,
// Fit the validating format.
_birthDate,
]
),
Now it's all right, thank you
https://pub.dartlang.org/packages/masked_text
masked_text
A package for masked texts, so if you want a mask for phone, or zip code or any kind of mask, just use it :D
Getting Started
It's very simple, it's a Widget as all the other ones.
new MaskedTextField
(
maskedTextFieldController: _textCPFController,
mask: "xx/xx/xxxx",
maxLength: 10,
keyboardType: TextInputType.number,
inputDecoration: new InputDecoration(
hintText: "Type your birthdate", labelText: "Date"),
);
'x' is the normal char that your text will have.
this sample reproduces something like this in the end: 11/02/1995.
My solution:
class MaskTextInputFormatter extends TextInputFormatter {
final int maskLength;
final Map<String, List<int>> separatorBoundries;
MaskTextInputFormatter({
String mask = "xx.xx.xx-xxx.xx",
List<String> separators = const [".", "-"],
}) : this.separatorBoundries = {
for (var v in separators)
v: mask.split("").asMap().entries.where((entry) => entry.value == v).map((e) => e.key).toList()
},
this.maskLength = mask.length;
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
final int newTextLength = newValue.text.length;
final int oldTextLength = oldValue.text.length;
// removed char
if (newTextLength < oldTextLength) return newValue;
// maximum amount of chars
if (oldTextLength == maskLength) return oldValue;
// masking
final StringBuffer newText = StringBuffer();
int selectionIndex = newValue.selection.end;
// extra boundaries check
final separatorEntry1 = separatorBoundries.entries.firstWhereOrNull((entry) => entry.value.contains(oldTextLength));
if (separatorEntry1 != null) {
newText.write(oldValue.text + separatorEntry1.key);
selectionIndex++;
} else {
newText.write(oldValue.text);
}
// write the char
newText.write(newValue.text[newValue.text.length - 1]);
return TextEditingValue(
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
This solution checks when date is out of range (eg. there is not month like 13). It's super inefficient, but it works.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class DateFormatter extends TextInputFormatter {
final String mask = 'xx-xx-xxxx';
final String separator = '-';
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
if(newValue.text.length > 0) {
if(newValue.text.length > oldValue.text.length) {
String lastEnteredChar = newValue.text.substring(newValue.text.length-1);
if(!_isNumeric(lastEnteredChar)) return oldValue;
if(newValue.text.length > mask.length) return oldValue;
if(newValue.text.length < mask.length && mask[newValue.text.length - 1] == separator) {
String value = _validateValue(oldValue.text);
print(value);
return TextEditingValue(
text: '$value$separator$lastEnteredChar',
selection: TextSelection.collapsed(
offset: newValue.selection.end + 1,
),
);
}
if(newValue.text.length == mask.length) {
return TextEditingValue(
text: '${_validateValue(newValue.text)}',
selection: TextSelection.collapsed(
offset: newValue.selection.end,
),
);
}
}
}
return newValue;
}
bool _isNumeric(String s) {
if(s == null) return false;
return double.parse(s, (e) => null) != null;
}
String _validateValue(String s) {
String result = s;
if (s.length < 4) { // days
int num = int.parse(s.substring(s.length-2));
String raw = s.substring(0, s.length-2);
if (num == 0) {
result = raw + '01';
} else if (num > 31) {
result = raw + '31';
} else {
result = s;
}
} else if (s.length < 7) { // month
int num = int.parse(s.substring(s.length-2));
String raw = s.substring(0, s.length-2);
if (num == 0) {
result = raw + '01';
} else if (num > 12) {
result = raw + '12';
} else {
result = s;
}
} else { // year
int num = int.parse(s.substring(s.length-4));
String raw = s.substring(0, s.length-4);
if (num < 1950) {
result = raw + '1950';
} else if (num > 2006) {
result = raw + '2006';
} else {
result = s;
}
}
print(result);
return result;
}
}
You can now use the boolean property, "obscureText" of TextField to mask input.
I have already try to understand the API doc, the articles about them, and this post: How do you create a Stream in Dart
I'm making a simple web app using WebSocket. Actually, it's working well, but I want add a feature (enjoy learn).
This is my class (can be optimized I guess)
library Ask;
import 'dart:html';
import 'dart:async';
import 'dart:convert';
class Ask {
final String addr;
String _protocol;
String _port;
WebSocket _ws;
bool openned;
Map<int, Completer> _completer_list = {};
int _counter = 0;
static final Map<String, Ask> _cache = <String, Ask>{};
factory Ask(String addr) {
if (_cache.containsKey(addr)) {
return _cache[addr];
} else {
final ask_server = new Ask._internal(addr);
_cache[addr] = ask_server;
return ask_server;
}
}
Ask._internal(this.addr);
Future<bool> open() {
if (openned)
return true;
_completer_list[0] = new Completer();
if (window.location.protocol == 'http:') {
_port = ':8080/ws';
_protocol = 'ws://';
} else {
_port = ':8443/ws';
_protocol = 'wss://';
}
_ws = new WebSocket(_protocol + addr + _port);
_ws.onOpen.listen((e) {
_get_data();
_get_close();
openned = true;
_completer_list[0].complete(true);
});
return _completer_list[0].future;
}
Future<String> send(Map data) {
bool check = false;
int id;
_completer_list.forEach((k, v) {
if (v.isCompleted) {
id = data['ws_id'] = k;
_completer_list[k] = new Completer();
_ws.send(JSON.encode(data));
check = true;
}
});
if (!check) {
_counter++;
id = data['ws_id'] = _counter;
_completer_list[id] = new Completer();
_ws.send(JSON.encode(data));
}
return _completer_list[id].future;
}
void _get_data() {
_ws.onMessage.listen((MessageEvent data) {
var response = JSON.decode(data.data);
_completer_list[response['ws_id']].complete(response);
});
}
void _get_close() {
_ws.onClose.listen((_) {
print('Server have been lost. Try to reconnect in 3 seconds.');
new Timer(new Duration(seconds: 3), () {
_ws = new WebSocket(_protocol + addr + _port);
_get_data();
_get_close();
_ws.onOpen.listen((e) => print('Server is alive again.'));
});
});
}
}
Example of use:
void showIndex() {
Element main = querySelector('main');
Ask connect = new Ask('127.0.0.1');
Map request = {};
request['index'] = true;
connect.open().then((_) {
connect.send(request).then((data) {
main.setInnerHtml(data['response']);
});
});
}
I would replace the then by a listen who will be canceled when the message will completed. By this way, I can add a progress bar, I think...
So my question, my send function can be a stream and keep my concept of one websocket for all ? (yes, if my function is used when a request is in progress, it's sent and if she's finish before the first, I recovered her properly. Thank you ws_id).
Thank you.
I think what you need is a StreamController
https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-async.StreamController