The getter 'uid' was called on null - but only half the time - firebase

So I have this screen in my Flutter app that is supposed to show all the notes for a particular user. I have the Firestore structured so that there is a collection of notes and each user has one document named their uid. Then all their notes are store in a collection (usernotes) under their document.
The problem I am having here is that when you try to access the notes page in the app, you get the error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
But when I simply click run from the Flutter app, everything works just fine. You can see all the notes on the screen. Here is my note screen.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'welcome_screen.dart';
class NoteScreen extends StatefulWidget {
static const String id = 'note_screen';
#override
_NoteScreenState createState() => _NoteScreenState();
}
class _NoteScreenState extends State<NoteScreen> {
final _auth = FirebaseAuth.instance;
User loggedInUser;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser;
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Field Notes'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.chat),
tooltip: 'Messages',
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.exit_to_app),
tooltip: 'Log Out',
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, WelcomeScreen.id);
},
),
],
),
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('notes').doc(loggedInUser.uid).collection('usernotes')
.snapshots(),
builder: (ctx, streamSnapShot) {
if(!streamSnapShot.hasData) return const Text('Loading...');
if (streamSnapShot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
final noteData = streamSnapShot.data.docs;
return ListView.builder(
itemCount: noteData.length,
itemBuilder: (ctx, index) => Container(
padding: EdgeInsets.all(8),
child: Text(noteData[index]['text']),
),
);
},
),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: () {
FirebaseFirestore.instance.collection('notes').doc(loggedInUser.uid).collection('usernotes').add({
'text' : 'This was added by clicking the button!'
});
}),
);
}
}

currentUser is of type User therefore you do not need to use await since it doesn't return a Future. You can do the following:
FirebaseFirestore.instance.collection('notes').doc(_auth.currentUser.uid).collection('usernotes').snapshots(),
And:
FloatingActionButton(child: Icon(Icons.add), onPressed: () {
FirebaseFirestore.instance.collection('notes').doc(_auth.currentUser.uid).collection('usernotes').add({'text' : 'This was added by clicking the button!'});
});

Related

onTap method for Flutter to open longitude and latitude stored in Firestore

