I am trying to read data realtime from my firebase firestore database through flutter. I learned that the best way to manage this is streambuilder. Checked a few sites on this topic and made a code like below and put a print statement just to see if everything works ok, however when i run the program it give the fallowing errors:
A build function returned null.
The relevant error-causing widget was:
StreamBuilder<DocumentSnapshot> file:///C:/Users/faruk/Desktop/3-sayiavi/sayi_avi/lib/gamescreen.dart:116:15
The method '[]' was called on null.
Receiver: null
Tried calling: []("status")
The relevant error-causing widget was:
StreamBuilder<DocumentSnapshot> file:///C:/Users/faruk/Desktop/3-sayiavi/sayi_avi/lib/gamescreen.dart:116:15
My Code is below:
import 'package:flutter/material.dart';
import 'package:assets_audio_player/assets_audio_player.dart';
import 'numbers.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
Numbers myNumbers = Numbers();
void main(){
runApp(
GameScreen()
);
}
class GameScreen extends StatefulWidget {
static String id ='gamescreen';
@override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
final _auth =FirebaseAuth.instance;
FirebaseUser loggedInUser;
final _firestore = Firestore.instance;
String collectionPath = 'users';
String docPath;
@override
void initState(){
super.initState();
getCurrentUser();
}
void getCurrentUser() async{
try{
final user = await _auth.currentUser();
if(user !=null){
loggedInUser =user;
docPath = loggedInUser.uid;
print(docPath);
print(collectionPath);
}
}catch(e){
print(e);
}
}
Expanded attachNumber(imagenumber){
return Expanded(
child:FlatButton(
onPressed: (){
setState(() {
if(!myNumbers.numberStatus[1]){
myNumbers.buttonValues['numberimage1'] = imagenumber;
myNumbers.numberStatus[1] =true;
}else if(!myNumbers.numberStatus[2]){
myNumbers.buttonValues['numberimage2'] = imagenumber;
myNumbers.numberStatus[2] =true;
}else if(!myNumbers.numberStatus[3]){
myNumbers.buttonValues['numberimage3'] = imagenumber;
myNumbers.numberStatus[3] =true;
}else if(!myNumbers.numberStatus[4]){
myNumbers.buttonValues['numberimage4'] = imagenumber;
myNumbers.numberStatus[4] =true;
}
});
final assetsAudioPlayer = AssetsAudioPlayer();
assetsAudioPlayer.open(
Audio("assets/audios/click.wav"),
);
},
padding: EdgeInsets.all(0),
child: Image.asset('images/$imagenumber'),
),
);
}
FlatButton showDeleteNumbers(statusNumber,number){
return FlatButton(
onPressed: (){
setState(() {
myNumbers.numberStatus[statusNumber] =false;
myNumbers.buttonValues[number] = 'nonumber.png';
});
},
child: Image.asset('images/'+myNumbers.buttonValues['$number']),
//todo: _auth.signOut(); ekleyerek signout yapacaz
//Navigator.pop(context); bir önceki ekrana dönecez;
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
title: Text('Sayı Avı Oyun Ekranı'),
),
body: StreamBuilder(
stream: _firestore.collection(collectionPath).document(docPath).snapshots(),
builder: (context,snapshot){
if(snapshot.hasData){
var userDocument = snapshot.data;
print(userDocument['status']);
return Column(
children: <Widget>[
Expanded(
flex: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 50,
child: Column(
children: myNumbers.getUserNumbers(),
),
),
Expanded(
flex: 50,
child: Column(
children: myNumbers.getOpponentNumbers(),
),
),
],
),
),
Expanded(
flex:10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showDeleteNumbers(1,'numberimage1'),
showDeleteNumbers(2,'numberimage2'),
showDeleteNumbers(3,'numberimage3'),
showDeleteNumbers(4,'numberimage4'),
],
),
),
Expanded(
flex: 10,
child: Row(
children: <Widget>[
attachNumber('one.png'),
attachNumber('two.png'),
attachNumber('three.png'),
attachNumber('four.png'),
attachNumber('five.png'),
attachNumber('six.png'),
attachNumber('seven.png'),
attachNumber('eight.png'),
attachNumber('nine.png'),
attachNumber('zero.png'),
],
),
),
],
);
}
},
),
),
);
}
}
changed the code like this:
import 'package:flutter/material.dart';
import 'package:assets_audio_player/assets_audio_player.dart';
import 'numbers.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
Numbers myNumbers = Numbers();
void main(){
runApp(
GameScreen()
);
}
class GameScreen extends StatefulWidget {
static String id ='gamescreen';
@override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
final _auth =FirebaseAuth.instance;
FirebaseUser loggedInUser;
final _firestore = Firestore.instance;
String collectionPath = 'users';
String docPath;
@override
void initState(){
super.initState();
getCurrentUser();
}
void getCurrentUser() async{
try{
final user = await _auth.currentUser();
if(user !=null){
loggedInUser =user;
docPath = loggedInUser.uid;
print(docPath);
print(collectionPath);
}
}catch(e){
print(e);
}
}
Expanded attachNumber(imagenumber){
return Expanded(
child:FlatButton(
onPressed: (){
setState(() {
if(!myNumbers.numberStatus[1]){
myNumbers.buttonValues['numberimage1'] = imagenumber;
myNumbers.numberStatus[1] =true;
}else if(!myNumbers.numberStatus[2]){
myNumbers.buttonValues['numberimage2'] = imagenumber;
myNumbers.numberStatus[2] =true;
}else if(!myNumbers.numberStatus[3]){
myNumbers.buttonValues['numberimage3'] = imagenumber;
myNumbers.numberStatus[3] =true;
}else if(!myNumbers.numberStatus[4]){
myNumbers.buttonValues['numberimage4'] = imagenumber;
myNumbers.numberStatus[4] =true;
}
});
final assetsAudioPlayer = AssetsAudioPlayer();
assetsAudioPlayer.open(
Audio("assets/audios/click.wav"),
);
},
padding: EdgeInsets.all(0),
child: Image.asset('images/$imagenumber'),
),
);
}
FlatButton showDeleteNumbers(statusNumber,number){
return FlatButton(
onPressed: (){
setState(() {
myNumbers.numberStatus[statusNumber] =false;
myNumbers.buttonValues[number] = 'nonumber.png';
});
},
child: Image.asset('images/'+myNumbers.buttonValues['$number']),
//todo: _auth.signOut(); ekleyerek signout yapacaz
//Navigator.pop(context); bir önceki ekrana dönecez;
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
title: Text('Sayı Avı Oyun Ekranı'),
),
body: StreamBuilder<DocumentSnapshot>(
stream: _firestore.collection(collectionPath).document(docPath).snapshots(),
builder: (BuildContext context,AsyncSnapshot<DocumentSnapshot> snapshot){
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
if(snapshot.hasData){
Map<String, dynamic> userDocument = snapshot.data.data;
print(userDocument);
return Column(
children: <Widget>[
Expanded(
flex: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 50,
child: Column(
children: myNumbers.getUserNumbers(),
),
),
Expanded(
flex: 50,
child: Column(
children: myNumbers.getOpponentNumbers(),
),
),
],
),
),
Expanded(
flex:10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showDeleteNumbers(1,'numberimage1'),
showDeleteNumbers(2,'numberimage2'),
showDeleteNumbers(3,'numberimage3'),
showDeleteNumbers(4,'numberimage4'),
],
),
),
Expanded(
flex: 10,
child: Row(
children: <Widget>[
attachNumber('one.png'),
attachNumber('two.png'),
attachNumber('three.png'),
attachNumber('four.png'),
attachNumber('five.png'),
attachNumber('six.png'),
attachNumber('seven.png'),
attachNumber('eight.png'),
attachNumber('nine.png'),
attachNumber('zero.png'),
],
),
),
],
);
}
},
),
),
);
}
}
Currently it gets the values of the map when i click save button, but when i go back and forward in the avd it gets null again. What may be wrong?
what i would like to do is, there is a firebase function which matches users according to their "status" in the database. when a user enters this screen, its status will be "on" in the database and when firebase function finds another user with "on" status it will make both their status "ready" or something like this. When the user's status changed to "ready" instead of showing a for example "searching for a match" screen it will change to game screen and 2 ppl will start to play turn based. So to sum up as soon as user enters this screen it should start to listen changes in its status. Also as it will be a turn based game it should continue to listen other fields later for database changes. I hope i could have explained myself.
Updated the flutter firebase code for new dependencies below
import 'package:flutter/material.dart';
import 'package:assets_audio_player/assets_audio_player.dart';
import 'numbers.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
Numbers myNumbers = Numbers();
void main(){
runApp(
GameScreen()
);
}
class GameScreen extends StatefulWidget {
static String id ='gamescreen';
@override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
final _auth =FirebaseAuth.instance;
User loggedInUser;
final _firestore = FirebaseFirestore.instance;
final String collectionPath = 'users';
String docPath;
DocumentReference userdoc;
@override
void initState(){
super.initState();
getCurrentUser();
}
void getCurrentUser() async{
try{
final user = await _auth.currentUser;
if(user !=null){
loggedInUser =user;
docPath = loggedInUser.uid;
}
}catch(e){
print(e);
}
}
Expanded attachNumber(imagenumber){
return Expanded(
child:FlatButton(
onPressed: (){
setState(() {
if(!myNumbers.numberStatus[1]){
myNumbers.buttonValues['numberimage1'] = imagenumber;
myNumbers.numberStatus[1] =true;
}else if(!myNumbers.numberStatus[2]){
myNumbers.buttonValues['numberimage2'] = imagenumber;
myNumbers.numberStatus[2] =true;
}else if(!myNumbers.numberStatus[3]){
myNumbers.buttonValues['numberimage3'] = imagenumber;
myNumbers.numberStatus[3] =true;
}else if(!myNumbers.numberStatus[4]){
myNumbers.buttonValues['numberimage4'] = imagenumber;
myNumbers.numberStatus[4] =true;
}
});
final assetsAudioPlayer = AssetsAudioPlayer();
assetsAudioPlayer.open(
Audio("assets/audios/click.wav"),
);
},
padding: EdgeInsets.all(0),
child: Image.asset('images/$imagenumber'),
),
);
}
FlatButton showDeleteNumbers(statusNumber,number){
return FlatButton(
onPressed: (){
setState(() {
myNumbers.numberStatus[statusNumber] =false;
myNumbers.buttonValues[number] = 'nonumber.png';
});
},
child: Image.asset('images/'+myNumbers.buttonValues['$number']),
);
}
@override
Widget build(BuildContext context) {
userdoc = _firestore.collection(collectionPath).doc(docPath);
return StreamBuilder<DocumentSnapshot>(
stream: userdoc.snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
if(snapshot.hasData){
Map<String, dynamic> userDocument = snapshot.data.data();
print(collectionPath);
print(docPath);
print(snapshot.data);
print(userDocument);
return MaterialApp(
home:Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
title: Text('Sayı Avı Oyun Ekranı'),
),
body:Column(
children: <Widget>[
Expanded(
flex: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 50,
child: Column(
children: myNumbers.getUserNumbers(),
),
),
Expanded(
flex: 50,
child: Column(
children: myNumbers.getOpponentNumbers(),
),
),
],
),
),
Expanded(
flex:10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showDeleteNumbers(1,'numberimage1'),
showDeleteNumbers(2,'numberimage2'),
showDeleteNumbers(3,'numberimage3'),
showDeleteNumbers(4,'numberimage4'),
],
),
),
Expanded(
flex: 10,
child: Row(
children: <Widget>[
attachNumber('one.png'),
attachNumber('two.png'),
attachNumber('three.png'),
attachNumber('four.png'),
attachNumber('five.png'),
attachNumber('six.png'),
attachNumber('seven.png'),
attachNumber('eight.png'),
attachNumber('nine.png'),
attachNumber('zero.png'),
],
),
),
],
),
),
);
}
},
);
}
}
You can see there are 4 prints to check if i am getting the data correctly. Currently when i run the app it shows
I/flutter (15703): users I/flutter (15703): viPK8SpL9MOIV2bQqGzBkKVdYAk2 I/flutter (15703): Instance of 'DocumentSnapshot' I/flutter (15703): null
The 4th one shouldnt supposed to be null, when i click "save" button in android studio i get
I/flutter (15703): users I/flutter (15703): viPK8SpL9MOIV2bQqGzBkKVdYAk2 I/flutter (15703): Instance of 'DocumentSnapshot' I/flutter (15703): {password: 123456, lose: 0, win: 0, email: [email protected], status: ready}
Which is the correct result.
You forgot to call
data()
onsnapshots()
's result in StreamBuilder.Refer this: https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes