Getting null value (Firestore query result) in Flutter app - firebase

I have an application and In this i'm making a query for get user details by the e-mail account.
I'm using Future class to get data and fill my variable but the widget Text always show null value.
Please let me now if i am doing something wrong.
class _HomePageAppState extends State<HomePageApp> {
String _emailUsuario;
Usuario usuario;
void initState() {
super.initState();
Autenticacao().getCurrentUser().then((user) {
setState(() {
if (user != null) {
_emailUsuario = user.email.toString(); //the user email is returnig correctly
recuperarDadosUsuarioFirebase().then((ds) {
usuario = Usuario(
email: _emailUsuario,
nome: ds['nome'] != null ? ds['nome'] : null,
);
});
}
});
});
}
Future<DocumentSnapshot> recuperarDadosUsuarioFirebase() async {
DocumentSnapshot ds;
await Firestore.instance
.collection('usuarios')
.document(_emailUsuario)
.get()
.then((DocumentSnapshot _ds) {
ds = _ds;
});
return ds;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.white10,
child: ListView(
children: <Widget>[
Text('Bem vindo ${usuario.nome} !!!'),
],
),
),
);
}
}

U might want to use Future Builder for such async work cause build method was called before usuario is assign so like this :
FutureBuilder(
future: getCurrentUser(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
// after getting data
},
);
Method getCurrentUser() needs to be created :)

Related

