Flutter wifi EspTouch connection stream problem - flutter

I want to connect to an ESP32 with my Flutter app. To implement the connection via ESPTouch I use the package "esptouch_flutter".
Now there is the following problem: If I enter my password incorrectly, it tries to connect for a minute and aborts, which is what it is supposed to do. However, if I then enter the correct password, it also searches for a minute and aborts, although it should actually connect. If I enter the correct password without having entered anything wrong before, it works fine.
The actual connection is established via a stream.
stream = widget.task.execute();
I have a suspicion that even after dispose of the page, the stream continues to run and then does not restart properly. Can this be? Or maybe anyone else have an idea?
Full Code:
class wifiConnection extends StatefulWidget {
const wifiConnection({Key? key, required this.task}) : super(key: key);
final ESPTouchTask task;
#override
State<StatefulWidget> createState() => wifiConnectionState();
}
class wifiConnectionState extends State<wifiConnection> {
late final Stream<ESPTouchResult> stream;
late final StreamSubscription<ESPTouchResult> streamSubscription;
late final Timer timer;
final List<ESPTouchResult> results = [];
var connectionSuccessfull = true;
#override
void initState() {
stream = widget.task.execute();
streamSubscription = stream.listen(results.add);
final receiving = widget.task.taskParameter.waitUdpReceiving;
final sending = widget.task.taskParameter.waitUdpSending;
// final cancelLatestAfter = receiving + sending; // Default = 1 min
const testTimer = Duration(seconds: 15);
timer = Timer(
// cancelLatestAfter,
testTimer,
() {
streamSubscription.cancel();
if (results.isEmpty && mounted) {
setState(() {
connectionSuccessfull = false;
});
}
},
);
super.initState();
}
#override
dispose() {
timer.cancel();
streamSubscription.cancel();
super.dispose();
}
_dissolve() {
setState(() async {
await Future.delayed(const Duration(milliseconds: 1000));
setOnboardingEnum(Onboarding.wifiFinished);
Navigator.of(context).pushReplacement(createRoute(const StaticBellPage()));
});
}
getConnectionSite(AsyncSnapshot<ESPTouchResult> snapshot) {
if (snapshot.hasError) {
return connectionError();
}
if (!snapshot.hasData) {
return connectionPending();
}
switch (snapshot.connectionState) {
case ConnectionState.active:
return connectionActive();
case ConnectionState.none:
return connectionError();
case ConnectionState.done:
return connectionActive();
case ConnectionState.waiting:
return connectionPending();
}
}
connectionPending() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CircularProgressIndicator()),
connectionPendingText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
connectionActive() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CheckArrow()),
connectionSuccessfullText,
MydoobeButton(buttonText: "Weiter", callback: _dissolve),
],
),
);
}
connectionError() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: ErrorCross()),
connectionErrorText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
StreamBuilder<ESPTouchResult>(
builder: (context, AsyncSnapshot<ESPTouchResult> snapshot) {
return AnimatedSwitcher(
duration: const Duration(seconds: 2),
child: connectionSuccessfull ? getConnectionSite(snapshot) : connectionError() as Widget);
},
stream: stream,
),
],
),
],
),
);
}
}
var connectionPendingText = RichText(...);
var connectionSuccessfullText = RichText(...);
var connectionErrorText = RichText(...);

Related

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

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.

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?

Flutter: How to make a page wait until data loads?

