How to make Speech To Text stop when the user stop talking? - flutter

I'm trying to make an icon in the textfield that is supposed to stop and convert into mic_none when the user has finished saying the text , but that it doesn't happen.
What happens is that the text reception stops, but the icon does not return to its outliend form, but rather I have to click on it to convert it into mic_none.
I would appreciate any help from you
TextEditingController? _directionController;
String text = "";
bool isListening = false;
bool isListening1 = false;
#required
Function(String text)? onResult;
#required
ValueChanged<bool>? onListening;
static final _speech = SpeechToText();
void toggleRecording() async {
if (!isListening1) {
bool isAval = await _speech.initialize(
onStatus: (status) => onListening!(_speech.isListening),
onError: (e) => print('Error: $e'),
);
if (isAval) {
setState(() {
isListening1 = true;
});
// it is for recognaization
_speech.listen(
onResult: (value) => setState(() {
_directionController!.text = value.recognizedWords;
addRecipe.userDirections[widget.index] =
value.recognizedWords;
// onResult!(value.recognizedWords);
}));
}
} else {
setState(() {
isListening1 = false;
_speech.stop();
});
}
}
Here is the build of my code:
Widget build(BuildContext context) {
// run this method when the interface has been loaded
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
_directionController!.text = addRecipe.userDirections[widget.index] ?? '';
});
return TextFormField(
controller: _directionController,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: toggleRecording,
icon: Icon(
isListening1 ? Icons.mic : Icons.mic_none,
color: Color(0xFFeb6d44),
),
),
hintText: 'Enter a direction'), // errorText: _errorText
onChanged: (value) {
addRecipe.userDirections[widget.index] = _directionController!.text;
// setState(() {}); //used to refresh the screen //OLD
},
validator: (value) {
if (value!.trim().isEmpty) return 'Please enter a direction';
return null;
},
);
}

You can use _speech.isListening instead of your isListening1
Icon(
_speech.isListening? Icons.mic : Icons.mic_none,
color: Color(0xFFeb6d44),
),

Related

How to limit text field with a value in flutter?

I want to limit the value that user input not to over a expected value in flutter text field.
Example,
If there is a value come from API is 10 and want to limit the input not over 10, if client type 11 or something over 10, want to show alert or make user not to type.
How to control this?
TextFormField(
style: TextStyle(fontSize: 20),
onChanged: (value) {
if (value != "") {
int _checkValue = int.parse(value);
if (_checkValue >
Provider.of<SaleProvider>(context, listen: false)
.remainNewQuantity(
this.currentProductItemSelected.id)) {
return 'error';
} else {
setState(() {
this.qty = int.parse(value);
updateByQty();
});
}
} else {
setState(() {
});
}
},
),
This is my trying, but can't do that I want.
Please check below method. I think this will resolve your issue. If still not work, please let me know
Widget getTextField({required int maxValue}) {
return TextFormField(
controller: _textController,
keyboardType: TextInputType.number,
onChanged: (text) {
if (int.parse(text) > maxValue) {
// show popup here.
_textController.text = validText;
_textController.selection = TextSelection.fromPosition(TextPosition(offset: _textController.text.length));
}else{
validText = text;
}
},
);
}
As an exercise for my own learning I gave it a go and came up with the following approach; creating a bespoke widget which admittedly looks like a lot of code for something so simple... but it appears to work as expected I think and one could modify, expand and integrate it with other elements in various ways.
Usage: const MaxIntField(max: 100),
Implementation:
class MaxIntField extends StatefulWidget {
const MaxIntField({Key? key, this.max = 1}) : super(key: key);
final int max;
#override
State<MaxIntField> createState() => _MaxIntFieldState();
}
class _MaxIntFieldState extends State<MaxIntField> {
final TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.value.copyWith(text: '0');
_controller.addListener(() {
if (_controller.text.isNotEmpty && _controller.text != '0') {
int intVal = int.parse(_controller.text);
if (intVal > widget.max) {
setState(() {
_controller.value =
_controller.value.copyWith(text: widget.max.toString());
_showMyDialog();
});
} else if (_controller.text != intVal.toString()) {
//remove leading '0'
setState(() {
_controller.value =
_controller.value.copyWith(text: intVal.toString());
});
}
}
});
}
// assuming using Material
_showMyDialog() async {
showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('AlertDialog Title'),
content: Text('This field is limited to ${widget.max}'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
);
}
}

