using FutureBuilder to get Future<String> from firestore - flutter

This is my code in which I want to display an email which is a Future and I will get it from my firestore.However, I am not sure how I will need to retrieve my value using FutureBuilder which I want to use as a string.
This is my method to get my email:
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
DocumentSnapshot snapshot = await _firestore.collection('users')
.document(_email)
.collection('met_with')
.document('email')
.get();
// print("data: ${snapshot.data}"); // might be useful to check
return snapshot.data['email']; // or another key, depending on how it's saved
}
this is my updated code:
#override
Widget build(BuildContext context) {
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
child: ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage(imagePath),
),
trailing: Icon(Icons.more_horiz),
title: Text(
email,
style: TextStyle(
c #override
Widget build(BuildContext context) {
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
child: ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage(imagePath),
),
trailing: Icon(Icons.more_horiz),
title: Text(
email,
style: TextStyle(
color: Colors.deepPurple[700],
fontWeight: FontWeight.bold,
),
),
subtitle: Text(infection),
onTap: () => showModalBottomSheet(
context: context,
builder: (builder) {
return FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Padding(padding: EdgeInsets.symmetric(vertical: 50.0, horizontal: 10.0),
child: Column(
children: <Widget>[
BottomSheetText(
question: 'Email', result: snapshot.data['email']),
SizedBox(height: 5.0),
BottomSheetText(
question: 'Contact Time',result:"lol"),// getTimeStamp().toString()),
SizedBox(height: 5.0),
BottomSheetText(
question: 'Contact Location',
result: "help"),
SizedBox(height: 5.0),
BottomSheetText(question: 'Times Contacted', result: "lool",),
],
),
);
}
}else{
return CircularProgressIndicator();}
}
);
}
),
),
);
}
}
Here is my firebase database:
enter image description here

Your query is wrong, try following one.
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
var a = await Firestore.instance
.collection("met_with")
.where('email', isEqualTo:. _email )
.getDocuments();
return a.documents[0]['email'];
}
And to call this method you need futureBuilder.
FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Center( // here only return is missing
child: Text(snapshot.data['email'])
);
}
}else if (snapshot.hasError){
return Text('no data');
}
return CircularProgressIndicator();
},
),

You need to use a FutureBuilder:
FutureBuilder(
future: getEmail(),
builder: (BuildContext context, snapshot) {
if(snapshot.hasData){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}else{
return Center( // here only return is missing
child: Text(snapshot.data['email'])
);
}
}else if (snapshot.hasError){
Text('no data');
}
return CircularProgressIndicator();
},
),
This way you can use the returned value of the method inside the build method. Also change the Future method to the following:
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
return await _firestore.collection('users')
.document(_email)
.collection('met_with')
.document('email')
.get();
}

Related

how to return a form widget in futureBuild in flutter