I am trying to create a search engine for electoral sections, once it finds the electoral
section by clicking on the item it should send me to a longitude and latitude that I have stored
in firestore and display it on Google maps as markers with flutter, but I cannot create the
method, what will be the most efficient way to do this?
class SearchPage extends StatefulWidget {
#override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
TextEditingController textEditingController = TextEditingController();
final database = Firestore.instance;
String searchString;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(30.0),
child: Container(
child: TextField(
onChanged: (val) {
setState(() {
searchString = val.toLowerCase();
});
},
controller: textEditingController,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () => textEditingController.clear()),
hintText: 'Buscar seccion',
hintStyle: TextStyle(
fontFamily: 'Antra', color: Colors.blueGrey)),
),
),
),
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: (searchString == null || searchString.trim() == ' ')
? Firestore.instance.collection('secciones').snapshots()
: Firestore.instance
.collection('secciones')
.where('searchIndex', arrayContains: searchString)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('We got an error ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Cargando');
case ConnectionState.none:
return Text('Error de conexion');
case ConnectionState.done:
return Text('We are done!');
default:
return new ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return new ListTile(
title: Text(document['estado']),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return MapsScreen(
);
}),
);
});
}).toList());
}
},
),
)
],
),
)
],
));
}
}
This is the screen where you should send the position stored in firestore,
but I can't find out how to do it and I took the method from a video
tutorial in which they taught you how to show and store your current
location in Google maps.
class MapsScreen extends StatefulWidget{
final String partyNumber;
final String userId;
const MapsScreen({Key key, this.userId, this.partyNumber}) : super(key: key);
#override
_MapsScreenState createState() => _MapsScreenState();
}
class _MapsScreenState extends State<MapsScreen>{
GoogleMapController _mapController;
Location _location = Location();
StreamSubscription<LocationData> subscription;
#override
void initState(){
super.initState();
_initLocation();
}
_initLocation() async{
var _serviceEnabled = await _location.serviceEnabled();
if(!_serviceEnabled) {
_serviceEnabled = await _location.requestService();
if(!_serviceEnabled){
return;
}
}
var _permissionGranted = await _location.hasPermission();
if(_permissionGranted == PermissionStatus.DENIED){
_permissionGranted = await _location.requestPermission();
if(_permissionGranted != PermissionStatus.GRANTED){
print("Sin permisos de GPS");
return;
}
}
subscription = _location.onLocationChanged().listen((LocationData event) {
if(_mapController != null){
_mapController.animateCamera(
CameraUpdate.newLatLng(
LatLng(event.latitude, event.longitude),
),
);
}
Firestore.instance
.collection('seccion')
.document(widget.partyNumber)
.collection('people')
.document(widget.userId)
.setData({
'lat': event.latitude,
'lng': event.longitude,
});
print("${event.latitude}, ${event.longitude}");
});
}
#override
void dispose(){
if(subscription != null){
subscription.cancel();
}
Firestore.instance
.collection('seccion')
.document(widget.partyNumber)
.collection('people')
.document(widget.userId)
.delete();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Instituto Nacional Electoral"),
),
body: GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(16.879860202903764, -99.9013661857768),
zoom: 15,
),
zoomGesturesEnabled: true,
myLocationEnabled: true,
myLocationButtonEnabled: true,
onMapCreated: (controller) => _mapController = controller,
),
);
}
}
I am not quite sure what exactly you are trying to accomplish.
I initially thought you had latitudes and longitudes stored somewhere in Firebase and wanted to display the marker in those locations.
I you wanted to do that, you would need to get the location data from Firebase and pass it into the GoogleMap. I am not familiar with the widget itself, but from the documentation as you can see here: https://github.com/flutter/plugins/blob/f3024731b090659edaa92d01416549c690f65678/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart#L112
the widget accepts a Set of Markers.
If you did a little in the repository you can see how to build a Marker. And then you can construct one or more from the location data in Firebase and pass them to the GoogleMap widget.
If that is what you want to accomplish. The code you posted saves the current user location to Firebase, so I am unsure what exactly your goal is.

Flutter video_player with URL from Firestore Document

I'm trying to play a video from a URL of a Firestore Document. To play a video in Flutter, I have to instantiate its Url in the init() method. I set a default URL to a butterfly video, and the value was supposed to be replaced by the URL obtained from Firestore. (So that it is easy for me to see if the code works). However, the code does not work properly. I got an error that says "NoSuchMethodError: The getter 'value' was called on null".
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// Create the initialization Future outside of build
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
// Check for error
if (snapshot.hasError) {
print(snapshot.error);
return Center(
child: Container(
child: Text(
"Something went wrong",
textDirection: TextDirection.ltr,
),
),
);
}
//Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
return MaterialApp(
title: 'Flutter Demo',
home: VideoPlayerScreen(),
);
}
return CircularProgressIndicator();
});
}
}
class VideoPlayerScreen extends StatefulWidget {
#override
_VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
VideoPlayerController _controller;
Future<void> _initializeVideoPlayerFuture;
FirebaseFirestore firestore = FirebaseFirestore.instance;
String videoUrl =
'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4';
#override
void initState() {
firestore.collection("videos").get().then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
// _controller.dispose();
videoUrl = doc["videoUrl"];
_controller = VideoPlayerController.network(videoUrl);
_initializeVideoPlayerFuture = _controller.initialize();
print(videoUrl);
})
});
// _controller = VideoPlayerController.network(videoUrl);
// _initializeVideoPlayerFuture = _controller.initialize();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Video Player"),
),
body: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Column(
children: [
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
],
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
Try the following:
#override
void initState() {
super.initState();
firestore.collection("videos").get().then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
videoUrl = doc["videoUrl"];
_controller = VideoPlayerController.network(videoUrl);
_initializeVideoPlayerFuture = _controller.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
});
});
}
Since initialize() is asynchronous, then you can use the method then which will get called when the future completes. Inside the callback, you can call setState() which will trigger a rebuild and notify the framework that the internal state of the widgets has changed .
https://pub.dev/packages/video_player