How to make speech to text Icons toggle in all text fields of dynamic text fields

I'm trying build a dynamic text fields with speech to text icon in each text field
When I click on the speech to text icon in the second text field, the toggle is applied on the icon in the first field. I want to toggle the icon that is in the same text field that I want to write the text. How can I do that?
This is my speechto text code:
import 'package:flutter/cupertino.dart';
import 'package:speech_to_text/speech_to_text.dart';
class SpeechApi {
static final _speech = SpeechToText();
static Future<bool> toggleRecording({
#required Function(String text) onResult,
#required ValueChanged<bool> onListening,
}) async {
if (_speech.isListening) {
_speech.stop();
return true;
}
final isAvailable = await _speech.initialize(
onStatus: (status) => onListening(_speech.isListening),
onError: (e) => print('Error: $e'),
);
if (isAvailable) {
// it is for recognaization
_speech.listen(onResult: (value) => onResult(value.recognizedWords));
}
return isAvailable;
}
}
The dynamic text fields code:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_ingredientController.text = addRecipe.userIngredients[widget.index];
});
return TextFormField(
controller: _ingredientController,
decoration: InputDecoration(
//============================
suffixIcon: AvatarGlow(
animate: isListening,
endRadius: 25,
glowColor: Colors.orange,
child: IconButton(
onPressed: toggleRecording,
icon: Icon(
isListening ? Icons.mic : Icons.mic_none,
color: Color(0xFFeb6d44),
),
),
),
//---------------------
hintText: 'Enter an ingredient'), //errorText: _errorText
onChanged: (value) {
addRecipe.userIngredients[widget.index] = value;
},
validator: (value) {
if (value.trim().isEmpty) {
return 'Please enter an ingredient';
}
return null;
},
);
}
Future toggleRecording() => SpeechApi.toggleRecording(
onResult: (text) =>
setState(() => this._ingredientController.text = text),
onListening: (isListening) {
setState(() => this.isListening = isListening);
});
This is a picture of the interface.
As you can see, the cursor in the third field but the toggle always applies on the first field icon

Showing Error Text on TextField in ShowDialog

I am trying to build an AlertDialog to reauthenticate user before deleting account. I would like to show error text when reauthentication does not work.
The errors are handled correctly as I can see by using print() statements. However I cannot get the TextField to show the error as a feedback to the user. I think it must be something to do with the state of the SimpleDialog itself, rather than the state of the outer widget...
Here is the code:
Future _validate({String email, String password}) async {
setState(() {
_wrongEmail = false;
_wrongPassword = false;
_emailErrorText = null;
_passwordErrorText = null;
});
try {
if (await userDataBase.checkCorrectEmail(
email: email, loggedInUser: widget.user.id)) {
throw FirebaseAuthException(
message: 'email is not correct', code: 'user-not-found');
}
await userDataBase.authenticateUser(email, password);
} catch (e) {
if (e.code == 'wrong-passwod') {
setState(() {
_passwordErrorText = 'Password not Correct';
});
} else if (e.code == 'user-not-found') {
setState(() {
_emailErrorText = 'Email not Correct';
});
} else {
setState(() {
_emailErrorText = 'Email not Correct';
_passwordErrorText = 'Password not Correct';
_wrongPassword = true;
_wrongEmail = true;
});
}
throw e;
}
}
Future _authenticate({String email, String password}) async {
try {
await _validate(email: email, password: password);
} catch (e) {
print(e.code);
print(_wrongEmail);
print(_emailErrorText);
return;
}
print('correct behaviour');
// Navigator.pop(context);
// setState(() {
// _loading = true;
// });
// await userDataBase.deleteUser(widget.user.id);
// Navigator.of(context).popUntil((route) => route.isFirst);
}
Future _confirmIdentity() {
final emailController = TextEditingController();
final passwordController = TextEditingController();
_emailErrorText = null;
_passwordErrorText = null;
return showDialog(
context: context,
builder: (context) => AlertDialog(
content: Container(
height: 200,
child: Column(
children: [
Text('Confirm Your Identity'),
TextField(
controller: emailController,
decoration: InputDecoration(
errorText: _wrongEmail ? _emailErrorText : null,
labelText: 'email'),
),
TextField(
controller: passwordController,
decoration:
InputDecoration(
errorText: _wrongPassword ? _passwordErrorText : null,
labelText: 'password'
),
),
Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FlatButton(
onPressed: () {
_authenticate(
email: emailController.text,
password: passwordController.text);
},
child: Text('Confirm'),
),
FlatButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel'),
),
],
)
],
),
),
),
);
}
Thank you. I can post all the widget code ecc. if you need it: it is a statefull widget.
For 1st 2 if conditions, you are setting the error message, but not setting the respective boolean flags to true.
Example:
if (e.code == 'wrong-passwod') {
setState(() {
_passwordErrorText = 'Password not Correct';
});
}
Here, in the setState()function, you will also need to set _wrongPassword = true;
I fixed the issue. The boolean variables _wrongPassword, _wrongEmail are not necessary seen as when the _PasswordErrorText or _EmailErrorText are null, the error text is not displayed by default.
It is necessary to build a statefull widget that is the content of ShowDialog! then everything works.

