Null check operator used on a null value when calling a function from another class - flutter

I have a class called DataCollectionPage where I have the following function in the initState function:
void establishConnection (){
BluetoothConnection.toAddress(widget.server!.address).then((connection) {
Utils.showSnackBar2("Successfully connected to your device");
this.connection = connection;
setState(() {
isConnecting = false;
isDisconnecting = false;
});
this.connection!.input!.listen(_onDataReceived).onDone(() {
if (isDisconnecting) {
Utils.showSnackBar2("Device Disconnected!!");
} else {
Utils.showSnackBar2("Device Disconnected!!");
}
if (mounted) {
setState(() {});
}
});
}).catchError((error) {Utils.showSnackBar("Connection failed, please try again later");
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BluetoothConnectionTask()),
);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error occurred while connecting'),
content: const Text(
"Connection failed. Please Try connecting to your device again"),
actions: <Widget>[
TextButton(
child: const Text("Close",style: TextStyle(color: Colors.black),),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
});
}
Then, in the same class, I have another function (startDataCollection) that looks like this:
void startDataCollection() async {
startFlag = startFlag.trim();
try {
List<int> list = startFlag.codeUnits;
Uint8List bytes = Uint8List.fromList(list);
connection!.output.add(bytes);
await connection!.output.allSent;
} catch (e) {
print(e);
}
}
At this point, there is no issue. However when I tried calling startDataCollection function from another screen (DataCollectionTimer Class), I got an error that
Null check operator used on a null value
which was referring to "connection" from the startDataCollection function.
Basically, I established a bluetooth connection with some hardware when I called the function establishConnection for the first time, and when I called it from the class DataCollectionTimer, it was still connected, but for some reason the connection var was empty when I called it from the DataCollectionPage class.
How can I use the function startDataCollection from another screen correctly?
If you need any more elaboration please let me know and thank you in advance.

Update:
I simple solved the problem by changing Bluetooth connection to global
Old:
BluetoothConnection? connection;
New:
static BluetoothConnection? connection;

Please ensure:
You're using the same connection object in all the pages.
you're awaiting BluetoothConnection.toAddress(widget.server!.address).then before doing anything with the connection value
The connection received from BluetoothConnection.toAddress(widget.server!.address).then((connection) is not null

Related

How to: Check (or avoid checking) if BuildContext passed as Function argument is still mounted

Let's say I have a function for handling errors (used across an entire app) that takes a BuildContext as an argument and displays an alert to the user e.g.
void handleException(BuildContext context, String messageToDisplay) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Error'),
content: Text(messageToDisplay),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
);
},
);
}
The problem I'm trying to solve is having to perform a mounted check before every call to handleException e.g.
try {
...
} on CustomException catch (e) {
if (mounted) { // I want to handle this inside of `handleException`
return;
}
handleException(
context,
"A known error occurred",
);
} catch (e) {
if (mounted) {
return;
}
handleException(
context,
"An unknown error occurred",
);
}
It would be great if I could perform this check inside of handleException or if anyone can suggest a better method for achieving the same results that would also work!
Thanks in advance

Automatically set State of Button WITHOUT pressing it

I have got a State Management Problem I couldn't get rid of and I want to reach out to you.
Basically, I activate with the Buttons a game and I am sending a String to the uC. The uC does its stuff and sends a response to Flutter including gameFinished=true (that works).
Now I want to reset the State of the Button to the init state WITHOUT pressing the Button. Following are some things I tried that didn't work.
#override
void initState() {
super.initState();
setState(() {
gameAktivated = false;
gameStarted = false;
});
}
void asyncSetState() async {
setState(() async {
gameAktivated = false;
gameStarted = false;
});
}
I am changing the style from "Start" to "Stop" when the Button is pressed and I send Data to the uC. (Works)
Edit: Ofc I have a second button that triggers gameAktivated=true :)
ElevatedButton(
onPressed: () {
if (gameAktivated) {
setState(() {
gameStarted = !gameStarted;
});
if (gameStarted) {
//Send Data to uC
} else if (!gameStarted) {
//Send Data to uC
}
}
},
child:
!gameStarted ? const Text('Start') : const Text('Stop'),
),
Button Displays Stop now.
Following I am receiving a String from the uC that I jsonEncode and I receive gameFinished=true. (Works)
Container(
child: streamInit
? StreamBuilder<List<int>>(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<int>> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
if (snapshot.connectionState ==ConnectionState.active) {
// getting data from Bluetooth
var currentValue =const BluetoothConnection().dataParser(snapshot.data);
config.jsonDeserializeGameFinished(currentValue);
if(config.gameFinished){
setState(() {
gameAktivated = false;
gameStarted = false;
});
asyncSetState();//Tested both methods seperate!
}
return Column(
children: [
Text(config.time.toString()),
],
);
} else {
return const Text(
'Check the stream',
textAlign: TextAlign.center,
);
}
},
): const Text("NaN",textAlign: TextAlign.center,),
),
When I try to reset the state like in the code above this error occures:
Calling setState Async didnt work for me either.
Where and how can I set the state based on the response from the uC?
Is it possible without using Provider Lib?
Thanks in advance Manuel.
Actually this error is not about the changing the state of button. Its a common mistake to update the widget state when its still building the widget tree.
Inside your StreamBuilder, you are trying to update the state before creating the UI which is raising this issue.
if(config.gameFinished){
setState(() {
gameAktivated = false;
gameStarted = false;
});
This will interrupt the build process of StreamBuilder as it will start updating the whole page. You need to move it out of the StreamBuilder's builder method.
To do that simply convert your stream to a broadcast, which will allow you to listen your stream multiple time.
var controller = StreamController<String>.broadcast();
Then inside the initState of the page you can setup a listener method to listen the changes like this
stream.listen((val) => setState((){
number = val;
}));
Here you can change the state values because from here it will not interrupt the widget tree building cycle.
For more details see this example I created
https://dartpad.dev/?id=a7986c44180ef0cb6555405ec25b482d
If you want to call setState() immediately after the build method was called you should use:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// this method gets called once directly after the previous setState() finishes.
});
Answer to my own Question:
In initState()
added this:
stream.listen((event) {
String valueJSON = const BluetoothConnection().dataParser(event);
config.jsonDeserializeGameFinished(valueJSON);
if (config.gameFinished) {
setState(() {
gameAktivated = false;
gameStarted = false;
});
}
});
The Code above listens to the stream, UTF-8 Decodes and JSON-Decodes the data. After this you can access the variable to set a state.

