i stored phone numbers in a firebase document(table). What i want is to detect if the number already existed by using validators and display a message under a textbox that the phone number is already exists i had no problem with this , my problem is a have to double tap the button to execute to complete the task.
var _formKey = GlobalKey<FormState>();
var validate;
String validateNumber(String value){
if (value.isEmpty) {
return 'Please enter number';
}if(validate == true){
return "Phone number already exists";
}
return null;
}
addPhoneNumber(phoneNumber) async{
var exist = await db.checkPhoneNumber(phoneNumber); //my query to firestore so far i have no problem with this.
if(exist == true){
validate = true;
print(validate);
}
if(exist == false){
validate = false;
Navigator.of(context).push(_verifyPhone(_phoneNumber.text));
await db.addPhoneNumber(phoneNumber);
}
} //my function to detect if the number exists
#override
Widget build(BuildContext context) {
_get1();
return WillPopScope(
onWillPop: () async {
Navigator.pop(context);
return true;
},
child: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
key:_formKey,
child:Expanded(
child:Scrollbar(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
TextFormField(
controller: _phoneNumber,
validator: validateNumber, //my validator
),
],
),
),
),
),
FlatButton(
onTap: (){
if(_formKey.currentState.validate()){
addPhoneNumber(phoneNumber)
}
},
)
],
),
),
);
}
}
You need to call the validate() method on your form state. Look here for API reference.
This is not invoked automatically by the framework for you. You need to invoke it inside the onTap() method of your button. Add there a line _formKey.currentState.validate().
When this method is invoked then the form calls the validator for each form field.
---------------------------UPDATE---------------------------------------
Ok, #ppdy, you are one step closer now. It doesn't work yet because your validator only checks if the value is not empty. Just look carefully at what happens when you push the button. It runs the validateNumber and if the value is empty the framework will render your validate message. Then if the value is not empty you run the addPhoneNumber method, but you run it yourself. This is important, notice that it is not get run as part of the text form field validator property function. So you need to handle the output of the await db.checkPhoneNumber(phoneNumber); yourself and render the validation red message in the text form field if the check is false.
For this please note first that the TextFormField class has the decoration property. Type of this property is the InputDecoration. Please read the documentation of this class here. So by setting the errorText property of the decoration you will be able to add the red validation message to the form field manually.
Try to think it all over, play with it, stick this all together and you will have what you want :)
Related
What is the difference between TextField and TextFormField in flutter? Both look the same. Which one should i use? Which one do you recommend? I use TextField like this:
const TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
)
A TextFormField mainly has the validator argument, while TextField doesn't: it must return null if the input is valid, and it must return a String if there's some error (usually the String itself contains information about the error). This argument allows you to validate the user input in a very easy way, provided that you include the form fields (there are others, in addition to TextFormField) in a Form widget and that you apply a Key to the Form.
If you do everything correctly, you could just invoke formKey.currentState!.validate() and automatically Flutter will invoke the validator argument for each form field that you added to the Form. If everything checks, the validate will return true, and you can proceed with your program logic. Otherwise, it will return false and it will show the String returned by the validator near the form field that contained incorrect data.
This example is taken from the Flutter cookbook on forms:
[...]
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
),
],
),
);
}
}
You use TextFormField when using Form widget. Combined with Form widget you can make more complex forms + validation for whole form.
TextField is basically a TextFormField but you don't have to include it into the Form widget and validation would work a bit differently.
I am building a flashChat app using FireBase .It is basically a chat app that can be used to send messages .And the messages are stored inside the FireBase collection .We have to retrieve and display the messages on the screen.
To display the contents(snapshots) I am using Streams .
I am getting the following error
error: The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. (body_might_complete_normally at [flashchat1] lib\screens\chat_screen.dart:80)
Here is my Code:-
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flashchat1/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static String id='Chat_Screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _fireStore = FirebaseFirestore.instance;//an instance of fireBase store that stored data created
final _auth = FirebaseAuth.instance;//instance/object of fireBase auth that authorizes users is created
late User loggedInUser;//LoggedInUser is of type FireBase user(now changed to user)
late String messageText;
#override
void initState()
{
super.initState();
getCurrentUser();//calling the getCurrentUser
}
void getCurrentUser()
async{
try
{
final user= await _auth.currentUser;//get the current user id/name.Also currentUser return a future so make it async by adding await and async keywords
if(user!=null)
{
loggedInUser=user ;//LoggedInUser = user contains email of the users
print(loggedInUser.email);
}
}
catch(e)
{
print(e);
}
}// Under collection there is documents.Inside documents there are fields like type ,values etc.These fields contain our information
Future<void> messageStream()//Using a stream it becomes very easy .U just need to click once after you run the app .Then u will be done.
async {//The snapShot here is FireBase's Query SnapShot
await for(var snapshot in _fireStore.collection('messages').snapshots()){//make a variable snapshot to store the entire items of the collection in fireBase (Look at the fireBase console there is a collection called messages).This collection takes the snapshot of all the iteams (not literal snapshot .Think it like a snapShot)
for(var message in snapshot.docs)//make a variable message to access the snapShot.docs .(docs stands for Documentation.Look at the fireBase console)
print(message.data());
}
}
void getMessages()//(The problem with this is that we need to keep clicking on the onPressed button every single time the new message is sent .So it is not convinient
async {
final messages = await _fireStore.collection('messages').get();//to retrieve the data from fire base we are creating a variable message
messages.docs;//retreive the data from document section under the collection in firestore
for(var message in messages.docs)//since it is a messages.docs is a list we need to loop through it
{
print(message.data());//print the data its messge.data()
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
messageStream();
//_auth.signOut();
//Navigator.pop(context);
//Implement logout functionality
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder(
stream:_fireStore.collection('messages').snapshots(),
builder: (context, AsyncSnapshot snapshot) {//This is Flutter's Async snapShot
if(snapshot.hasData){//flutters async snapshot contains a query snapshot
final messages = snapshot.data.docs;
List<Text> messageWidgets = [];
for(var message in messages)//Loop through the messages
{
final messageText = message.data['text'];//retrieve the data under the text field in message collection
final messageSender = message.data['Sender'];//retrieve the data under the Sender field in message collection
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(
children: messageWidgets,
);
}
},
),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
onChanged: (value) {
messageText=value;//Whatever you chat will be stored in the variable String variable messageText
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
_fireStore.collection('messages').add({
'text': messageText,//add the messages sent to fireStore under the messages object that we created manually
'Sender': loggedInUser.email,//add the current users email to the sender field
},);
},//goal is to send the data that we type here to the fireStore cloud
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
You don't return anyting in your StreamBuilder when the if condition is not resolved...
Just add a else with, say... SizedBox.shrink() or whatever you need
Or return an empty Column
I am building a table_calendar widget, on which I want to select multiple days and keep them in firestore. (Selected days are green, everything else white)
I get the values from firestore via stream provider and use ChangeNotifier to read those values where I need to and I use _selectedDays.addAll (from table_calendar) to add all the values onto the calendar. My problem is that sometimes, when I try to deselect a day, it get's removed from firebase, but the ChangeNotifier listener gets refreshed with the old value before it gets refreshed with the new value, meaning that the day remains selected, even though it shouldn't be.
When I am halting a test listener.
final events = watch(todoChangeNotifierProvider).todos;
I can see that 60-70% of the times it gets the new value, but sometimes it refreshes with the old one, screwing up the table_calendar ui.
Why does this happen?
I have the provider declared globally
final todoChangeNotifierProvider = ChangeNotifierProvider.autoDispose<TodosProvider>((ref) {
return TodosProvider();
});
And TodosProvider looks like this
class TodosProvider extends ChangeNotifier {
final FirebaseAuth auth = FirebaseAuth.instance;
List<Todo> _todos = [];
void setTodos(List<Todo> todos) {
_todos = todos;
}
I am setting the Change Notifier value inside the stream, so if something changes it gets updated.
EDIT: More code
Setting the ChangeNotifer with stream value
Widget build(BuildContext context,ScopedReader watch) {
final todoProvider = watch(todoChangeNotifierProvider);
//Todo? currentTodo = Todo();
final firebaseAuth = context.read(firebaseAuthProvider);
final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('DASHBOARD'),
),
body: todoStream.when(
data: (data) {
final currentTodo = data;
todoProvider.setTodos(currentTodo);
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(10, 10, 0, 55),
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center, //Center Column contents horizontally,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center, //Center Row contents horizontally,
children: [Text('${currentUser!.loginStreak}',style: TextStyle(fontSize: 20)), Icon(Icons.local_fire_department, size: 40,color: Color(0xFFFD8787),), Text('DAILY ACTIVITY STREAK', style: TextStyle(fontSize: 20),)],),
],
),
Column(
children: [
TodoListWidget(editAllowed: true, currentUser: currentUser,)
],
)
Reading the provider
class TodoListWidget extends ConsumerWidget {
final UserData? currentUser;
final editAllowed;
TodoListWidget({this.editAllowed, this.currentUser});
#override
Widget build(BuildContext context, ScopedReader watch) {
final todoProvider = watch(todoChangeNotifierProvider);
final todos = todoProvider.todos;
return todos.isEmpty
...
selecting the day, marking it, and updating firebase
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
setState(() {
if (_selectedDays.contains(selectedDay)) {
docRef.update({
'calendarEvents': FieldValue.arrayRemove([selectedDay])
});
userPointsRef.update({'points': FieldValue.increment(-1)});
habitPointsRef
.update({'daysTrackedThisWeek': FieldValue.increment(-1)});
_selectedDays.remove(selectedDay);
} else {
//INCREMENTS POITNS AND DAYS TRACKED ON SELECTED DAY
docRef.update({
'calendarEvents': FieldValue.arrayUnion([selectedDay])
});
userPointsRef.update({'points': FieldValue.increment(1)});
habitPointsRef.update({'daysTrackedThisWeek': FieldValue.increment(1)});
_selectedDays.add(selectedDay);
}
});
enter code here
To try and paint a better picture.
I get the table_calendar values from firebase via a stream.
I set the values to a ChangeNotifier
I read the ChangeNotifier (But it seems, that 40% of the time, when a new value comes it first refreshes with the old value, before it gets the new one (that messes up the ui)
This is how I set the values for table_calendar selected days
in TodoWidget
Widget build(BuildContext context) {
_selectedDays.addAll(widget.todo!.calendarEvents);
The values come from TodoListWidget
You have to use notifyListeners with ChangeNotifier. When you are watching the provider, it only knows to pick up a new value if notifyListeners is called.
The reason you are getting a runtime error when doing so is that you are triggering an update during the initial build.
In your ChangeNotifier, add the call to notifyListeners:
void setTodos(List<Todo> todos) {
_todos = todos;
notifyListeners();
}
In your widget code:
Widget build(BuildContext context,ScopedReader watch) {
final firebaseAuth = watch(firebaseAuthProvider);
final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('DASHBOARD'),
),
body: todoStream.when(
data: (currentTodo) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
context.read(todoChangeNotifierProvider).setTodos(currentTodo);
});
return SingleChildScrollView(...);
}
The PostFrameCallback does exactly what it sounds like - delays the code from being run until the next frame, which will eliminate the runtime error you were facing.
I'm using a StreamProvider to capture changes that occur in Firestore and display them on the user homepage. When the user initially signs in or registers, they're directed to this homepage and expect to see their details, for which I use the StreamProvider.
Keeping in mind that the first value fetched will be null, I've added a Loading Text Widget. However, the widget does not seem to change unless I hot-refresh the page and the Stateful Widget gets rebuilt again.
What seems to be happening is that the Stateful Widget I'm using as my HomePage gets "built" only once due to which even when the stream gets populated with non-null values, the loading widget does not disappear because its parent widget doesn't get rebuilt.
This is the basic HomeScreen widget around which I'm wrapping my StreamProvider.
#Override
Widget build(BuildContext context) {
return StreamProvider<UserDataModel>.value(
initialData: null,
value: DatabaseServices(userDetails: _userDetails).userDataDocument,
updateShouldNotify: (previous, current) => true,
child: Scaffold(
backgroundColor: Colors.white,
body: HomeBody(),
)
);
}
This is the relevant snippet from the HomeBody widget
class HomeBody extends StatefulWidget {
#override
_HomeBodyState createState() => _HomeBodyState();
}
class _HomeBodyState extends State<HomeBody> {
#override
Widget build(BuildContext context) {
final AuthenticationServices _authServices = AuthenticationServices();
final UserDataModel _data = Provider.of<UserDataModel>(context);
print(_data);
print((_data != null) ? _data.name : 'NULL');
/* Some code */
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 0, 0, 5),
child: Text(_data != null ? _data.name : 'LOADING...', style: dashboardName),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 2, 0, 5),
child: Text(_data != null ? _data.scholarId : 'LOADING...', style: dashboardName),
),
],
),
I also noticed that the print statements I've used for debugging get executed only once printing those initial null values, until, I hot-refresh the page and the non-null values are shown.
Is there a way to go around this? I've been stuck for a couple of days now without making any significant advances and any sort of guidance would be appreciated.
I am working on a login system, where i authenticate user by OTP ,Here i want to disable the Resend OTP button for 30 seconds every time the user clicks it and show the time remaining
if you want to have a live counter for showing the user the seconds past you should use stream builder
StreamBuilder(
stream: _timerStream.stream,
builder: (BuildContext ctx,
AsyncSnapshot snapshot) {
return SizedBox(
width: 300,
height: 30,
child:RaisedButton(
textColor: Theme.of(context)
.accentColor,
child: Center(
child:
snapshot.data == 0 ?
Text('send code again')
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(' button will be enable after ${snapshot.hasData ? snapshot.data.toString() : 30} seconds '),
],)
),
onPressed: snapshot.data == 0 ? () {
// your sending code method
_timerStream.sink.add(30);
activeCounter();
} : null,
)
);
},
)
you can find complete code on dartpad.dev with this link
Declare boolean onPressedValue variable with true,
Add Condition in onPressed Parameter.
bool onPressedValue=true;
RaisedButton(
child: Text('OTP'),
onPressed: onPressedValue==true?(){
setState((){
onPressedValue=false;
});
Timer(Duration(seconds: 30),(){
setState((){
onPressedValue=true;
});
});
}:null)
You can try this
Declare a variable call like this globally
bool shouldButtonEnabled=true;
then on click of send OTP button call this method while you other stuff like sending OTP call this method after it
_disabledButton(){
shouldButtonEnabled=false;
Timer(
Duration(seconds: 30),
() => shouldButtonEnabled=true);
}
and when check this bool on resend OTP button like this
onPressed: () {
if(shouldButtonEnabled){
//do your work here
}
}