StreamBuilder data == null - flutter

#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).currentUserData,
builder: (context, snapshot) {
print(snapshot.data); // return null
DatabaseService class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
//collection reference
CollectionReference userCollection = Firestore.instance.collection('user');
//get user data stream
Stream<UserData> get currentUserData {
return userCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
//user data from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(
uid: snapshot.data['uid'],
email: snapshot.data['email'],
fName: snapshot.data['first_name'],
lName: snapshot.data['last_name'],
mobileNumber: snapshot.data['mobile_number'],
gender: snapshot.data['gender'],
dob: snapshot.data['date_of_birth'],
);
}
}
The data that I expected to get is Instances of the object I called.
it return flutter: null.
When I tried in my practice app it return I/flutter (11395): Instance of 'UserData'
I don't know where the source of the problem, please kindly help. I'm new in Flutter.

As we are using Streams it is possible that at the time of widget rendering no data has been fetched yet. So we need to check the state of the stream before trying to access the data like below
if (snapshot.hasError){
// access error using snapshot.error
}
else{
// check for stream state
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active: // access snapshot.data in this case
case ConnectionState.done:
}
}
For more reference, you can have a look at the document of StreamBuilder

Related

Nested StreamBuilders Flutter

So I'm currently using this nest of two streams, one to listen for AuthStateChanges, to know if the user is logged in, and another that listens to a firebase document snapshot request, to know if the user has already setup is account or not.
My problem is that the latter StreamBuilder(_userStream) only runs if the firts one runs, meaning that the only way for my _userStream to run is if the user either logs in or logs out(authStateChanges Stream).
This is inconvinient because after the user creates an account(moment where i run Auth().createUserWithPasswordAndEmail()), I need the user to go throw the process of seting up the account, and only after that the user can acess the mainPage. Only in the end of seting up the account theres a button to "Create Account", which changes the "HasSetupAccount" parameter in firebase to true. But because of the nested Streams problem, the app doesn't go to the mainPage until I force update it.
I hope my question is not as confusing as it looks :)
class _WidgetTreeState extends State<WidgetTree> {
#override
//construtor da class?
Widget build(BuildContext context) {
return StreamBuilder(
stream: Auth().authStateChanges,
builder: (context, snapshot) {
if (snapshot.hasData) {
return StreamBuilder(
stream: _userStream(),
builder:
((context, AsyncSnapshot<DocumentSnapshot> userSnapshot) {
if (userSnapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else {
Map<String, dynamic> userData =
userSnapshot.data!.data() as Map<String, dynamic>;
print(userSnapshot.data!.data().toString());
if (userData['HasSetupAccount'] == true) {
return MyHomePage();
} else {
return AccountSetup();
}
}
}));
} else {
return LoginPage();
}
},
);
}
Stream<DocumentSnapshot<Map<String, dynamic>>> _userStream() {
return FirebaseFirestore.instance
.collection('Users')
.doc(Auth().currentUser!.uid)
.snapshots();
}
}

Conver a stateLessWidget to function

I'm a new flutter developer.
I have a code to read data from firebase for one time
this code:
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
it's work fine but I want to put these method into my Provider as function like this
Future<DocumentSnapshot> getUserName(String uid) => _database.doc(uid).snapshots();
so I want to put a function into provider class when I call this function it return a field data of this documents... (Replace GetUserName class as shown app, to be a function method only)
so how to write this function and how to call it as a map of data?
Edit:
as shown in this image:
here I got data as StreamBuilder and its work fine
here the explained method for stream in my provider class
as shown in the Following Image
Map<String, dynamic> data
I use data like
data['username']
it works fine so I want to put in My Provider class a function and returns a String, has two parameters for Example:
Text(myfunction(uid, value));
and it returns a string from (uid),
value = data[value]
add it before return HomePage();
auth.userData = data;
return HomePage();
add it in Auth provider
class Auth implements AuthBase {
Map<String,dynamic>? userData;
String getUserName(String parameter) {
return userData![parameter].toString();
}
}

snapshot.ConnectionState is always waiting in the FutureBuilder using provider package

I am using cloud firestore as a backend and the provider package for state management for my app. I have a BooksProvider class (ChangeNotifierProvider) which I use to fetch, add, update and delete data. In this class, I have an async method to fetch data from cloud firestore.
Future<void> fetchAndSetData() async {
try {
final response = await bookCollection.getDocuments();
List<Book> loadedBooks = [];
final querySnapshot = response.documents;
if (querySnapshot.isEmpty) {
return;
}
querySnapshot.forEach((book) {
loadedBooks.add(
Book(
id: book.documentID,
title: book.data['title'],
description: book.data['description'],
image: book.data['image'],
rate: book.data['rate'],
category: CategoryName.values[book.data['category_index']],
date: book.data['date'],
price: book.data['price'],
isFree: book.data['isFree'],
isFavorite: book.data['isFavorite']),
);
});
_booksList = loadedBooks;
notifyListeners();
} catch (e) {
throw e;
}
}
I am trying to use it with the future builder but it doesn't work as it is always stuck in ConnectionState.waiting case.
class BooksScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// final booksData = Provider.of<BooksProvider>(context);
// final books = booksData.booksList;
// final _noContent = Center(child: Text('No books are available.'));
return FutureBuilder(
future: Provider.of<BooksProvider>(context).fetchAndSetData(),
builder: (ctx, dataSnapshot) {
switch (dataSnapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
return Text('waiting'); //always returning waiting
break;
case ConnectionState.done:
return Text('done');
break;
case ConnectionState.none:
return Text('none');
break;
default:
return Text('default');
}
},
);
}
}
I don't know what might be wrong, I tried to change the return type of the async method so that it returns a list of Book but it didn't work. I also used if statement instead of the switch case and the same thing kept happening.
In addition, I tried it with if (dataSnapshot.hasdata) as it is suggested by on of the answers, and it worked!, but i want to use a spinner while waiting and when i tried it, the same problem occurred again.
I tried the same method with another approach where i used stateful widget and didChangeDependencies() method to fetch the data and it worked just fine, and I am trying future builder approach because I don't know how to handle errors with didChangeDependencies() approach.
so if you please can help me with the future builder or error handling with the latter approach. Thank you in advance.
Use dataSnapshot.hasData instead of dataSnapshot.connectionState

