What does this speech recognition error mean in my flutter app? - android

I have an error with my flutter app, so basically I have made a speech to text app, and it works on my iOS simulator and device perfectly. But on my android emulator, when I start the speech to text function, it outputs this error:
I/flutter ( 4958): onError: SpeechRecognitionError msg: error_permission, permanent: true
I am also using this speech to text package:
speech_to_text: ^2.3.0
Does anyone know what this means? I have also added the requirements in the AndroidManifest.xml, the "RECORD_AUDIO" and "INTERNET" ones. (these were listed on the pub.dev page for this package)
Here is my code:
class SpeechScreen extends StatefulWidget {
#override
_SpeechScreenState createState() => _SpeechScreenState();
}
class _SpeechScreenState extends State<SpeechScreen> {
final Map<String, HighlightedWord> _highlights = {
'Hablas': HighlightedWord(
onTap: () => print('voice'),
textStyle: const TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
};
stt.SpeechToText _speech;
bool _isListening = false;
String _text = 'Press the button and start speaking';
double _confidence = 1.0;
#override
void initState() {
super.initState();
_speech = stt.SpeechToText();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Speech To Text'),
centerTitle: true,
backgroundColor: Colors.orange,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AvatarGlow(
animate: _isListening,
glowColor: Colors.red,
endRadius: 75.0,
duration: const Duration(milliseconds: 2000),
repeatPauseDuration: const Duration(milliseconds: 100),
repeat: true,
child: RawMaterialButton(
onPressed: _listen,
child: Icon(
_isListening ? Icons.mic : Icons.mic_none,
size: 32,
color: Colors.white,
),
fillColor: Colors.red,
elevation: 2.0,
padding: EdgeInsets.all(15.0),
shape: CircleBorder(),
constraints: BoxConstraints.tight(Size(64, 64)),
splashColor: Colors.red),
),
body: SingleChildScrollView(
reverse: true,
child: Container(
padding: const EdgeInsets.fromLTRB(30.0, 30.0, 30.0, 150.0),
width: double.infinity,
child: Column(
children: [
Text(
'AI Confidence Level: ${(_confidence * 100.0).toStringAsFixed(1)}%',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32.0,
color: Colors.black,
fontWeight: FontWeight.w700,
),
),
SizedBox(
height: 50,
),
TextHighlight(
text: _text,
words: _highlights,
textAlign: TextAlign.center,
textStyle: const TextStyle(
fontSize: 32.0,
color: Colors.black,
fontWeight: FontWeight.w400),
),
],
)),
),
);
}
void _listen() async {
if (!_isListening) {
bool available = await _speech.initialize(
onStatus: (val) => print('onStatus: $val'),
onError: (val) => print('onError: $val'),
);
if (available) {
setState(() => _isListening = true);
_speech.listen(
onResult: (val) => setState(() {
_text = val.recognizedWords;
if (val.hasConfidenceRating && val.confidence > 0) {
_confidence = val.confidence;
}
}),
);
}
} else {
setState(() => _isListening = false);
_speech.stop();
}
}
}

It Doesn't work on android because you didn't give it the permissions to Record Audio. Navigate to this file:
<project root>/android/app/src/main/AndroidManifest.xml
and add these two lines to it
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
at the end it should look something like this:

Related

RegExp not working with the Validation Form in flutter?

This is a part of a project regarding feedback forms.
I already manage to create the validation form properly thanks to the answers here in StackOverflow developers.
But the problem is creating this regular expression which seems to not work on the validation form. I found in flutter docs the Iterable but I do not know how can I implement it on the dart page.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class SimpleDialog extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final title;
const SimpleDialog(this.title, {super.key});
#override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Alert'),
content: Text(title),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'))
],
);
}
}
class FeedbackPage extends StatefulWidget {
const FeedbackPage({super.key});
#override
State<FeedbackPage> createState() => _FeedbackPageState();
}
class _FeedbackPageState extends State<FeedbackPage> {
final nameOfuser = TextEditingController();
final emailOfuser = TextEditingController();
final messageOfuser = TextEditingController();
final _formKey = GlobalKey<FormState>();
List<bool> isTypeSelected = [false, false, false, true, true];
#override
Widget build(BuildContext context) {
return Scaffold(
// AppBar para sa taas ng design
appBar: AppBar(
centerTitle: true,
title: const Text(
"PicLeaf",
style: TextStyle(
color: Color.fromRGBO(102, 204, 102, 1.0),
fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
shadowColor: const Color.fromARGB(255, 95, 94, 94),
),
//body of the application
backgroundColor: const Color(0xffeeeeee),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(
height: 20,
),
const Text(
"Feedback",
style: TextStyle(
fontSize: 30.0,
fontFamily: 'RobotoBold',
fontWeight: FontWeight.bold,
color: Color.fromRGBO(102, 204, 102, 1.0)),
),
const Text(
"Give us your feedback!",
style: TextStyle(
fontSize: 18.0,
fontFamily: 'RobotoMedium',
),
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
const SizedBox(height: 16.0),
TextFormField(
controller: nameOfuser,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Name",
border: OutlineInputBorder(),
),
validator: (nameOfuser) {
if (nameOfuser == null || nameOfuser.isEmpty) {
return 'Please enter your Name';
}
return null;
},
),
const SizedBox(height: 8.0),
TextFormField(
controller: emailOfuser,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Email",
border: OutlineInputBorder(),
),
validator: (emailOfuser) {
if (emailOfuser == null || emailOfuser.isEmpty) {
String emailOfuser1 = emailOfuser.toString();
String pattern = r'\w+#\w+\.\w+';
if (RegExp(pattern).hasMatch(emailOfuser1)) {
return 'Please enter your Email Properly';
} else {
return 'Please enter your Email';
}
}
return null;
},
),
const SizedBox(height: 8.0),
TextFormField(
controller: messageOfuser,
maxLines: 6,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Message",
border: OutlineInputBorder(),
),
validator: (messageOfuser) {
if (messageOfuser == null || messageOfuser.isEmpty) {
return 'Please enter your Message';
}
return null;
},
),
const SizedBox(height: 8.0),
MaterialButton(
height: 50.0,
minWidth: double.infinity,
color: const Color.fromRGBO(102, 204, 102, 1.0),
onPressed: () {
if (_formKey.currentState!.validate()) {
showDialog(
context: context,
builder: (BuildContext context) {
return const SimpleDialog(
'Feedback Submitted');
});
Map<String, dynamic> data = {
"Name": nameOfuser.text,
"Email": emailOfuser.text,
"Message": messageOfuser.text,
"Time": FieldValue.serverTimestamp(),
};
setState(() {
nameOfuser.clear();
emailOfuser.clear();
messageOfuser.clear();
});
FirebaseFirestore.instance
.collection("FeedbackMessages")
.add(data);
}
},
child: const Text(
"SUBMIT",
style: TextStyle(
fontFamily: 'RobotoBold',
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
const SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
child: const Text(
'Contact Us!',
style: TextStyle(
fontSize: 30,
fontFamily: 'RobotoBold',
color: Color.fromRGBO(102, 204, 102, 1.0)),
textAlign: TextAlign.center,
),
),
Column(
children: <Widget>[
Container(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
margin: const EdgeInsets.symmetric(horizontal: 0),
child: TextButton.icon(
// <-- TextButton
onPressed: () {},
icon: const Icon(
Icons.facebook,
color: Colors.black,
size: 35.0,
),
label: const Text(
'facebook.com/picleaf',
style: TextStyle(fontFamily: 'RobotoMedium'),
),
style: TextButton.styleFrom(
foregroundColor: Colors.black,
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 10),
margin: const EdgeInsets.symmetric(horizontal: 0),
child: TextButton.icon(
// <-- TextButton
onPressed: () {},
icon: const Icon(
Icons.email,
color: Colors.black,
size: 35.0,
),
label: const Text(
'picleaf#gmail.com',
style: TextStyle(fontFamily: 'RobotoMedium'),
),
style: TextButton.styleFrom(
foregroundColor: Colors.black,
),
)),
],
)
],
),
),
),
],
),
));
}
}
You can use this validator:
validator: (emailOfuser) {
if (emailOfuser == null || emailOfuser.isEmpty) {
return 'Please enter your Email';
} else if (emailOfuser.isNotEmpty) {
String emailOfuser1 = emailOfuser.toString();
String pattern = r'\w+#\w+\.\w+';
if (RegExp(pattern).hasMatch(emailOfuser1) == false) {
return 'Please enter your Email Properly';
}
}
return null;
},
try to implement like this:
import 'package:flutter/material.dart';
class EmailValidationForm extends StatefulWidget {
const EmailValidationForm({Key? key}) : super(key: key);
#override
State<EmailValidationForm> createState() => _EmailValidationFormState();
}
class _EmailValidationFormState extends State<EmailValidationForm> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Email Validation'),
),
body: Form(
key: formKey,
child: Column(
children: [
TextFormField(
autofocus: false,
maxLength: 300,
keyboardType: TextInputType.emailAddress,
autocorrect: false,
validator: (email) {
if (email!.isEmpty) {
return 'required field';
} else if (email.trim().contains(' ')) {
return 'contain space';
} else if (!emailValid(email)) {
return 'invalid email';
}
return null;
},
onSaved: (email) {
return debugPrint(email);
},
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
formKey.currentState!.save();
}
},
child: const Text('Validate')),
],
),
),
);
}
}
bool emailValid(String email) {
final RegExp regex = RegExp(
r"^(([^<>()[\]\\.,;:\s#\']+(\.[^<>()[\]\\.,;:\s#\']+)*)|(\'.+\'))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$");
return regex.hasMatch(email.trim());
}

