Stack Overflow error when grabbing data from Firebase in Flutter - firebase

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.

Related

Execution failed for task ':location:parseDebugLocalResources'

i'm trying to run my project , i have this page where i want to get current location of the user
but i get a message error when i run it
Message error
** Execution failed for task ':location:parseDebugLocalResources'.
Could not resolve all files for configuration ':location:androidApis'.
Failed to transform android.jar to match attributes {artifactType=android-platform-attr, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
> Execution failed for PlatformAttrTransform:
C:\Users\lenovo\AppData\Local\Android\sdk\platforms\android-30\android.jar.
> C:\Users\lenovo\AppData\Local\Android\sdk\platforms\android-30\android.jar **
The code in the page
// ignore_for_file: deprecated_member_use
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:location/location.dart';
import '../rounded_button.dart';
class DemandeList extends StatefulWidget {
#override
_DemandeList createState() => _DemandeList();
}
class _DemandeList extends State<DemandeList>{
late bool _serviceEnabled;
late PermissionStatus _permissionGranted;
LocationData? _userLocation;
Future<void> _getUserLocation() async {
Location location = Location();
// Check if location service is enable
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return;
}
}
// Check if permission is granted
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return;
}
}
final _locationData = await location.getLocation();
setState(() {
_userLocation = _locationData;
});
}
final db = FirebaseFirestore.instance;
String? Key ;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Listes des demandes"),
centerTitle: true,
),
body: StreamBuilder<QuerySnapshot>(
stream: db.collection('ambulance')
.where("etat", isEqualTo: "en cours")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else
return ListView(
children: snapshot.data!.docs.map((doc) {
int lng;
int lat;
return Card(
child: ListTile(
trailing: Text("Accepter",
style: TextStyle(
color: Colors.green,fontSize: 15
),
),
title: new Text(doc['id']) ,
subtitle: new Text(doc['etat']),
onTap: () => {
_getUserLocation,
lat = _userLocation?.latitude as int ,
lng = _userLocation?.longitude as int ,
db.collection("position").add({'lat': lat , "lng": lng ,}),
db.collection("ambulance").doc(doc.id).update({"etat": 'Accept' }),
}
),
);
}).toList(),
);
},
),
);
}
}

Why calling an async function whose defination is given in another program returns null or Future<type> instance for the first time as output?

Hello Im very to the flutter framework, so please let me know if im going wrong anywhere and the appropriate way of doing the things.
this is a drawerPage.dar file
In this file im trying to call a function getData for retrieving the data from firebase,this fucntion is in Database.dart file.
Database.dart
In the Database.dart file i wrote the getData function inside which im retrieving a particular record from the firebase and storing in a global variable. And then im trying to print the global variable in the drawerPage.dart file.But here when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable.Please let me know how can i get rid of this problem.
output
drawerPageOutput
drawerPage.dart
import 'package:attendee/constants.dart';
import 'package:attendee/models/userdeails.dart';
import 'package:attendee/pages/profile.dart';
import 'package:attendee/services/authentication_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:attendee/services/database.dart';
import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:attendee/pages/userdetails.dart';
class StudentDashboard extends StatefulWidget {
#override
_StudentDashboardState createState() => _StudentDashboardState();
}
class _StudentDashboardState extends State<StudentDashboard> {
userdetails userdetail;
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final AuthenticationService _auth = AuthenticationService();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
DatabaseService().getData('email');
final drawerHeader = UserAccountsDrawerHeader(
accountName: Text(userName),
accountEmail: Text('${result}'),
currentAccountPicture
: CircleAvatar(
child: FlutterLogo(size: 42.0),
backgroundColor: Colors.white,
);
final drawerItems = ListView(
children: <Widget>[
drawerHeader,
ListTile(
title: Row(
children: <Widget>[
Icon(Icons.perm_identity_outlined),
Text(' Profile'),
],
),
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context)=>Profile())),
),
ListTile(
title: Text('To page 2'),
onTap: () => Navigator.of(context).push(_NewPage(2)),
),
ListTile(
title:Row(
children: <Widget>[
Icon(Icons.exit_to_app_rounded),
Text(' Logout'),
],
),
onTap: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
),
],
);
return StreamProvider<List<userdetails>>.value(
value: DatabaseService().students,
initialData: [],
child: SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightGreen,
title: Text('Student Welcome'),
actions: <Widget>[
TextButton.icon(
onPressed: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
icon: Icon(Icons.person),
label: Text('Logout'))
],
),
body:
UserDetails(),
drawer: GestureDetector(
onTap: display,
child: Drawer(
child: drawerItems,
),
),
),
),
);
}
display() async{
await DatabaseService().getData('email');
}
}
// <Null> means this route returns nothing.
class _NewPage extends MaterialPageRoute<Null> {
_NewPage(int id)
: super(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page $id'),
elevation: 1.0,
),
body: Center(
child: Text('Page $id'),
),
);
});
}
database.dart
import 'package:attendee/models/userdeails.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import '../constants.dart';
class DatabaseService{
final String uid;
DatabaseService({this.uid});
//collection reference
final CollectionReference user_details=FirebaseFirestore.instance.collection('users');`
final CollectionReference tutor_details` `=FirebaseFirestore.instance.collection("tutors");`
Future updateStudentData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await user_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
Future updateTutorData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await tutor_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
//studentDetails from snapshot
List<userdetails> _studentDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
//role: doc.data()['role'] ?? '',
);
}).toList();
}
//get students stream
Stream<List<userdetails>> get students {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
//tutorsDetails from snapshot
List<userdetails> _tutorDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
);
}).toList();
}
//get tutors stream
Stream<List<userdetails>> get tutors {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
void display() {
tutor_details.get().then((querySnapshot) {
querySnapshot.docs.forEach((result) {
print(result.data());
});
});
}
getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
}
//print(document("name"));
});
}
}
After changes
terminaloutput
draweroutput
""when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable""
When we try to get data from http / https request, it takes some time. Meanwhile the page gets loaded and you get null values.
You can use Provider package to resolve this issue, or try the below code. Please add the below code in your drawerPage.dart.
What I have done below is made getData() return type. Only on receiving a value from this function, _loadOnce will change to false & final screen will be shown.
Database.dart
Future<bool> getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
return true;
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
return false;
}
//print(document("name"));
});
}
}
/// create a new variable.
bool _loadOnce = true;
/// shift your code `DatabaseService().getData('email');`
#override
void didChangeDependencies() {
if(_loadOnce == true) {
DatabaseService().getData('email').then((value) {
if(value == true){
setState(() {
_loadOnce = false;
});
} else {
/// you can write your code here
setState(() {
_loadOnce = false;
});
}
)}
}
super.didChangeDependencies();
}
Below code will show a spinner till the time all the code gets executed and values are retreived.
/// in your main page under Scaffold
body: _loadOnce == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor,
),
)
: UserDetails(),

Flutter FutureBuilder not building with data from sqlite

I'm very new in flutter, and are trying to write the app that will take value from sqlite. From what I tried, it needs to use FutureBuilder widget.
But the following code I wrote, the FutureBuilder widget seems to get data from sqlite, but the "builder" property was never called:
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutterapp/dbHelper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes:{
"/": (context) =>Test()
}
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool nameCheck = false; // Use to check name textfield has correctly be inputed
TextEditingController nameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
print("Start future"); // never get printed
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
Builder(
builder: (BuildContext context) {
print("got data");
final result = snapshot.data;
print(result) ; // never get printed
setState(() {
nameCheck = true;
});
return Container();
})
];
} else {
children = <Widget>[
AlertDialog(
content: SpinKitCircle(
color: Colors.white,
size: 80.0,
))
];
}
return Center(
child: Container(
color: Colors.blue,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment:
CrossAxisAlignment
.center,
children: children,
)));
});}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
}
Future<List<Map>> getData(String input_name) async{
final dbHelper = DBHelper.instance;
await dbHelper.database;
final result = await dbHelper.query("SELECT * FROM Guest WHERE Name = \"$input_name\"");
print(result); // This get printed
return result;
}
The DBHelper code is as follow, basically it just set up a sqlite database and some database operation:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class DBHelper {
static final _databaseName = "MonetaryDB.db";
static final _databaseVersion = 1;
static final create_table_Test = "CREATE TABLE \"Guest\" (\"Name\" TEXT NOT NULL PRIMARY KEY, \"Money\" INTEGER, \"Person\" INTEGER)";
static final String insert_guest = "INSERT INTO Guest (Name, Money, Person) VALUES (\"testname\", 1000, 1)";
DBHelper._privateConstructor();
static final DBHelper instance = DBHelper._privateConstructor();
static Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;}
else{
_database = await _initDatabase();
return _database;}
}
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate,
);
}
Future _onCreate(Database db, int version) async {
await db.execute(create_table_Test);
await db.rawInsert(insert_guest);
}
Future<int> insert(String statement) async {
Database db = await instance.database;
return await db.rawInsert(statement);
}
Future<List<Map>> query(String statement) async {
Database db = await instance.database;
return await db.rawQuery(statement);
}
Future<int> update(String statement) async{
Database db = await instance.database;
return await db.rawUpdate(statement);
}
Future<int> delete(String statement) async{
Database db = await instance.database;
return db.rawDelete(statement);
}
}
If I simply change the IconButton onPressed function into setState((){nameCheck = true}), The Text("test") widget will show, so the problem must be the FutureBuilder. Also, the getData() function can get the correct result from the sqlite database
I have no idea why the FutureBuilder doesn't get build, did someone have any idea of it?
Thanks!
So with the help in the comment section, I change the code and it worked:
...
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
actions: <Widget>[
SizedBox(
width: 300,
height: 300,
child: FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {
nameCheck = true;
}));
Navigator.pop(context, true);
}
else {
return Container(
child: SpinKitCircle(
color: Colors.white,
size: 80.0,
));
}
return Container(color: Colors.blue);
})
),
],
);
}
);
}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
...
The other codes are still the same.
Like the comment section above had suggested, the main problem is that there will be no place to build for the widget the FutureBuilder that is going to built. To solve this problem, I place the FutureBuilder widget into the AlertDialog widget, since that I still want to keep the SpinKitCircle widget when loading.
I also gave up the Column widget at the end of the FutureBuilder widget, and deleted the Builder widget at the beginning of the FutureBuilder widget, which it was no longer needed when there was no Column.
The above codes still throw an acceptable exception:"setState() or markNeedsBuild() called during build." But the whole things still can run, so I will try to fix that the other day.
Thanks for the suggestion in the comment section.

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.

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