How to update variable from TextFormField in flutter?

I have TextFormField which has its own controller. I also have a disabled button that I would like to enable after any input inside TextFormField. The problem is that I managed to enable the button however only when I close the keyboard. For example, if I'm typing a word in a session it won't enable the button unless I close the keyboard. I would like to enable the button after the first character.
This is the code:
final TextEditingController passwordController = new TextEditingController();
TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: passwordController,
),
RaisedButton(
onPressed: (passwordController.text== "") ? null : () {
setState(() {
_isLoading = true;
});
signIn(email, passwordController.text);
},
),
Is this what you're looking for?
final TextEditingController _controller = TextEditingController();
bool _isLoading = true;
// it tracks if TextField is enabled or disabled
bool _enabled = false;
#override
void initState() {
super.initState();
_controller.addListener(() { // you need to add listener like this
setState(() {
if (_controller.text.length > 0)
_enabled = true;
else
_enabled = false;
});
});
}
Widget build(context) {
return Scaffold(
body: Column(
children: <Widget>[
SizedBox(height: 50),
TextFormField(controller: _controller),
RaisedButton(
onPressed: !_enabled ? null : () => setState(() => _isLoading = true),
),
],
),
);
}
Based on what you're looking for, I created a boolean called isEnabled and set the initial value to false. This should work for you!
bool isEnabled;
final TextEditingController passwordController = new TextEditingController();
#override
void initState() {
super.initState();
passwordController.addListener(() {
setState(() {
if (passwordController.text.length > 0) {
isEnabled = true;
} else {
isEnabled = false;
}
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: passwordController,
),
RaisedButton(
child: Text('Click Me'),
onPressed: isEnabled ? () {
//print('Button has been pressed');
setState(() {
_isLoading = true;
});
signIn(email, passwordController.text);
} : null,
),
],
),
);
}
This worked:
var password = '';
TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: passwordController,
onChanged: (value){
setState(() {
password = value;
});
},
),
RaisedButton(
onPressed: (password == "") ? null : () {
setState(() {
_isLoading = true;
});
signIn(email, password);
},
),

How to clear error message in TextFormField in Flutter