how to open a url with url_launcher and onTap of InkWell only once?

if i touch it once then it prints out 1 line 123
if i touch it many times then it prints out many line 123
So how when I touch it many times then it prints out 1 line 123 or exiting _launchUrl
When I touch it many times then I also had to go back to that number of times to get rid of _launchUrl
My code here
Hope to get everyone's help!
final Uri _url = Uri.parse('https://flutter.dev');
....
Future<void> _launchUrl() async {
if (!await launchUrl(_url)) {
throw 'Could not launch $_url';
}
}
...
InkWell(
onTap: () {
_launchUrl;
print('123');
}
)
I tried using the wait function but still not getting the desired result
Create a variable buttonPressed and set it default to false
bool buttonPressed = false;
Inside your onTap you can check if the buttonPressed is set to false. If it is set to false you can set it to true and run your _launchUrl function. After you called _launchUrl you can set it back to false to run it again.
if(buttonPressed == false){
buttonPressed = true;
await _launchUrl();
buttonPressed = false;
}
Also mark your onTap as async to use the await keyword
onTap: () async {
I fixed it by following way
Uri uri = Uri.parse('https://flutter.dev');
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LoadURL(uri),
)
);
// Navigate to here
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class LoadURL extends StatelessWidget {
late final Uri uri;
LoadURL(this.uri);
Future<void> _launchUrl(Uri uri) async {
if (!await launchUrl(uri)) {
throw 'Could not launch $uri';
}
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: _launchUrl(uri),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Navigator.pop(context);
}
return CircularProgressIndicator();;
},
),
);
}
}

Why using <Null> after showDialog?

Here I'm rendering an AlertDialog inside showDialog to display when error thrown from provider file. But that didn't work first time( Seems like stuck in catchError block, didn't execute future of catchError), Then I told to add <Null> after showDialog. That worked But How, What is changed, what it means?
Here is the code
if (_editedProduct.id == null) {
Provider.of<Products>(context, listen: false)
.addProduct(_editedProduct)
.catchError((error) {
return showDialog<Null>( //Here is that Null I didn't understand
context: context,
builder: (ctx) {
return AlertDialog(
title: Text('ERROR'),
content: Text('Error Occured'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('I GOT IT'),
)
],
);
});
}).then((_) {
print('THEN');
setState(() {
isLoading = false;
});
Navigator.of(context).pop();
});
} else {
Provider.of<Products>(context, listen: false)
.updateProduct(_editedProduct.id, _editedProduct);
Navigator.of(context).pop();
}
}
Nb: isLoading true shows a circluarProgressIndicator()
From the Official Docs
Returns a Future that resolves to the value (if any) that was passed to Navigator.pop when the dialog was closed.
The place where you sent Null indicates the type of data the Future will return.
By using Null you are indicating that the Future won't return any data, this tell your program not to wait for the possible value that might be returned.
Suppose in your dialog user has to pick 2 numbers and you want the picked number to be returned to the place where you called the showDialog() function then you'll use int instead of Null.
Something like
showDialog<int>(...)

Flutter exception: 'Context is not a subtype of BuildContext' error using Navigator

I am working on a flutter app and I am running into the following error: "The argument type 'Context' can't be assigned to the parameter type 'BuildContext'." However, when I try to pass in context to my functions as it says to do on the internet, I am unable to get it to work. Can someone please help me with this? The app is a camera app, and I know the video is successfully being recorded. here is the code:
This is the stop button that is pressed.
IconButton(
icon: const Icon(Icons.stop),
color: Colors.red,
onPressed: () {controller != null &&
controller.value.isInitialized &&
controller.value.isRecordingVideo
? onStopButtonPressed. //this is the function that is being called
: null;},
) //icons.stop
This is where the app isn't working with my Navigator.push call. It works fine when I take it out.
void onStopButtonPressed() {
stopVideoRecording().then((_) {
if (mounted) setState(() {});
print('Video recorded to: $videoPath');
print('Navigator is hit');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PreviewImageScreen(videoPath: videoPath),
), //MaterialpageRoute
); //Navigator
});
}
And here is my stopVideoRecording function
Future<void> stopVideoRecording() async {
if (!controller.value.isRecordingVideo) {
return null;
}
try {
await controller.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
//await _startVideoPlayer();
}
Thanks!
change it to
Navigator.push(
this.context, //add this so it uses the context of the class
MaterialPageRoute(
builder: (context) => PreviewImageScreen(videoPath: videoPath),
), //MaterialpageRoute
); //Navigator
Maybe you're importing a library or something that has a class named context and is interfering with the name context