Error: Expected a value of type 'String', but got one of type 'Null' (in flutter)

I am trying to authenticate a user's login info in a flutter app (having a login page and signup page) using nodeJS and MongoDB, but whenever I enter a valid user and press login button, I get the following error:
Launching lib\main.dart on Chrome in debug mode...
This app is linked to the debug service: ws://127.0.0.1:50690/e09XNGpEkmc=/ws
Debug service listening on ws://127.0.0.1:50690/e09XNGpEkmc=/ws
Running with sound null safety
Connecting to VM Service at ws://127.0.0.1:50690/e09XNGpEkmc=/ws
Flutter Web Bootstrap: Auto
All validations passed
Error: Expected a value of type 'String', but got one of type 'Null'
at Object.throw_ [as throw] (http://localhost:50635/dart_sdk.js:5080:11)
at Object.castError (http://localhost:50635/dart_sdk.js:5039:15)
at Object.cast [as as] (http://localhost:50635/dart_sdk.js:5356:17)
at String.as (http://localhost:50635/dart_sdk.js:46240:19)
at authservice.AuthService.new.login (http://localhost:50635/packages/flutterform/authservice.dart.lib.js:39:67)
at login.throw (<anonymous>)
at http://localhost:50635/dart_sdk.js:40646:38
at _RootZone.runBinary (http://localhost:50635/dart_sdk.js:40515:59)
at _FutureListener.thenAwait.handleError (http://localhost:50635/dart_sdk.js:35449:33)
at handleError (http://localhost:50635/dart_sdk.js:36015:51)
at _Future._propagateToListeners (http://localhost:50635/dart_sdk.js:36041:17)
at [_completeError] (http://localhost:50635/dart_sdk.js:35878:23)
at [_completeError] (http://localhost:50635/dart_sdk.js:35358:36)
at _SyncCompleter.new.completeError (http://localhost:50635/dart_sdk.js:35282:29)
at onError (http://localhost:50635/dart_sdk.js:35122:49)
at _RootZone.runBinary (http://localhost:50635/dart_sdk.js:40515:59)
at _FutureListener.then.handleError (http://localhost:50635/dart_sdk.js:35449:33)
at handleError (http://localhost:50635/dart_sdk.js:36015:51)
at _Future._propagateToListeners (http://localhost:50635/dart_sdk.js:36041:17)
at [_completeError] (http://localhost:50635/dart_sdk.js:35878:23)
at async._AsyncCallbackEntry.new.callback (http://localhost:50635/dart_sdk.js:35927:31)
at Object._microtaskLoop (http://localhost:50635/dart_sdk.js:40778:13)
at _startMicrotaskLoop (http://localhost:50635/dart_sdk.js:40784:13)
at http://localhost:50635/dart_sdk.js:36261:9
Application finished.
Exited (sigterm)
I am running the code in VSCode editor and my server is running in Heroku.
Here are my codes:
From main.dart:
import 'package:flutter/material.dart';
import '../../signup.dart';
import '../../login.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of our application.
#override
Widget build(BuildContext context) {
return MaterialApp(
// Application name
title: 'Registration Form',
// Application theme data, we can set the colors for the application as
// you want
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: MySignUpPage(),
routes: {
'register': (context) => MySignUpPage(),
'login': (context) => MyLoginPage(),
},
);
}
}
From login.dart:
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../authservice.dart';
class MyLoginPage extends StatefulWidget {
#override
LoginForm createState() {
return LoginForm();
}
}
class LoginForm extends State<MyLoginPage> {
final formKey = GlobalKey<FormState>();
final passwordController = TextEditingController();
final confirmpasswordController = TextEditingController();
var name, password, token;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// The title text which will be shown on the action bar
title: Text('Registration'),
),
body: Container(
padding: EdgeInsets.symmetric(
vertical: 50.0,
horizontal: 10.0,
),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/signupimg.jpg"),
fit: BoxFit.cover,
),
),
child: Form(
key: formKey,
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.all(2),
padding: const EdgeInsets.all(8.0),
child: const Align(
alignment: Alignment.topLeft,
child: Text(
'Login',
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.left,
))),
Container(
margin: EdgeInsets.all(2),
padding: const EdgeInsets.all(8.0),
child: TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (String? val) {
if (val == null || val.isEmpty) {
return 'Username cannot be empty';
} else {
name = val;
}
return null;
},
decoration: InputDecoration(
filled: true, //<-- SEE HERE
fillColor: Colors.white,
labelText: 'Username',
hintText: 'Your Username',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
)),
),
),
Container(
margin: EdgeInsets.all(2),
padding: const EdgeInsets.all(8.0),
child: TextFormField(
obscureText: true,
controller: passwordController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (String? val) {
if (val == null || val.isEmpty) {
return 'Password cannot be empty';
} else if (val.length < 6) {
return 'Password must be at least 6 characters long.';
} else {
password = val;
}
return null;
},
decoration: InputDecoration(
filled: true, //<-- SEE HERE
fillColor: Colors.white,
labelText: 'Password',
hintText: 'Your password',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
)),
),
),
Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(10),
child: ElevatedButton(
child: Text(
"Login",
style: TextStyle(fontSize: 20),
),
onPressed: () {
AuthService().login(name, password).then((val) {
if (val.data['success'] != null) {
token = val.data!['token'];
Fluttertoast.showToast(
msg: 'Authenticated',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.white,
textColor: Colors.white,
fontSize: 16.0,
);
}
});
if (formKey.currentState!.validate()) {
debugPrint('All validations passed');
}
style:
ElevatedButton.styleFrom(
primary: Colors.orange, // Background color
onPrimary: Colors.black, //Text Color (Foreground color)
elevation: 3, //elevation of button
shape: RoundedRectangleBorder(
//to set border radius to button
borderRadius: BorderRadius.circular(35)),
);
},
),
),
Container(
margin: EdgeInsets.all(10),
child: Text(
"Don't have an account?",
style: TextStyle(fontSize: 16),
),
),
Container(
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, 'register');
},
child: Text("Sign up",
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: 18,
color: Colors.orange,
))))
],
),
),
),
);
}
}
From authservice.dart (which holds the login function and connection details to my server):
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:fluttertoast/fluttertoast.dart';
class AuthService {
Dio dio = new Dio();
login(String name, String password) async {
try {
return await dio.post(
'https://flutter-collegeapp.herokuapp.com/authenticate',
data: {"name": name, "password": password},
options: Options(contentType: Headers.formUrlEncodedContentType));
} on DioError catch (e) {
Fluttertoast.showToast(
msg: (e.response?.data['msg']),
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.white,
textColor: Colors.orange[900],
fontSize: 16.0,
);
}
}
}
I have followed this youtube tutorial to write this code (have made some changes due to new updates in flutter)
https://www.youtube.com/watch?v=2D_76lkyF1c&t=4903s
Can someone please tell me what is wrong and why this error is occuring?

Flutter speech_to_text not listening on device

I'm building a flutter voice to text app and using the speech_to_text: ^5.5.0 plugin, but when I run the code on the android emulator I get the error:
onError: SpeechRecognitionError msg: error_speech_timeout, permanent: true
I have made the changes to the AndroidManifest file according to the documentation (API 32). Could anyone point out any mistakes in the code or if I should use some alternate plugin. Thanks.
Code:
class _SpeechScreenState extends State<SpeechScreen> {
final Map<String, HighlightedWord> _highlights = {
'hello': HighlightedWord(
onTap: () => print('hello'),
textStyle: const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
};
late stt.SpeechToText _speech;
bool _isListening = false;
String _text = 'Press the button and start speaking';
double _confidence = 1.0;
#override
void initState() {
super.initState();
_speech = stt.SpeechToText();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Confidence: ${(_confidence * 100.0).toStringAsFixed(1)}%'),
centerTitle: true,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AvatarGlow(
animate: _isListening,
glowColor: Theme.of(context).primaryColor,
endRadius: 75.0,
duration: const Duration(milliseconds: 2000),
repeatPauseDuration: const Duration(milliseconds: 100),
repeat: true,
child: FloatingActionButton(
onPressed: _listen,
child: Icon(_isListening ? Icons.mic : Icons.mic_none),
),
),
body: SingleChildScrollView(
reverse: true,
child: Container(
padding: const EdgeInsets.fromLTRB(30.0, 30.0, 30.0, 150.0),
child: TextHighlight(
text: _text,
words: _highlights,
textStyle: const TextStyle(
fontSize: 32.0,
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
),
),
);
}
void _listen() async {
if (!_isListening) {
bool available = await _speech.initialize(
onStatus: (val) => print('onStatus: $val'),
onError: (val) => print('onError: $val'),
);
if (available) {
setState(() => _isListening = true);
_speech.listen(
onResult: (val) => setState(() {
_text = val.recognizedWords;
if (val.hasConfidenceRating && val.confidence > 0) {
_confidence = val.confidence;
}
}),
);
}
} else {
setState(() => _isListening = false);
_speech.stop();
}
}
}

How to prevent TextFormField redirecting to previous screen?

I am trying to create form.
I managed to create every widget in it, but every time I try to open TextFormField I get redirected back to my MainMenuScreen without any error.
I am using BLoC and routes. I think that issue might be related with using named routes.
Issue was not spotted before changing to named routes
MainMenuScreen fragment:
CategoryCard(
categoryName: 'Main dishes',
assetPath: 'assets/images/main_dish.png',
onPressed: () => Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider.value(
value: BlocProvider.of<RecipesBloc>(context)
..add(LoadRecipesEvent())
..category = 'main_dish',
child: RecipesScreen(),
);
})),
),
From MainMenuScreen I redirect to RecipesScreen
Fragment of RecipesScreen with redirect to RecipeCreateForm:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) {
return RecipeCreateForm();
}),
),
and then I redirect to RecipeCreateForm where I'm using TextFormFields.
Whenever I try to use TextFormField I get redirected back to MainMenuScreen.
class RecipeCreateForm extends StatefulWidget {
#override
_RecipeCreateFormState createState() => _RecipeCreateFormState();
}
class _RecipeCreateFormState extends State<RecipeCreateForm> {
final _recipeNameController = TextEditingController();
final _imageUrl = TextEditingController();
String? _difficultyValue;
late int _ingredientsQuantity;
late int _preparationStepsQuantity;
late List<Ingredient> _ingredientsValues;
late List<PreparationStep> _preparationStepsValues;
late double _preparationTime;
String? _portions;
#override
void initState() {
_ingredientsQuantity = 1;
_preparationStepsQuantity = 1;
_ingredientsValues = [];
_preparationStepsValues = [];
_preparationTime = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
body: Scrollbar(
thickness: 10,
hoverThickness: 2,
child: SingleChildScrollView(
child: Container(
color: Colors.lightGreen.shade100,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Recipe name',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextFormField(
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
controller: _recipeNameController,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Image',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextFormField(
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
controller: _imageUrl,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Difficulty',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
DropdownButton(
hint: _difficultyValue == null
? Text(
'Select difficulty',
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
)
: Text(
_difficultyValue!,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
items: ['Easy', 'Medium', 'Hard'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
_difficultyValue = val as String;
},
);
},
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Preparation time',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Slider(
value: _preparationTime,
onChanged: (newPreparationTime) {
setState(() => _preparationTime = newPreparationTime);
},
label: _preparationTime.toStringAsFixed(0),
min: 0,
max: 360,
divisions: 24,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Ingredients',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
SizedBox(
height: 175,
child: Scrollbar(
child: ListView.builder(
itemCount: _ingredientsQuantity,
itemBuilder: (context, index) {
return _ingredientRow(index);
}),
),
),
Row(
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
setState(() {
_ingredientsQuantity++;
});
}),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
setState(() {
_ingredientsQuantity = 1;
_ingredientsValues.clear();
});
})
],
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Preparation steps',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Scrollbar(
child: SizedBox(
height: 100,
child: ListView.builder(
shrinkWrap: true,
itemCount: _preparationStepsQuantity,
itemBuilder: (context, index) {
return _preparationStepRow(index);
}),
),
),
Row(
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
setState(() {
_preparationStepsQuantity++;
});
}),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
setState(() {
_preparationStepsQuantity = 1;
_preparationStepsValues.clear();
});
}),
],
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Portions',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
DropdownButton(
hint: _portions == null
? Text(
'Select number of portions',
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
)
: Text(
_portions!,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
items: ['1', '2', '3', '4', '5', '6', '7'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
_portions = val as String;
},
);
},
),
ElevatedButton(
onPressed: () {
BlocProvider.of<RecipesBloc>(context).add(
AddRecipeEvent(
Recipe(
name: _recipeNameController.text,
image:
'https://www.thespruceeats.com/thmb/dA8o8EZpjJyeocYZNpzfknoKh2s=/4351x3263/smart/filters:no_upscale()/baked-stuffed-potatoes-482217-hero-01-850f2d87fe80403f923e140dbf5f1bf3.jpg',
ingredients: _ingredientsValues,
difficulty: _difficultyValue,
preparationTime: _preparationTime,
preparationSteps: _preparationStepsValues,
type: BlocProvider.of<RecipesBloc>(context)
.category
.toString(),
portions: _portions,
),
),
);
Navigator.of(context).pop();
},
child: Text('Submit'),
),
],
),
),
),
),
);
}
_ingredientRow(int key) {
return IntrinsicHeight(
child: Row(
children: [
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 225,
child: TextFormField(
maxLength: 35,
onChanged: (val) {
setState(() {
_onIngredientUpdate(key,name: val);
});
},
),
),
VerticalDivider(
width: 20,
thickness: 1,
color: Colors.black,
indent: 30,
endIndent: 10,
),
SizedBox(
width: 55,
child: TextFormField(
maxLength: 7,
initialValue: '0',
onChanged: (val) {
setState(() {
_onIngredientUpdate(key, quantity: val);
});
},
),
),
Padding(padding: EdgeInsets.only(left: 10)),
DropdownButton(
hint: Text('pcs'),
items: ['pcs', 'ml', 'g'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(() {
_onIngredientUpdate(key,measurement: val.toString());
});
},
)
],
),
);
}
_onIngredientUpdate(int key, {String? name, String? measurement, String? quantity}) {
int foundKey = -1;
_ingredientsValues.forEach((element) {
if (element.id.contains(key.toString())) {
foundKey = key;
}
});
if (-1 != foundKey) {
_ingredientsValues.removeWhere((map) {
return map.id == foundKey.toString();
});
}
Map<String, dynamic> json = {'id': key, 'name': name, 'measurement': measurement, 'quantity':quantity};
_ingredientsValues.add(json as Ingredient);
}
_preparationStepRow(int key) {
return IntrinsicHeight(
child: Row(
children: [
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 225,
height: 50,
child: TextFormField(
maxLength: 35,
onChanged: (val) => {
_onPreparationUpdate(key,val)
},
),
),
],
),
);
}
_onPreparationUpdate(int key, String val) {
int foundKey = -1;
_preparationStepsValues.forEach((element) {
if (element.id.contains(key.toString())) {
foundKey = key;
}
});
if (-1 != foundKey) {
_preparationStepsValues.removeWhere((map) {
return map.id == foundKey.toString();
});
}
Map<String, dynamic> json = {'id': key, 'step': val};
_preparationStepsValues.add(json as PreparationStep);
}
}
Issue GIF:
EDIT:
Issue is not related with form. I have replaced whole form with only one field without any logic and issue remains.
It is probably related to named routes.
As I was thinking, issue was related with usage of named routes.
I managed to bypass this issue with using Future.delayed and pushNamedAndRemoveUntil
In main_menu_screen I have created method which I later used to redirect to categories.
void redirectToCategory(BuildContext context, String categoryName) {
Future.delayed(Duration.zero, () {
Navigator.pushNamedAndRemoveUntil(
context,
'/recipeScreen',
(_) => false,
arguments: BlocProvider.value(
value: BlocProvider.of<RecipesBloc>(context)
..add(LoadRecipesEvent())
..category = categoryName,
child: RecipesScreen(),
),
);
});

Show value from BLE device to Heart Widget in flutter App

I want to show my readings that are coming from Esp32 to my Heart Widget that is displayed on HomeScreen.
I used the example code of flutter_blue package to check whether readings are coming to the app, but I am having difficulty understanding the code as I am new to Flutter.
My Bluetooth Connection Screen looks like this, which shows the list of nearby BLE devices
When I press Connect Button it goes to this screen showing the services, descriptors, and MTU size.
As clearly seen in the picture that the last service is getting values from ESP32, I want to show the values under Characteristic to show in Heart Rate Widget, where 96 is written. But I can't understand how I should display the value of the characteristic on this screen and not on the above screen.
These are the values that I am getting on the console as well, which are correct. I just want that when I press the connect button of ESP32, it should connect instantly and instead of going to 2nd screen it should go to the home screen and show values inside the heart rate widget
My code for Bluetooth Scanning Devices Screen is:
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'BluetoothConnectBand.dart';
import 'widgets.dart';
class FlutterBlueApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
//backgroundColor:Colors.lightBlue ,
body: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key key, this.state}) : super(key: key);
final BluetoothState state;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
appBar:AppBar(
backgroundColor: Colors.lightBlue,
elevation: 0,
leading:IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BluetoothConnectBand();
},
),
);
},
),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
style: Theme.of(context)
.primaryTextTheme
.subhead
?.copyWith(color: Colors.white),
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xffE5E0A1),
elevation: 0,
centerTitle: true,
title: Text(
"Connect Band",
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
fontFamily: 'Montserrat',
fontWeight: FontWeight.normal,
),
),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BluetoothConnectBand();
},
),
);
},
),
),
body: RefreshIndicator(
onRefresh: () =>
FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: size.height * 0.4,
width: size.width,
color: const Color(0xffE5E0A1),
child: Image.asset(
'assets/images/bluetooth.png',
)),
Container(
width: size.width,
padding: EdgeInsets.symmetric(vertical: 20),
child: Text(
"Scanning Available Devices...",
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Montserrat',
),
textAlign: TextAlign.center,
),
),
//Expanded(child:_buildListViewOfDevices()),
],
),
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map((d) => ListTile(
title: Text(d.name,
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
fontWeight: FontWeight.w600,
fontFamily: 'Montserrat',
),
),
subtitle: Text(d.id.toString(),
style: TextStyle(
fontSize: 13.0,
color: Colors.black,
fontWeight: FontWeight.w500,
fontFamily: 'Montserrat',
),
),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data ==
BluetoothDeviceState.connected) {
return FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
),
child: Text('Connected'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString(),
style: TextStyle(
color: Colors.green,
),);
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return DeviceScreen(device: r.device);
})),
),
)
.toList(),
),
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 4)),
backgroundColor: const Color(0xffE5E0A1),);
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key key, this.device}) : super(key: key);
final BluetoothDevice device;
List<int> _getRandomBytes() {
final math = Random();
return [
math.nextInt(255),
math.nextInt(255),
math.nextInt(255),
math.nextInt(255)
];
}
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
return services
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
onReadPressed: () => c.read(),
onWritePressed: () async {
await c.write(_getRandomBytes(), withoutResponse: true);
await c.read();
},
onNotificationPressed: () async {
await c.setNotifyValue(!c.isNotifying);
await c.read();
},
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
onReadPressed: () => d.read(),
onWritePressed: () => d.write(_getRandomBytes()),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name),
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = 'DISCONNECT';
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = 'CONNECT';
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return FlatButton(
onPressed: onPressed,
child: Text(
text,
style: Theme.of(context)
.primaryTextTheme
.button
?.copyWith(color: Colors.white),
));
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(
'Device is ${snapshot.data.toString().split('.')[1]}.'),
subtitle: Text('${device.id}'),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
title: Text('MTU Size'),
subtitle: Text('${snapshot.data} bytes'),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data),
);
},
),
],
),
),
);
}
}
Code for Widgets is the same as written in the code example, I have only changed main file for my UI.
Code for Heart Rate Widget:
Center(
child: Container(
height: size.height * 0.190,
width: size.width * 0.80,
padding: EdgeInsets.all(11.0),
child: Card(
color: const Color(0xffe8e5af),
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
alignment: Alignment.centerLeft,
child: const SpinKitPumpingHeart(
color: Colors.white,
)
//Image.asset(
// 'assets/images/heart_button.png',
// ),
),
Container(
padding: EdgeInsets.symmetric(vertical: 20),
alignment: Alignment.center,
width: size.width * 0.80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'96',
style: TextStyle(
fontFamily: 'SF Pro Display',
fontSize: 19,
color: const Color(0xffffffff),
fontWeight: FontWeight.w500,
height: 1.4736842105263157,
),
textHeightBehavior: TextHeightBehavior(
applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
SizedBox(
height: 5,
),
Text(
'Heart Rate',
style: TextStyle(
fontFamily: 'SF Pro Display',
fontSize: 19,
color: Colors.white.withOpacity(0.7),
fontWeight: FontWeight.w500,
height: 1.2777777777777777,
),
textHeightBehavior: TextHeightBehavior(
applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
],
),
)
],
),
),
),
)
One way to solve it is to use a Provider.
With one class
import 'package:flutter_blue/flutter_blue.dart';
class BlueDevice extends ChangeNotifier {
/// Internal, private state of the Device.
BluetoothDevice _device;
BluetoothService _service;
/// Device metrics
int heartRate;
...
/// Adds device to model. This is the only way to modify the device from outside.
void add(BluetoothDevice d) async {
_device = d;
await _device.connect();
_device.discoverServices();
_device.services.listen((lista) {
for (int iService = 0; iService < lista.length; iService++) {
if (lista[iService].uuid.toString().startsWith("YOUR UUID")) {
_service = lista[iService];
}
}
});
// This line tells [Model] that it should rebuild the widgets that
// depend on it.
notifyListeners();
}
void read() async {
List<List> values = [];
if (_service != null) {
for (BluetoothCharacteristic c in _service.characteristics) {
values.add(await c.read());
}
}
heartRate = values[0][0];
notifyListeners();
}
}
need to create the provider
class FlutterBlueApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// In this app, BlueDevice is implemented as a ChangeNotifier,
// which calls for the use of ChangeNotifierProvider.
create: (context) => BlueDevice(),
child: Scaffold(...
and you may use it with a consumer
Consumer<BlueDevice>(
builder: (context, blue, child) => Text(blue.heartRate.toString(),
//'96', THIS WAS THE VALUE
style: TextStyle(
fontFamily: 'SF Pro Display',
fontSize: 19,
color: const Color(0xffffffff),
fontWeight: FontWeight.w500,
height: 1.4736842105263157,
),

Categories

Resources