how to save user data got from Firebase ui for auth to firestore? - flutter

Firebase_ui_auth saves me lot of time. but i can't save the data from this package into firestore.
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(
providers: [
EmailAuthProvider(),
GoogleProvider(
clientId:
"client_id")
],
);
}
return const HomePage();
},
),
the ui gives only this codes
EmailAuthProvider() or GoogleProvider()
how can i perform .then() clause or something else to save those user data to firestore

Related

How to make Flutter Stream Builder return seamlessly

I have a little problem here where i have logged in with Google Auth using Firebase but everytime i tried to restart the app i expect the app will show the HomePage() without any problem, but i found that before it return, the app had like a bit seconds in LoginPage() before displaying HomePage(), is there any way to make it seamlessly
class AuthService extends StatelessWidget {
const AuthService({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return HomePage();
} else {
return LoginPage();
}
},
),
);
}
}
It is happening because for snapshot to reach snapshot.hasData state it takes time, and meanwhile else part is executed which is LoginPage().
How to overcome this?
Try to wrap within snapshot.connectionState == ConnectionState.active which means once stream is connected then check the condition else return CircularProgressIndicator
Code:
StreamBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
return HomePage();
} else {
return LoginPage();
}
}
return const CircularProgressIndicator();
},
);

Continuously Retrieving data from stream builder

I am trying to implement stream builder with cloud firestore to retrieve field data.
Here is the code:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("test builder"),
),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('joystick').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, i){
QueryDocumentSnapshot<Object?>? ds = snapshot.data?.docs[i];
return Text("$snapshot.data?.docs[i].data()!['call']");
});
}
),
);
}
However, it does not output the actual data stored in the database.
I get the following output:
AsyncSnapshot<QuerySnapshot<Object?
>>(ConnectionState.active,Instance of '_JsonQuerySnapshot',null, null).data?.docs[i].data()!['call']
What should I do to get the data stored in the database? (The field name is 'call')
In order to implement stream builder with cloud firestore to retrieve field data, you can follow the StreamBuilder Documentation and link related to explanation of streambuilder in flutter.
As mentioned the thread, you can go through the following code on retrieving data through streambuilder :
body: new StreamBuilder(
stream: Firestore.instance.collection("collection").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text( 'No Data...', );
}
else {
<DocumentSnapshot> items = snapshot.data.documents;
return new Lost_Card(
headImageAssetPath : items[0]["url"] );
}
If you want to create list builder from many documents use it like
this
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Lost_Card(
headImageAssetPath : ds["url"];
);
For more information, you can refer to the Documentation where real time updates with cloud firestore have been explained.
Try this one to return your data
return Text(ds['enterYourDataFieldName']);
What you are trying to achieve concerns RealTime database, Firestore database is not made for that.

How do I iterate through all documents in collection to show content in ListView?

My Goal
I want to iterate through one collection containing 1…n documents. I want to put the content of the documents in in ListView. The collection represents the ListView and each document should be one ListTile.
My Firestore Data
I have a Firestore database containing one collection called “current_question” containing multiple documents. Each document contains the details about one question: title, description, date and so on.
My doing so far
I know how to show the data of one document. What I don’t know is, how to load all documents and publish them.
The code for loading one document:
body: FutureBuilder(
future: connectToFirebase(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<DocumentSnapshot>(
stream: database.getQuestions(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
Map<String, dynamic> items = snapshot.data.data;
print(items);
return ListView.builder(
itemCount: 1,
itemBuilder: (context, i) {
return ListTile(
title: Text(items['Titel'].toString()),
trailing: Text(items['Zaehler'].toString()),
);
}
);
}
},
);
}
}),
Future<void> connectToFirebase() async {
final FirebaseAuth auth = FirebaseAuth.instance;
AuthResult result = await auth.signInAnonymously();
user = result.user;
database = DatabaseService();
}
final CollectionReference aktFragen = Firestore.instance.collection('aktFragen')/*.orderBy('Zaehler')*/;
Stream getQuestions() {
return aktFragen.document('xAGRoZCgiClrpeAPtb5B').snapshots();
}
See the result in the screenshot:
enter image description here
If you want to retrieve all documents then you will have to do a QuerySnapshot instead. Here is one way of doing it:
// Creating an instance of your model with all the users info
List<CustomModelNameHere> _myListSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return CustomModelNameHere(
title: doc.data["title"] ?? "",
description: doc.data["description"] ?? "",
);
}).toList();
}
// Get the stream
Stream<List<CustomModelNameHere>> get getQuestions {
return aktFragen.snapshots()
.map(_myListSnapshot);
}
Great answer by #Unbreachable!
This is how I implemented it:
#override
Widget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('currentQuestions').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return new ListView(
children: snapshot.data.documents.map((document) {
return new ListTile(
title: new Text(document['title'].toString()),
subtitle: new Text(document['count'].toString())
);
}).toList()
);
}
}
);
}
I also found a good youTube tutorial for this topic:
https://www.youtube.com/watch?v=Ak_6_pBBe3U&feature=youtu.be