In my code I validate phone number. If phone number is incorrect - I show error message. But, when user starts to edit number I want to hide this error message.
I've found the solution with currentState.reset(), but it seems not the good one. I have to handle issues with saving text and cursor position. And I still have one small artifact. Normally when I press and hold backspace - it deletes symbols one by one. If I do it when error message is shown - then error message disappears and only one symbol is deleted.
Does anybody know the right solution for this case?
final TextEditingController controller = TextEditingController();
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
TextSelection currentPosition;
return Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
),
onChanged: () {
if (controller.selection.start < 0 &&
controller.text.length > 0) {
TextSelection position =
controller.text.length > currentPosition.start
? currentPosition
: TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
controller.selection = position;
}
if (isError) {
isError = false;
currentPosition = controller.selection;
if (currentPosition.start > controller.text.length) {
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
}
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
}
},
),
RaisedButton(
onPressed: () {
_textKey.currentState.validate();
},
child: Text(login),
)
],
);
EDIT (Nov 2020)
autovalidate was deprecated after v1.19.0.
Instead use autovalidateMode:
Form(
autovalidateMode: AutovalidateMode.onUserInteraction`.
...
)
Original post
here is a suitable solution to this problem.
You don't actually need to use onChanged or any tips causing side-effects, I solved it by creating a class property which is initialized to false:
bool _autovalidate = false;
The Form Widget has a named property autovalidate. You should pass it the previous boolean:
Form(
key: _textKey,
autovalidate: _autovalidate,
...
)
And in your Submit button onPressed() method, you should update the _autovalidate boolean to be true if the form is invalid, this will make the form to auto validate the TextFormField on every onChanged call:
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = true);
}
},
child: Text(login),
)
I hope it helped Somebody.
January 2021
...
AutovalidateMode _autoValidate = AutovalidateMode.disabled;
Form(
key: _textKey,
autovalidateMode: _autovalidate,
...
)
RaisedButton(
onPressed: () {
if (_textKey.currentState.validate()) {
print('valid');
} else {
print('invalid');
setState(() => _autoValidate = AutovalidateMode.always);
}
},
child: Text("login"),
)
The problem here is errorText is automatically managed by the validator field of the TextFormField. At the same time, the simple solution is to handle the errorText manually.
Step 1: Create
String field, _errorText initialised to null. The field will hold the error message that needs to be shown.
Boolean field, _error initialised to false. The filed is true if there is an error otherwise false.
Step 2:
Assign _errorText to TextFormField
Step 3 (Important):
Make sure that TextFormField validator returns a null value.
Handle the validation here and assign the proper error message to _errorText.
Update _error state correspondingly.
Step 4 (Important):
Reset _errorText and _error. This will remove the error from field soon as you start editing.
Step 5:
Trigger field validation in the onFieldSubmitted and manage your code flow...
import 'package:flutter/material.dart';
class WorkGround extends StatefulWidget {
#override
_WorkGroundState createState() => _WorkGroundState();
}
class _WorkGroundState extends State<WorkGround> {
final _formKey = GlobalKey<FormState>();
final _usernameFocusNode = FocusNode();
final _phoneNumberFocusNode = FocusNode();
/*
* Step 1.
* */
String _userNameErrorText;
bool _userNameError = false;
String _phoneNumberErrorText;
bool _phoneNumberError = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
focusNode: _usernameFocusNode,
decoration: InputDecoration(
labelText: 'Username',
/*
* Step 2
* */
errorText: _userNameErrorText, // Handling error manually
),
textInputAction: TextInputAction.next,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_userNameError = true;
_userNameErrorText = 'Enter Username';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_userNameError = false;
_userNameErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_userNameError) {
FocusScope.of(context).requestFocus(_phoneNumberFocusNode);
}
},
),
TextFormField(
focusNode: _phoneNumberFocusNode,
decoration: InputDecoration(
labelText: 'Phone Number',
/*
* Step 2
* */
errorText: _phoneNumberErrorText, // Handling error manually
),
textInputAction: TextInputAction.done,
/*
* Step 3
* */
validator: (value) {
setState(() {
if(value.isEmpty) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Enter Phone number';
} else if( value.length < 10) {
_phoneNumberError = true;
_phoneNumberErrorText = 'Invalid Phone number';
}
});
return null; // Return null to handle error manually.
},
/*
* Step 4
* */
onChanged: (value) {
setState(() {
_phoneNumberError = false;
_phoneNumberErrorText = null; // Resets the error
});
},
/*
* Step 5
* */
onFieldSubmitted: (value) {
_formKey.currentState.validate(); // Trigger validation
if(!_phoneNumberError) {
// submit form or whatever your code flow is...
}
},
),
],
),
),
);
}
}
I have achieved your both below functionality:
1) Hide error message when editing
2) validate input field when login button pressed
Note: i have commented phone number regex and put validation for
string length < 10 digit for testing.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
}
final TextEditingController controller = TextEditingController();
// final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
bool isWriting = false;
bool isLoginPressed = false;
int counter = 0;
String myErrorString = "";
TextSelection currentPosition;
final _textKey = GlobalKey<FormState>();
#override
Widget build(BuildContext ctx) {
return Scaffold(
appBar: AppBar(
title: Text('MapSample'),
),
body: Container(
child: Column(
children: <Widget>[
Form(
key: _textKey,
child: TextFormField(
controller: controller,
validator: (str) {
myErrorString = "";
if(isLoginPressed){
isError = true;
if (str.isEmpty) {
myErrorString = 'err_empty_field';
return myErrorString;
}
else if (str.length < 10) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}
/*else if (!_phoneRegex.hasMatch(str)) {
myErrorString = 'err_invalid_phone';
validateMe();
return myErrorString;
}*/
isError = false;
myErrorString = "";
}else{
myErrorString = "";
}
},
),
onChanged: () {
counter++;
if(counter == 9){
counter = 0;
isLoginPressed = false;
}
if(isLoginPressed){
}else{
isWriting = true;
isLoginPressed = false;
myErrorString = "";
_textKey.currentState.validate();
}
},
),
RaisedButton(
onPressed: () {
counter = 1;
isWriting = false;
isLoginPressed = true;
_textKey.currentState.validate();
},
child: Text('login'),
)
],
),
),
);
}
void validateMe() {
if(isLoginPressed){
currentPosition = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
String currentText = controller.text;
_textKey.currentState.reset();
controller.text = currentText;
controller.selection = currentPosition;
isWriting = false;
isLoginPressed = true;
}
}
}
I've found working and easier way
final _textKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();
Widget _getPhoneInputForm() {
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10,17}");
bool isError = false;
bool isButtonPressed = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 36.0),
child: Form(
key: _textKey,
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: hint_enter_phone,
contentPadding: EdgeInsets.all(24.0),
fillColor: Colors.blueGrey.withOpacity(0.3),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
borderSide: BorderSide(color: Colors.blueGrey))),
controller: _controller,
validator: (str) {
if (!isButtonPressed) {
return null;
}
isError = true;
if (str.isEmpty) {
return err_empty_field;
} else if (!_phoneRegex.hasMatch(str)) {
return err_invalid_phone;
}
isError = false;
},
onFieldSubmitted: (str) {
if (_textKey.currentState.validate()) _phoneLogin();
},
),
onChanged: () {
isButtonPressed = false;
if (isError) {
_textKey.currentState.validate();
}
},
),
),
RaisedButton(
color: Colors.teal,
textColor: Colors.white,
onPressed: () {
isButtonPressed = true;
if (_textKey.currentState.validate()) _phoneLogin();
},
child: Text(login),
)
],
);
}
This is an exemple , i think its not necessary to do onchange() , the function validate name do the work ...
String validateName(String value) {
String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Name is Required";
} else if (!regExp.hasMatch(value)) {
return "Name must be a-z and A-Z";
}
return null;
}
TextFormField(
controller: _lastname, validator: validateName ,
//initialValue: widget.contact.last_name,
decoration:
InputDecoration(labelText: 'Last name'),
),
void Save() {
if (_keyForm.currentState.validate()) {
// No any error in validation
_keyForm.currentState.save();
................
}
i have found that using a combination of FocusNode and AtuoValidateMode.onUserInteraction does the trick.
class _TextAutoValidateModeExampleState extends State<TextAutoValidateModeExample> {
FocusNode node = FocusNode();
#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
focusNode: node,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if(node.hasFocus) return null;
if (value!.isEmpty) return "value cannot be empty";
if (!value.isEmail) return "not a valid email";
},
),
);
}
}
// Call this method inside onChanged() and when its focusnode hasFocus
void formReset(GlobalKey<FormState> formKey, TextEditingController controller) {
String stringValue = controller.text;
TextPosition textPosition = controller.selection.base;
formKey.currentState.reset();
controller.text = stringValue;
controller.selection = TextSelection.fromPosition(textPosition);
}
this format worked for me, Hope it helps someone....
validator: (value){
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value);
isError = true;
if(value.isEmpty){
return "Provide an email";
}else if(!emailValid){
return "Enter a valid email";
}
isError = false;
return null;
},