Fetching Data from a Realtime Firestore Document

I have seen similar questions and answers being solved with the StreamBuilder widget.
In my case when I am implementing it my code does not await to fetch the data and just moves on (in my case, the app jumps to the next page). Thus, do I need the build a StreamBuilder Widget or is there a simple method that could work and fetch the data in realtime?
I noticed that I did not use async* with the asterisc but if I do so, then the authentication is not working.
Clarification:
The code does not enter the following lines:
if (!snapshot.hasData)
return new Text('Loading...');
return new Text(
snapshot.data.data['name']
);
Also the print(test); statement prints the following:
StreamBuilder<DocumentSnapshot>
Here is the whole part:
onPressed: () async {
setState(() {
showSpinner = true;
});
try {
LoginScreen.user =
await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (LoginScreen.user != null) {
// get the users data and save them
if (LoginScreen.user.user.uid !=
'IDVwQXAsZas213Va0OIH2IsoU5asdaTfraBJ2') {
Widget test = await StreamBuilder<DocumentSnapshot>(
stream: _firestore
.collection('Employees')
.document(LoginScreen.user.user.uid)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData)
return new Text('Loading...');
return new Text(
snapshot.data.data['name']
);
},
);
print(test);
Navigator.pushReplacementNamed(
context, TabCreator.screenId);
} else {
}
}
} catch (e) {
print(e);
// when getting an erro stop spinner
setState(() {
showSpinner = false;
});
}
}
Update:
I created a new standard flutter project in order to see if there was something else within my code that was messing the StreamBuilder. I am still getting no output.
On a side note when I am implementing the following code within the onPressed method I am getting the wanted result:
Alternative Solution:
onPressed: () {
DocumentReference documentReference = await Firestore.instance
.collection('Employees')
.document('8nss0gppzNfOBMuRz9H44dv7gSd2');
documentReference.snapshots().listen((datasnapshot) {
if (datasnapshot.exists) {
print(datasnapshot.data['name'].toString());
} else {
print('Error!');
}
});
}
Here is the implemented StreamBuilder implemented in the standard Flutter project:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'Testing',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// DocumentReference documentReference = await Firestore.instance
// .collection('Employees')
// .document('8nss0gppzNfOBMuRz9H44dv7gSd2');
// documentReference.snapshots().listen((datasnapshot) {
// if (datasnapshot.exists) {
// print(datasnapshot.data['name'].toString());
// } else {
// print('Error!');
// }
// });
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Employees')
.document('8nss0gppzNfOBMuRz9H44dv7gSd2')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new Text(snapshot.data.data['name']);
}
},
);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Change your code to the following:
builder: : (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData){
return new Text('Loading...');
}
else{
print(snapshot);
Navigator.pushReplacementNamed(
context, TabCreator.screenId);
}
Add an else block so when you have data it will enter the else and navigate to the page.
Also you need to use the StreamBuilder inside the build method not inside the onPressed function which is used to handle data processing. Example you can do the following:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool visible = false;
final firestoreInstance = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
Visibility(
child: StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('users')
.document('FIJbBBiplAGorYzdtUQF')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
print(snapshot);
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else if (snapshot.hasData) {
print(snapshot.data.data);
return new Text(snapshot.data.data["age"].toString());
}
return new CircularProgressIndicator();
},
),
visible: visible,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
visible = true;
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
So here I also use the Visibility() widget to hide it, but when FAB button is clicked the data from firestore will appear.

Getter uid being called on null in flutter fire