Firestore changes are not synced with stream builder

When I update the database in console or app, the changes are not synced in the app. I thought it's because of orderBy and indexing settings, but commenting them didn't help. What's wrong with my code? How to set up auto-sync?
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerMenu(),
appBar: AppBar(
title: Text('Заявки'),
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
// .orderBy('dateAdded', descending: true)
// .orderBy('statusId')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingScreen();
default:
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Items(
order: snapshot.data.documents[index],
),
)),
child: OrderCard(
order: snapshot.data.documents[index],
)));
}
},
),
);
}
Its because you are just getting the snapshot as a stream once, but to get the actual changes when you modify in console or app you need to make a subscription to the stream. You need to .listen the stream of data so that you get notified when something change.
Also you may want to separate the logic of firebase its easier.
For example:
Create a Stream Controller:
final StreamController<DataType> _streamController = StreamController<DataType>();
Listen to firebase data and add it to stream:
Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
.snapshots().listen((DocumentSnapshot snapShot){
_streamController.add(snapShot.documents);
});
Now you can listen to the stream from stream controller:
_streamController.stream;
It turned out that it's necessary to add custom indexes in firestore console when a query consists of .where().orderBy and so on.

Is nested StreamBuilder good pattern?

I am fetching articles from HackerNews API using Bloc Pattern and Streams.
I am loading all the articles and presenting in the UI with the help of a stream builder, and this works fine.
Now I wrapped the article fetching Stream builder with the new loading StreamBuilder.
Now when the loading stream builder has true (means it is loading) it shows a circular progress indicator or else, it shows the child (Article List wrapped with a Stream Builder).
This works fine. But it is bugging me that I have wrapped Stream builder inside a stream builder. I know I can take help of rxdart but I am just not sure how.
I tried to add a loader with the help of snapshot.hasData or not but that didn't work, so I decided to create another stream and subject that takes a bool and tells the UI if it is loading or not.
Code fetching data int the bloc:
_getAndUpdateArticles(StoryType storyType) {
_isLoadingSubject.add(true);
getListIds(storyType).then((list) {
getArticles(list.sublist(0, 10)).then((_){
_articleSubject.add(UnmodifiableListView(_articles));
_isLoadingSubject.add(false);
});
});
}
UI:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder(
stream: widget.hnBloc.isLoading,
builder: (context, snapshot) {
if (snapshot.data) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) => ListView(
children: snapshot.data.map(_buildItem).toList(),
),
);
}
},
),
.........
EDIT
I have tried this, but this isn't working:
StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
} else {
return CircularProgressIndicator();
}
}
),
I Don't think there is a complete way to avoid nested StreamBuilders. I personally wouldn't consider it a bad practice, but it will definitely lead to more build.
In your case, You can modify your hnBloc to emit a single state that can be a loading state or data state , thereby eliminating the need for a nested StreamBuider.
eg.
StreamBuilder<HnState>(
stream: hnBloc.currentState,
initialData: HnLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is HnLoadingState) {
return Center(child: CircularProgressIndicator());
}if (snapshot.data is HnDataState) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
),
}
},
)
This pattern is very common when using the flutter_bloc package. You can see a basic example of this here to understand it better.