Firebase snapshot documentations. I am following a tutorial but i can't figure out how to make this change

Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.orange,
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: FutureBuilder(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data!.docs, // i am getting an error here
);
},
),
),
);
}
}
2nd error case:
User user = User.fromDocument(snapshot.data()); // i am getting error here
3rd case : `
List<UserResult> userResults = [];
snapshot.data!.docs.forEach((doc) {
User user = User.fromDocument(doc);
final bool isAuthUser = currentUser.id == user.id;
final bool isFollowingUser = followingList.contains(user.id);
// remove auth user from recommended list
if (isAuthUser) {
return;
} else if (isFollowingUser) {
return;
} else {
UserResult userResult = UserResult(user);
userResults.add(userResult);
}
});
Here on snapshot i am getting error in every scenario. I don't understand how to change the snapshot formation i should do enter image description here
Instead of .documents, try .doc.

Stack Overflow error when grabbing data from Firebase in Flutter

I'm trying to grab data from firebase (users collection -> uid document-> Summoner Info collection -> id document -> summonerName field) and display the summonerName's rank. Below is the screen that is causing the error:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:first_project/network/api.dart';
import 'package:first_project/screens/summoner_search.dart';
import 'package:flutter/material.dart';
import 'set_summoner_name.dart';
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
static const id = '/mainScreen';
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
CollectionReference usersCollection =
FirebaseFirestore.instance.collection('users');
final FirebaseAuth _auth = FirebaseAuth.instance;
late User loggedInUser;
bool summonerExists = false;
void getCurrentUser() {
try {
final user = _auth.currentUser;
if (user != null) {
loggedInUser = user;
print(loggedInUser.email);
}
} catch (e) {
print(e);
}
// here you write the codes to input the data into firestore
}
#override
void initState() {
getCurrentUser();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
actions: [
IconButton(
onPressed: () => Navigator.pushNamed(context, SummonerSearch.id),
icon: Icon(Icons.search_off_rounded),
),
],
),
body: Center(
child: StreamBuilder(
stream: usersCollection
.doc(_auth.currentUser!.uid)
.collection('Summoner Info')
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
// checkIfSummonerExists(snapshot);
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
print('Reached Here!');
print(snapshot.data!.docs[0].data().toString());
return ListView(
children: snapshot.data!.docs.map((document) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListTile(
title: Text('Name:' + document['summonerName']),
),
Card(
child: FutureBuilder<dynamic>(
future: DataModel()
.getWholeRank(document['summonerName']),
builder: (context, snapshot) {
String tier;
String rank;
try {
//if successful, the player is ranked and has data
if (snapshot.hasData) {
tier = snapshot.data![0]['tier'];
rank = snapshot.data![0]['rank'];
} else {
return CircularProgressIndicator();
}
if (tier == 'CHALLENGER' || tier == 'MASTER') {
rank = '';
}
return Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(tier),
SizedBox(width: 2.0),
Text(rank),
],
),
);
} catch (e) {
//if unsuccessful call from api, means the player is unranked and json is empty
return Center(
child: Text('Unranked'),
);
}
},
),
),
],
);
}).toList(),
);
},
),
),
),
);
}
}
In the code above, I notice I am getting the Stack Overflow error starting at the 'Card' line about 3/4 of the way down, which is where I grab the data from the database and fetch the data from the API. If I comment all of that out and just display the summonerName, I get no error.
For the API functions as reference, below here is the code of the getWholeRank method
Future<dynamic> fetchRank(String name) async {
name = removeSpaces(name);
String id = await fetchByName(name, 'id');
NetworkHelper networkHelper = NetworkHelper(
'https://na1.api.riotgames.com/lol/league/v4/entries/by-summoner/$id?api_key=$api_key');
var rankData = await networkHelper.getRankData();
return rankData;
}
Future<dynamic> getWholeRank(summonerName) async {
var rankData = await rankObj.fetchRank(summonerName);
return rankData;
}
and below this is my NetworkHelper class:
import 'package:http/http.dart' as http;
import 'dart:convert';
class NetworkHelper {
NetworkHelper(this.url);
final String url;
Future getData({String ch = 'default'}) async {
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
String data = response.body;
// print(data);
var decodedData = jsonDecode(data);
if (ch == 'default') {
print(decodedData);
return decodedData; //returns map of data
} else {
//Options: id, accountID, name, puuid, profileIconID, revisionDate, summonerLevel,
print(decodedData[ch]);
return decodedData[ch];
}
} else {
print('Status code: ');
print(response
.statusCode); //if doesn't work, it will print status code (200 is good, 400 etc. is bad)
}
}
Future getRankData({String ch = 'default'}) async {
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
String data = response.body;
var decodedData = jsonDecode(data);
// print(decodedData[0]['tier']);
return decodedData;
} else {
print('Failed! Status code: ');
print(response
.statusCode); //if doesn't work, it will print status code (200 is good, 400 etc. is bad)
}
}
}
If anyone could help me understand why I'm getting the stack overflow error, it would be much appreciated!!
So I fixed the error, and it was because I was passing an object into the future argument and in the stack trace, there were lots of DataModel objects being initialized, causing the stack overflow. I fixed it in the getWholeRank method by replacing rankObj, a DataModel object, to the 'this' keyword.

The getter 'uid' was called on null

Guys I have this login page with email and password and when the user is succesfully logged it takes him to a chatscreen. On the chatscreen I need to access users uid to check who is sending the message, but I am getting the "The getter 'uid' was called on null.". I am using bloc pattern to login with validators. How can I fix this situation?
class LoginScreen extends StatefulWidget {
FirebaseUser user;
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _loginBloc = LoginBloc();
#override
void initState() {
super.initState();
_loginBloc.outState.listen((state) async {
switch (state) {
case LoginState.SUCCESS:
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => ComercialUserScreen()));
break;
case LoginState.FAIL:
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Erro'),
content: Text('Revise seu email e senha'),
));
break;
case LoginState.LOADING:
case LoginState.IDLE:
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<LoginState>(
stream: _loginBloc.outState,
initialData: LoginState.LOADING,
// ignore: missing_return
builder: (context, snapshot) {
print(snapshot.data);
switch (snapshot.data) {
case LoginState.LOADING:
return Center(
child: CircularProgressIndicator(),
);
case LoginState.FAIL:
case LoginState.SUCCESS:
case LoginState.IDLE:
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
InputField(
icon: Icons.person_outline,
hint: 'Usuário',
obscure: false,
stream: _loginBloc.outEmail,
onChanged: _loginBloc.changeEmail,
),
InputField(
icon: Icons.lock_outline,
hint: 'Senha',
obscure: true,
stream: _loginBloc.outEmail,
onChanged: _loginBloc.changePassword,
),
SizedBox(
height: 32,
),
StreamBuilder<bool>(
stream: _loginBloc.outSubmitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text("Entrar"),
onPressed:
snapshot.hasData ? _loginBloc.submit : null,
);
})
],
);
}
}),
);
}
}
class LoginBloc extends BlocBase with LoginValidators{
FirebaseUser _currentUser;
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _stateController = BehaviorSubject<LoginState>();
Stream<String> get outEmail => _emailController.stream.transform(validateEmail);
Stream<String> get outPassword =>_passwordController.stream.transform(validatePassword);
Stream<LoginState> get outState => _stateController.stream;
Stream<bool> get outSubmitValid => Observable.combineLatest2(
outEmail, outPassword, (a, b) => true);
Function(String) get changeEmail => _emailController.sink.add;
Function(String) get changePassword => _passwordController.sink.add;
StreamSubscription _streamSubscription;
LoginBloc(){
_streamSubscription = FirebaseAuth.instance.onAuthStateChanged.listen((user) async {
if(user != null) {
if(await verifyAdmins(user)) {
_stateController.add(LoginState.SUCCESS);
} else {
FirebaseAuth.instance.signOut();
_stateController.add(LoginState.FAIL);
}
} else {
_stateController.add(LoginState.IDLE);
}
});
}
void submit (){
final email = _emailController.value;
final password = _passwordController.value;
_stateController.add(LoginState.LOADING);
FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password
).catchError((e) {
_stateController.add(LoginState.FAIL);
});
}
Future<bool> verifyAdmins (FirebaseUser user) async {
return await Firestore.instance.collection('users').document(user.uid).get().then((doc)
{
if(doc.data != null){
return true;
} else {
return false;
}
}).catchError((e) {
return false;
});
}
#override
void dispose() {
_emailController.close();
_passwordController.close();
_stateController.close();
_streamSubscription.cancel();
// TODO: implement dispose
}
}
class _AdminChatState extends State<AdminChat> {
bool _isLoading = false;
void _sendMessage({String text, File imgFile}) async {
DocumentSnapshot snapshot;
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Map<String, dynamic> data = {
"uid" : user.uid,
'name' : user.displayName,
'photo' : user.photoUrl,
'time' : Timestamp.now()
};
if (imgFile != null){
StorageUploadTask task = FirebaseStorage.instance.ref().child('users').child(
DateTime.now().millisecondsSinceEpoch.toString()
).putFile(imgFile);
setState(() {
_isLoading = true;
});
StorageTaskSnapshot taskSnapshot = await task.onComplete;
String url = await taskSnapshot.ref.getDownloadURL();
data['imgUrl'] = url;
setState(() {
_isLoading = false;
});
}
if(text != null) data["text"] = text;
Firestore.instance.collection("users").document(snapshot.documentID)
.collection('messages').add(data);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('hello'),
),
body: Column(
children: <Widget>[
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream:
Firestore.instance.collection('users').document(widget.snapshot.documentID)
.collection('messages').orderBy('time').snapshots(),
builder: (context, snapshot){
switch(snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
List<DocumentSnapshot> documents =
snapshot.data.documents.reversed.toList();
return ListView.builder(
itemCount: documents.length,
reverse: true,
itemBuilder: (context, index){
// ignore: unrelated_type_equality_checks
return ChatMessage(documents[index].data, true);
});
}
},
),
),
_isLoading ? LinearProgressIndicator() : Container(),
TextComposer(_sendMessage),
],
),
);
}
}
The command await FirebaseAuth.instance.currentUser(); in the _AdminChatState doesn't return any data so when you try to insert data based on the user is returns null. This happens because you can't use await while the state hasn't initialized. If that happened the entire class would wait with nothing show until the user was returned. Furthermore, you use a storage query with the same logic. If you want a state widget to run something when it starts up you can use the initState() function. If you want do display a progress bar while waiting for the data to return a FutureBuilder or StreamBuilder are great choices.

How to retrieve a Firebase Storage image stream in flutter?

I've got a few photo's I've uploaded into my firebase storage under a file called 'photos' and I want to be able to retrieve them onto my app through a stream. I have done this before through Firebase cloud database by tapping into the Firestore.instance.collection('messages').snapshots() property in my StreamBuilder, but I don't know how to access the firebase storage snapshots and upload them as a stream into my app.
This was my code for the messages snapshot, I hope it helps:
final _firestore = Firestore.instance;
void messagesStream() async {
await for (var message in _firestore.collection('messages').snapshots()){
for (var snapshot in message.documents){
print(snapshot.data);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('messages').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(
child: CircularProgressIndicator(backgroundColor: Colors.lightBlueAccent,),
);
} else {
final messages = snapshot.data.documents;
List<Text> messageWidgets = [];
for (var message in messages){
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(children: messageWidgets,);
}
}
),
),
},
So I figured out you can't create a stream from the firebase storage, but what I could do was, in my firebase cloud database, start a new collection called 'my_collection' and in a new document, create an auto-ID, with a field called 'image' which is a string, with an http reference to an image that is on the internet, or one you can upload to the internet (this is what I did on imgur.com, credit to them)! Here is my code below, I hope it helps others! If it doesn't, have a look at this code written by iampawan, he helped me a tonne!
https://github.com/iampawan/FlutterWithFirebase
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
StreamSubscription<QuerySnapshot> subscription;
List <DocumentSnapshot> myList;
final CollectionReference collectionReference = Firestore.instance.collection('my_collection');
final DocumentReference documentReference = Firestore.instance.collection('my_collection').document('GFWRerw45DW5GB54p');
#override
void initState() {
super.initState();
subscription = collectionReference.snapshots().listen((datasnapshot) {
setState(() {
myList = datasnapshot.documents;
});
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return myList != null ?
ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index){
String imgPath = myList[index].data['image'];
return MyCard(assetImage: Image.network(imgPath), function:
(){
if (imgPath == myList[0].data['image']){
Navigator.pushNamed(context, MyMenu.id);
} else if (imgPath == myList[1].data['image']){
Navigator.pushNamed(context, YourMenu.id);
} else if (imgPath == myList[2].data['image']){
Navigator.pushNamed(context, HisMenu.id);
} else if (imgPath == myList[3].data['image']){
Navigator.pushNamed(context, HerMenu.id);
}
},);
})
: Center(child: CircularProgressIndicator(),
);
}
}
Just to note, MyCard is it's own page with it's own constructor that requires an assetImage and a function for the user to be pushed to a new screen:
MyCard({#required this.assetImage, #required this.function});
final Image assetImage;
final Function function;

Simple Flutter sqflite login->write to db->navigate->retrieve from db flow

What is the correct way to handle this, I have done a lot of searching and most samples which use future builders use them to draw lists so maybe I should be avoiding them all together here.
I want to submit a login form, perform the network request and draw a progress bar while the login is happening, and if successful navigate to a home page. If unsuccessful it should just kill the progress bar and redraw the home page. That part seems to be working, unsure if I am using the Navigator correctly.
The login call returns a user and access token object. The Homepage needs to retrieve the access token which was written to the db by the successful login response. From what I can tell the navigation is happening too quickly and the retrieval of the access token appears to happen before the navigation to the home page.
class LoginPage extends StatefulWidget {
LoginPage({Key key, this.title}) : super(key: key);
final String title;
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool _isValidForm = true;
Future<LoginResponse> _user;
void _submitLogin() {
setState(() {
if (_isValidForm) {
_user = login().then((_) => Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage())));
}
});
}
Widget _buildLoginForm(AsyncSnapshot<LoginResponse> snapshot) {
if (snapshot.connectionState != ConnectionState.none && !snapshot.hasData) {
return new Center(child: new CircularProgressIndicator());
} else {
return SafeArea(
child: Center(
child: new ListView(
children: <Widget>[
//..more views
Padding(
padding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//..email and password fields
FlatButton(
child: new Text(
'SIGN IN',
),
onPressed: _submitLogin),
]),
)
],
),
),
);
}
}
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _user,
builder: (context, AsyncSnapshot<LoginResponse> snapshot) {
return new Scaffold(
backgroundColor: kMyGreen,
body: _buildLoginForm(snapshot),
);
},
);
}
Future<LoginResponse> login() async {
final response = await http.post(...);
if (response.statusCode == 200) {
var loginResponse = LoginResponse.fromJson(json.decode(response.body));
//Write the user details to local db
DBProvider.db.newUser(loginResponse.user);
//Write the tokens to local db
DBProvider.db.newToken(loginResponse.tokens);
return loginResponse;
} else {
throw Exception('Failed to login');
}
}
}
Database methods:
newUser(User newUser) async {
final db = await database;
//get the biggest id in the table
var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM User");
int id = table.first["id"];
//insert to the table using the new id
var raw = await db.rawInsert(
"INSERT Into User (id,first_name,last_name)"
" VALUES (?,?,?)",
[id, newUser.firstName, newUser.lastName]);
return raw;
}
newToken(Tokens newTokens) async {
final db = await database;
//await db.rawDelete("DELETE FROM Token");
//get the biggest id in the table
var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM Token");
int id = table.first["id"];
//insert to the table using the new id
var raw = await db.rawInsert(
"INSERT Into Token (id,access_token,refresh_token)"
" VALUES (?,?,?)",
[id, newTokens.accessToken, newTokens.refreshToken]);
return raw;
}
Future<Tokens> getToken() async {
final db = await database;
var res = await db.query("Token", limit: 1);
return res.isNotEmpty ? Tokens.fromJson(res.first) : null;
}
Home page
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
#override
void initState() {
super.initState();
getHomePageStuff();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {},
child: Text('Go back!'),
),
),
);
}
}
Future<HomePageStuffResponse> getHomePageStuff() async {
Tokens token = await DBProvider.db.getToken();
//Accessing the token here throws an NPE
var accessToken = token.accessToken;
debugPrint("token = " + accessToken);
final response = await http.get(..);
if (response.statusCode == 200) {
debugPrint("FETCH SUCCESS");
return stuff;
} else {
throw Exception('Failed to fetch home page stuff');
}
}
You can simply wrap Scaffold's body in FutureBuilder like this
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: FutureBuilder<HomePageStuffResponse>(
future: getHomePageStuff(),
builder: (context, snap) {
if(snap.hasError) {
return ErrorWidget('Error occurred while fetching data');
}
if(snap.hasData) {
return Center(
child: RaisedButton(
onPressed: () {},
child: Text('Go back!'),
),
);
}
}
),
);
}
}
Future<HomePageStuffResponse> getHomePageStuff() async {
Tokens token = await DBProvider.db.getToken();
//Accessing the token here throws an NPE
var accessToken = token.accessToken;
debugPrint("token = " + accessToken);
final response = await http.get(..);
if (response.statusCode == 200) {
debugPrint("FETCH SUCCESS");
return stuff;
} else {
throw Exception('Failed to fetch home page stuff');
}
}
Okay I was pretty close. Navigation is fine the way it is, the issue was the writing to the db was not being awaited on so that would happen simultaneously to the navigation (the newUser and newToken calls). As I would navigate to the home screen and try and read the access token the call would fail because it did not exist yet.
This was made a little harder to figure out because the debugger is a little strange in Android Studio for flutter so I just had to log everything to the console to see the issue.
If you read my question thank you for your time :)

Resources