I am trying to concatenate two UID's in order to create a chatroom. One uid is being read from firebase while the other is read from the FirebaseAuth.instance.
The clientUID is being assigned as it should, as I am passing it to another page on a Text widget. However the chatroom is not being created in the firestore tree so I assume this should be because of the instructor uid.
Maybe I am not calling the FirebaseAuth.instance as it should?
Code:
class ClientiPage extends StatefulWidget {
static const String id = 'CLIENTI';
#override
_ClientiPageState createState() => _ClientiPageState();
}
class _ClientiPageState extends State<ClientiPage> {
String chatRoomID;
String clientUID;
Firestore db = Firestore.instance;
String instructor;
void getInstructorId() async {
instructor = (await FirebaseAuth.instance.currentUser()).uid;
}
void saveChatRoom() {
getInstructorId();
DocumentReference chatroomIdRef = db.collection('instructori').document(instructor).collection("chatrooms").document(chatRoomID);
if (chatroomIdRef == null) {
db.collection('instructori').document(instructor).collection("chatrooms").document(chatRoomID);
}
}
void createChatRoom() {
getInstructorId();
chatRoomID = clientUID + instructor;
if(chatRoomID != null) {
saveChatRoom();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
chatRoomID: chatRoomID,
clientUID: clientUID,
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: db.collection('clienti').snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
clientUID = snapshot.data.documents[index]["UID"];
return Column(
children: <Widget>[
Divider(
height: 10.0,
),
new ListTile(
onTap: createChatRoom,
title: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
snapshot.data.documents[index]["numar_telefon"],
style: new TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
],
);
},
);
}
},
),
);
}
}
Error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
instructor is a instance variable in the class ClientiPage, thats why you can access it using the property widget. But it seems you are not initializing it correctly.
The uid will retrieve the currently logged in user id, you dont have to pass it inside a constructor or from a different screen, therefore you can do the following:
void saveChatRoom() async {
String userId = (await FirebaseAuth.instance.currentUser()).uid;
DocumentReference chatroomIdRef = db.collection('instructori').document(userId).collection("chatrooms").document(chatRoomID);
if (chatroomIdRef == null) {
db.collection('instructori').document(userId).collection("chatrooms").document(chatRoomID);
}
}
As long as the user is logged in, you can retrieve the uid using the following code (await FirebaseAuth.instance.currentUser()).uid. There is no need to pass it from screen to screen.
https://pub.dev/packages/firebase_auth

Save Bookmark Article in Firebase Flutter

In Flutter app I want to fetch data list
I want to save bookmark any article from article list in Fire store data base but when bookmark button tapped the same article save in the database every time. I want that article should save in database for the first time
Does anyone lead me to the correct way? Any help is highly appreciated!
My code
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp( MyHomePage());
class MyHomePage extends StatefulWidget {
#override
_MyHomePage createState() => _MyHomePage();
}
class _MyHomePage extends State<MyHomePage> {
String title;
String subtitle;
int id;
Firestore firestore = Firestore.instance;
DocumentSnapshot document;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('jdj'),
),
body: Container(
child: ListView(
children: <Widget>[
stremBuilder(),
Container(
height: 310,
color: Colors.amber,
)
],
),
));
}
Widget stremBuilder() {
return Container(
height: 200,
child: StreamBuilder(
stream: Firestore.instance.collection("User").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: Text("Loding"),
);
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return listItem(context, snapshot.data.documents[index]);
},
);
},
),
);
}
Widget listItem(BuildContext context, DocumentSnapshot document) {
return ListTile(
title: Text(document["title"]),
subtitle: Text(document["subtitle"]),
trailing: GestureDetector(
child: Icon(Icons.bookmark),
onTap: () {
setState(() {
saveData(id, document);
});
}),
);
}
Map<String, dynamic> savedata = {};
saveData(int id, DocumentSnapshot document) {
Map<String, dynamic> savedata = {
"id": id,
"saveTitle": document["title"],
'saveSubtitle': document["subtitle"]
};
Firestore.instance.collection("savedata").add(savedata);
}
}
It looks like on the right track. Checking the code, the tapped List item should be saved. If what you're looking for is to save the "bookmark" only once, and clicking on it again should remove the saved bookmark. Then you can delete the document upon pressing again.
await FirebaseFirestore.instance.collection('savedata').doc(docId).delete();
Make sure to keep track of the id of the document that you'd like to delete to be used as reference.

Resources