Flutter: Future<String> returns null but if printing it has value

username is null but if I'm printing 'value' it contains some string, how can I get 'value'?
class HomeWrapper extends StatelessWidget {
final DataBaseServices _db = DataBaseServices();
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
String username;
_db.getUsername(user).then((value) => username = value);
print(username);
if(username != null){
return Home();
}else{
_db.createBlankUser(user);
return EditProfile();
}
}
.then() is called when the value of the Future is returned. So the value of value is always non null, whereas username is null when you print it.
Try the difference by replacing .then(...) with:
.then((value){
username = value;
print(username);
});
Additionally, you can have a look at how to handle Asynchronous data in Flutter
I'm guessing _db.getUsername is returning a Future?
In that case you should look into using FutureBuilder
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
return FutureBuilder(
builder: (context, snap) {
//snap.data will be the username
if(snap.hasData) {
return Home();
} else {
//you need to wait for another Future here I guess?
return FutureBuilder(
builder: (context, snap2){
if(snap2.connectionState == ConnectionState.done){
return EditProfile();
} else {
//return some sort of circular loader icon.
}
},
future: _db.createBlankUser(user)
);
}
},
future: _db.getUsername(user),
);

How to make the connection to waiting state by using StreamBuilder in flutter

My requirement is to make that StreamBuilder connection state to waiting.
I'm using publish subject, whenever I want to load data in stream builder I'm just adding data to the sink by calling postStudentsToAssign() method, here this method making an API call which takes some time, in that time I to want make that streamBuilder connection state to waiting
Stream Builder:
StreamBuilder(
stream: studentsBloc.studentsToAssign,
// initialData: [],
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// While waiting for the data to load, show a loading spinner.
return getLoader();
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return _getDrawer(snapshot.data);
}
}),
Initializing Observable:
final _assignStudentSetter = PublishSubject<dynamic>();
Observable<List<AssignMilestoneModel>> get studentsToAssign =>
_studentsToAssignFetcher.stream;
Method that add's data to Stream:
postStudentsToAssign(int studyingClass, String milestoneId, String subject,
List studentList) async {
var response = await provider.postAssignedStudents(
studyingClass, milestoneId, subject, studentList);
_assignStudentSetter.sink.add(response);
}
You can send null to the stream, so the snapshot.connectionState changes to active. I don't know why and whether it's official solution, but it works (at least now). I found this accidentally.
I would like the Flutter team to explain how to set snapshot's connectionState. It's not clear from StreamBuilder documentation. It seems you should replace the stream with a new one to have snapshot in waiting state. But it's agains the logic you want to implement.
I checked StreamBuilder source to find out that the AsyncSnapshot.connectionState starts as waiting (after stream is connected), after receiving data changes to active. snapshot.hasData returns true if snapshot.data != null. That's how following code works.
class SearchScreen extends StatelessWidget {
final StreamController<SearchResult> _searchStreamController = StreamController<SearchResult>();
final SearchService _service = SearchService();
void _doSearch(String text) async {
if (text?.isNotEmpty ?? false) {
_searchStreamController.add(null);
_searchService.search(text)
.then((SearchResult result) => _searchStreamController.add(result))
.catchError((e) => _searchStreamController.addError(e));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
SearchBar(
onChanged: (text) => _doSearch(text),
),
StreamBuilder<SearchResult>(
stream: _searchStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<SearchResult> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = Expanded(
// show search result
);
}
else if (snapshot.hasError) {
widget = Expanded(
// show error
);
}
else if(snapshot.connectionState == ConnectionState.active){
widget = Expanded(
// show loading
);
}
else {
// empty
widget = Container();
}
return widget;
},
),
]),
);
}
}