I have this code as am trying to code something to update data in firestore.
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(mid.toString()),
),
body: FutureBuilder<Member?>(
future: readMember(mid),
builder: (context, snapshot) {
if (snapshot.hasData) {
final member = snapshot.data;
/// return a form
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
if snapshot hasData I want to return a form like this
Card(
child: Row(
children: <Widget>[
TextField(
controller: controllerName,
decoration: decoration('name'),
),
const SizedBox(height: 24),
TextField(
controller: controllerAge,
decoration: decoration('Age'),
keyboardType: TextInputType.number,
),
const SizedBox(height: 24),
ElevatedButton(
child: const Text('Create'),
onPressed: () {},
),
],
));
All my attempt yield no success please I need help.
Check others state like error or if the data is null or not
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Got Error");
}
if (snapshot.data == null) {
return Text("No data");
}
if (snapshot.hasData) {
final member = snapshot.data;
return Card( ///here form
child: Row(
children: <Widget>[],
));
} else {
return const Center(child: CircularProgressIndicator());
}
},
And provide width on TextFiled to fix overflow, TextFiled and row are trying to get infinite with.
just wrap with Expanded
Expanded(child: TextField(...)),
You can find more about unbound height& width

Flutter: get a document from Firestore inside a widget that returns a Widget

In my build I have this StreamBuilder which will get the data of the posts
and map it into a method that will return a widget to build the posts
StreamBuilder<List<Post>>(
stream: readPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
print(snapshot.error.toString());
return Text(snapshot.error.toString());
} else {
final posts = snapshot.data;
print('from streambuilder');
return Column(
children: posts!.map(buildPost).toList(),
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
})
this is the readPosts function that provides the stream of the posts
Stream<List<Post>> readPosts() {
return FirebaseFirestore.instance
.collection('posts')
.where('availableFor', whereIn: ['All', 'Business Adminstration'])
.snapshots()
.map((snapshot) {
// print(snapshot);
return snapshot.docs.map((doc) {
// print(doc.data());
return Post.fromJson(doc.data());
}).toList();
});
}
and then the the list of posts are mapped into the buildPost function which will return the post widget
Widget buildPost(Post post) {
final organizations = getUserData(post.owner) //I want this final property to get an
Organization value as a return type
// however it is returning a Future<Organizations>
value
//is there any way I can use to convert
it to an Organizations type?
//and I want to keep this function as a
widget so that the stream builder
//does not give me an error
return Container(
padding: EdgeInsets.all(10),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
child: Image.network(post.imageUrl,
loadingBuilder: ((context, child, loadingProgress) {
return loadingProgress == null
? child
: LinearProgressIndicator();
}), height: 200, fit: BoxFit.fill
),
),
ListTile(
isThreeLine: true,
subtitle: Text(
'valid until: ${post.validUntil} for ${post.availableFor}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade400,
fontWeight: FontWeight.bold),
),
leading: ClipOval(
child: Container(
height: 30,
width: 30,
child: Image.network(organizations!.imageUrl,
loadingBuilder: ((context, child, loadingProgress) {
return loadingProgress == null
? child
: LinearProgressIndicator();
}), height: 200, fit: BoxFit.fill
),
),
),
title: Text(
post.description,
style: TextStyle(fontSize: 14),
),
trailing: Text(organizations!.id),
)
],
),
),
);
}
this is the getUserData function
Future<Organizations> getUserData(organizationId) async {
var organizations;
await FirebaseFirestore.instance
.collection('organizations')
.where('id', isEqualTo: organizationId)
.get()
.then((event) {
if (event.docs.isNotEmpty) {
Map<String, dynamic> documentData =
event.docs.single.data(); //if it is a single document
print(documentData.toString());
organizations = Organizations.fromJson(documentData);
}
}).catchError((e) => print("error fetching data: $e"));
return organizations;
}
is there a way to use the organizations data in build post method?

info: This function has a return type of 'Widget', but doesn't end with a return statement