I used async while using the database, however, the page loads before the data. On the page a lot of widgets are connected to the trophyPoint variable and the page loads before the variable. I used AutomaticKeepAliveClientMixin where the bottom navigation bar is located. It worked for the slide navigations, but when I tapped the navigation bar items, the variable returns null at first and it is the problem.
Edit: I also tried using the SharedPreferences class to load old data but it didn't work either.
Code
class MainScreen extends StatefulWidget {
static String id = "MainScreen";
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int trophyPoint = 0;
final Future<FirebaseApp> _future = Firebase.initializeApp();
//Database instance
final _db = FirebaseDatabase.instance
.reference()
.child("users")
.child(_auth.currentUser.uid);
//method for data
void readData(
_db,
) async {
await _db.once().then((DataSnapshot snapshot) {
if (!mounted) return;
setState(() {
trophyPoint = snapshot.value["tp"];
});
});
}
/* void tp() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
if (!mounted) return;
setState(() {
geciciTP = sharedPreferences.getInt("trophy");
sharedPreferences.setInt("geciciTP", geciciTP);
});
}*/
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
readData(_db);
Size size = MediaQuery.of(context).size;
precacheImage(AssetImage('images/indicator.gif'), context);
return buildContainer(size, context);
}
Container buildContainer(Size size, BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: kGradientColor,
),
child: Scaffold(
backgroundColor: Colors.transparent,
body: FutureBuilder(
future: _future,
initialData: trophyPoint,
builder: (context, snapshot) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
flex: 2,
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
right: size.width / 25,
top: size.height / 60,
child: Container(
width: 100,
height: size.height * 0.055,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45),
color: kDefaultLightColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FontAwesomeIcons.trophy,
color: Color(0xffFFD700),
),
Text(
" $trophyPoint",
...
gif file of the problem
Inside your FutureBuilder you can check:
if(snapshot.hasData){
return body....
} else {
return CircularProgressIndicator()
}
The example at the link is pretty good.

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

How to properly use async function from onPress

I am trying to do some image processing using filters, and image package. I can make it work, but it hangs the UI.
class ImageScreen extends StatefulWidget {
static MaterialPageRoute route(String path) => MaterialPageRoute(
builder: (context) => ImageScreen(
imagePath: path,
));
final String imagePath;
const ImageScreen({Key key, this.imagePath}) : super(key: key);
#override
_ImageScreenState createState() => _ImageScreenState();
}
class _ImageScreenState extends State<ImageScreen> {
int value = 0;
Filter _filter;
final _random = Random();
_ImageScreenState();
Widget _animatedWidget = const Center(child: CircularProgressIndicator());
Future<Widget> _createImage(Filter filter) async {
value++;
_filter = filter;
var bytes = await File(widget.imagePath).readAsBytes();
var i = await image.decodeImage( bytes );
_filter.apply(i.getBytes(), i.width, i.height);
return Image(key: ValueKey<int>(_random.nextInt(200)) , image: MemoryImage(image.encodeJpg(i)));
}
void _initiate(Filter filter) {
debugPrint("initializing");
_createImage(filter).then(
(widget)=> setState(()=> _animatedWidget =widget));
debugPrint("done");
}
#override
void initState() {
super.initState();
_initiate(NoFilter());
}
#override
Widget build(BuildContext context) {
return Scaffold(body:
Stack(
children: [
AnimatedSwitcher(child: _animatedWidget, duration: Duration(milliseconds: 200),),
Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: EdgeInsets.only(top: 10, bottom: 10),
color: Color.fromRGBO(200, 200, 200, 0.9),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FlatButton(
child: Text('Original'),
onPressed: () async {
_initiate(NoFilter());
},
),
FlatButton(
child: Text('B&W'),
onPressed: () {
_initiate(MoonFilter());
},
),
FlatButton(
child: Text('Dusty'),
onPressed: () {
_initiate(ReyesFilter());
},
)
],
),
)
)
],
));
}
}
What I want to happen is that the processing to be off main thread, and then see a nice transition. Unfortunately something is blocking the main thread.
The button gets stuck in depressed state, and then, after the process finished, page flickers and shows the new image.
you have to make this function to async
Future<void> _initiate(Filter filter)async {
debugPrint("initializing");
_createImage(filter).then(
(widget)=> setState(()=> _animatedWidget =widget));
debugPrint("done");
}
and if you want to call this function then you can call like
onPressed: () async {
await _initiate(NoFilter());
},