I am trying to work out why I am getting this error when my Streambuilder has returns
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
import 'package:google_fonts/google_fonts.dart';
import 'package:paylaterapp/screens/MerchantDetails.dart';
class MerchantList extends StatefulWidget {
#override
_MerchantListState createState() => new _MerchantListState();
}
class _MerchantListState extends State<MerchantList> {
StreamController _postsController;
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
int count = 1;
Future fetchPost([howMany = 10]) async {
final response = await http.get('http://localhost/categories/fitness/$howMany');
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load merchant');
}
}
loadPosts() async {
fetchPost().then((res) async {
_postsController.add(res);
return res;
});
}
showSnack() {
return scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text('New content loaded'),
),
);
}
Future<Null> _handleRefresh() async {
count++;
print(count);
fetchPost(count).then((res) async {
_postsController.add(res);
showSnack();
return null;
});
}
#override
void initState() {
_postsController = new StreamController();
loadPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: Text('Explore Retailers',
style: GoogleFonts.rubik(
fontWeight: FontWeight.w700
)
),
actions: <Widget>[
IconButton(
tooltip: 'Refresh',
icon: Icon(Icons.refresh),
onPressed: _handleRefresh,
)
],
),
body: StreamBuilder(
stream: _postsController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
print('Has error: ${snapshot.hasError}');
print('Has data: ${snapshot.hasData}');
print('Snapshot Data ${snapshot.data}');
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.hasData) {
return Column(
children: <Widget>[
Expanded(
child: Scrollbar(
child: RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var post = snapshot.data[index];
return GestureDetector(
onTap: () {
print(post);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
GridMerchantDetails(post)),
);
},
child: Container(
height: 160,
margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
decoration: BoxDecoration(
color: Colors.grey,
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.5),
BlendMode.darken),
image: new NetworkImage(
post['assets']['backgroundimage_url'] != null
? 'https:' +
post['assets']['backgroundimage_url']
: 'https://images.unsplash.com/photo-1446844805183-9f5af45f89ee',
))),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FadeInImage.assetNetwork(
image: post['assets']['logo_url'] != null
? 'https:' + post['assets']['logo_url']
: 'https://images.unsplash.com/photo-1446844805183-9f5af45f89ee',
placeholder: 'assets/images/transparent.png',
width: 140,
height: 140,
fit: BoxFit.contain
)
]
)
)
);
},
),
),
),
),
],
);
}
if (!snapshot.hasData &&
snapshot.connectionState != ConnectionState.done) {
return Text('No Merchants');
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
This issue should be fixed if you add at the end of your StreamBuilder builder a fallback return in case all else fails:
...
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
// This line at the very end after the last `if` statement
return Center(child: Text('Data unavailable'));
You should also make sure you're accounting for every possibility of failure. With the connection and possible null data
You don't return anything if snapshot has no data, but connectionState is done.

_FutureBuilderState<dynamic>The getter 'length' was called on null. Receiver: null Tried calling: length

My method getting the data from db and displaying on the console. I tried several hints given in the other posts as well with no luck.
_getUsers() async {
print("getting");
var data = await http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
var jsonData = json.decode(data.body);
print(jsonData);
}
However the future builder not able to display:
new FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: new Text('Error ${snapshot.error}'),
);
} else {
return Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(56.0, 8.0, 56.0, 8.0),
//Here I guarded against the null as well:
child: ListView.builder(
itemCount: snapshot.data.length == null // showing error here
? 0
: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: new Text(
'${snapshot.data[index]["branch"]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
trailing: new Text(
'${snapshot.data[index]["count(`branch`)".toString()]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
);
}),
),
);
}
}),
How can I solve the issue?
you should return jsonData in _getUser().
getUsers() async {
print("getting");
var data = await http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
var jsonData = json.decode(data.body);
return jsonData;
}
, and change this
itemCount: snapshot.data.length == null // showing error here
? 0
: snapshot.data.length,
to this
itemCount: snapshot.data?.length ?? 0,
snapshot.data? checks whether the data is null or not. ?? executes its successor when the predecessor is null.
Your function doesn't return any Future, therefore the FutureBuilder is unable to get a Future to run on.
_getUsers() {
print("getting");
return http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
}
It needs to return a Future, you shouldn't be using await because the FutureBuilder depends on an actual Future, not data. You obtain the data inside the builder and then decode it.
new FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: new Text('Error ${snapshot.error}'),
);
} else if (snapshot.hasData) { // checking for data
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 56, vertical: 8),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: new Text(
'${snapshot.data[index]["branch"]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
trailing: new Text(
'${snapshot.data[index]["count(`branch`)".toString()]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
);
}),
),
);
}
}),

StreamBuilder snapshot.hasError show many times when keyboard show/hide flutter

I have a login screen and I'm using BloC pattern, but when i click on button the validation fails, the message from error is called many times, because the stream builder snapshot.error has a value, i don't know how change this to show error only when the user click at the button and validation in fact throw an error.
class LoginPage extends StatefulWidget {
static String tag = 'login-page';
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<LoginPage> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
LoginBloc loginBloc = BlocProvider.of(context).loginBloc;
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Colors.blueAccent,
Colors.blue,
]),
),
child: Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
elevation: 4.0,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.0, right: 16.0),
children: <Widget>[
/*_logo(),*/
SizedBox(height: 24.0),
_emailField(loginBloc),
SizedBox(height: 8.0),
_passwordField(loginBloc),
SizedBox(height: 24.0),
_loginButtonSubmit(loginBloc),
_loading(loginBloc),
_error(loginBloc),
_success(loginBloc),
_settingsText()
],
),
),
)),
);
}
Widget _logo() {
return Hero(
tag: 'hero',
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: Center(
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage('assets/4.0x/ic_launcher.png'),
),
borderRadius: BorderRadius.all(Radius.circular(50.0)),
),
),
),
),
);
}
Widget _emailField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
//Anytime the builder sees new data in the emailStream, it will re-render the TextField widget
return TextField(
onChanged: loginBloc.setEmail,
keyboardType: TextInputType.emailAddress,
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Usuário',
errorText: snapshot
.error, //retrieve the error message from the stream and display it
),
);
},
);
}
Widget _passwordField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: loginBloc.setPassword,
obscureText: true,
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Senha',
errorText: snapshot.error,
),
);
},
);
}
Widget _loginButtonSubmit(LoginBloc loginBloc) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
onPressed: () {
loginBloc.submit(
LoginRequest(_usernameController.text, _passwordController.text));
},
padding: EdgeInsets.all(12),
color: Colors.blue,
child: Text('Entrar', style: TextStyle(color: Colors.white)),
),
);
}
Widget _loading(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.loadingStream,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Center(
child: snapshot.data
? Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
)
: null,
);
});
}
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
Widget _success(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData && snapshot.data.erro == 0) {
_onWidgetDidBuild(() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => HomePage()));
});
}
return Container();
});
}
Widget _settingsText() {
return Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LoginSettingsPage()));
},
child: Padding(
padding: EdgeInsets.fromLTRB(16.0, 0, 16.0, 16.0),
child: Text(
"Configurações",
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
),
),
),
);
}
void _onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
Bloc
class LoginBloc with Validator {
//RxDart's implementation of StreamController. Broadcast stream by default
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _loadingController = BehaviorSubject<bool>();
final _successController = BehaviorSubject<LoginResponse>();
final _submitController = PublishSubject<LoginRequest>();
//Return the transformed stream
Stream<String> get emailStream => _emailController.stream.transform(performEmptyEmailValidation);
Stream<String> get passwordStream => _passwordController.stream.transform(performEmptyPasswordValidation);
Stream<bool> get loadingStream => _loadingController.stream;
Stream<LoginResponse> get successStream => _successController.stream;
//Add data to the stream
Function(String) get setEmail => _emailController.sink.add;
Function(String) get setPassword => _passwordController.sink.add;
Function(LoginRequest) get submit => _submitController.sink.add;
LoginBloc() {
_submitController.stream.distinct().listen((request) {
_login(request.username, request.password);
});
}
_login(String useName, String password) async {
_loadingController.add(true);
ApiService.login(useName, password).then((response) {
if (response.erro == 0) {
saveResponse(response);
} else {
final error = Utf8Codec().decode(base64.decode(response.mensagem));
_successController.addError(error);
print(error);
}
_loadingController.add(false);
}).catchError((error) {
print(error);
_loadingController.add(false);
_successController.addError("Falha ao realizar login!");
});
}
saveResponse(LoginResponse response) {
SharedPreferences.getInstance().then((preferences) async {
var urlSaved = await preferences.setString(
Constants.LOGIN_RESPONSE, response.toJson().toString());
if (urlSaved) {
_successController.add(response);
}
}).catchError((error) {
_successController.addError(error);
});
}
dispose() {
_emailController.close();
_passwordController.close();
_loadingController.close();
_successController.close();
_submitController.close();
}
}
I found the solution to my error when in click in InputField and focus change, the StreamBuilder rebuild de widget e show again the error every time.
I just put a validation before start shows the error to consider the state of the snapshot.
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.active &&
snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
If is active is because I throw an error in my Bloc class, if not, is because the stream builder was rebuilt the widget. This solves my problem.
I don't know if it is the better solution but solves my problem at the moment.
I had same problem, adding initialdata null works fine for me.
return new StreamBuilder(
stream: _bloc.stream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
According Flutter Docs hasError validate null value.