How to merge multiple streams inside a stream - firebase

I'm trying to use the tab bar and tab bar view to appear some elements of the fire base. First, I used stream builder to get the text of the tabs in the tab bar:
class HomePage extends StatelessWidget {
final FirebaseUser user;
HomePage({this.user});
#override
Widget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("places").snapshots(),
builder: (BuildContext context,AsyncSnapshot<QuerySnapshot> snapshot){
if (!snapshot.hasData){
return Center(child: CircularProgressIndicator());
}
else{
return DefaultTabController(
length: 20,
child: Scaffold(
appBar: AppBar(
title: Text("Home Page"),
bottom: TabBar( isScrollable: true,
tabs: new List.generate(snapshot.data.documents.length, (index) {
return new Tab(child: Text(snapshot.data.documents[index]['name'].toString().toUpperCase()));
}),)),
Then I want from the fire store to get a stream builder of collection named "temps" which has documents inside of it, every document id represents a document id in another collection named "users". In every document in users, i have a field named place. I already made the tabs and it works but,What I can't do is:
wanna get the document id of every document in collection temps, and get this document id and use it to access the documents which has the same id in "users" collection and check if the field place has the same value of the name in the tab bar i wanna appear it in the tab bar view!
How can i do this?

If I understood correctly, one solution would be creating a StatefulWidget, inside its State, using a local StreamController and pointing your StreamBuilder to it.
Separately, consume both Streams and add these items to your StreamController.
It would look a bit like that:
class YourClass extends StatefulWidget {
... createState() ...
}
class _YourClassState extends State<YourClass> {
StreamController<YourItem> _places;
#override
void initState() {
super.initState();
// the unified stream
_places = new StreamController();
// listening to changes of the first reference
CollectionReference places1Ref = Firestore.instance.collection("places1");
places1Ref.listen((snapshopt) {
// posting item to the unified streamController
_places.add(item);
});
// listening to changes of the second reference
CollectionReference places2Ref = Firestore.instance.collection("places2");
places2Ref.listen((snapshopt) {
// posting item to the unified streamController
_places.add(item);
});
}
#override
Widget build(BuildContext context) {
return StreamBuilder<YourItem>(
stream: _places.stream, // using here only the unified stream
builder: (context, snapshot) {
return YourWidgets();
}
);
}
}
This mockup is using YourItem as unified object, but you can use something else, including dynamic itself.

Related

State updated in Flutter UI not reflected in Firebase Firestore realtime

Here I have a stream of objects of which I'm returning from Firestore and being displayed in my UI. My StreamBuilder is properly returning the data from my database and is displaying my objects correctly in the UI, however I'm trying to accomplish a two way binding here, when my PlannerItemContainer is tapped and the value of the boolean isChecked changes, it updates in both the UI and in Firestore.
Now when I update my boolean's value, the change is reflected in real time in the UI as expected but when I attempt to make changes to the boolean from my UI, it's new value is not reflected in Firestore.
My diagnosis is that because PlannerItemContainer maintains its own state, my StreamBuilder isn't notified whenever a change is made.
My two questions are: 1.) Am I on the right track with my thinking and 2.) If I am is there a notifier function that I need to pass up from my PlannerItemContainer to the StreamBuilder that will notify it that there has been a change made to the PlannerItemContainer so that the realtime update is made to Firestore??
StreamBuilder(
stream: userProvider.watchHealthPlannerItems(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
plannerController.add(snapshot.data);
return Timetable<HealthPlannerItem>(
eventBuilder: (HealthPlannerItem item) {
return Row(
children: [
Expanded(
child: PlannerItemContainer(item),
),
],
);
},
);
},
);
Stream passed to StreamBuilder:
Stream<List<HealthPlannerItem>> watchHealthPlannerItems() {
return FirebaseFirestore.instance
.collection('organizations')
.doc(this.organizationId)
.collection('apps')
.doc(this.appId)
.collection('clients')
.doc(this.user.id)
.collection('healthplanner')
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
Map<String, dynamic> healthPlannerData = doc.data();
HealthPlannerItem healthPlannerItem =
HealthPlannerItem.fromJson(healthPlannerData);
return healthPlannerItem;
}).toList();
});
}
PlannerItemContainer widget:
class PlannerItemContainer extends StatefulWidget {
final HealthPlannerItem item;
PlannerItemContainer(this.item);
#override
_PlannerItemContainerState createState() => _PlannerItemContainerState();
}
class _PlannerItemContainerState extends State<PlannerItemContainer> {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() => widget.item.isChecked = !widget.item.isChecked);
},
child: widget.item.isChecked
? _buildCheckedItemContainer(widget.item)
: _buildUncheckedItemContainer(widget.item),
);
}
}
I'm not following everything completely but a quick scan shows me you're trying to change an immutable (final) variable "item" in your setState. you can move item into your state object and it can be mutable in there. Hopefully that gets you unstuck.

How to get firestore data in another widget

What the app can do so far: So far, I have an app that shows markers on a Google Map that get the coordinates from Firestore.
What I want: I want that when the user presses a marker, he comes to another screen on which some more data is presented (the data is also on the firestore).
The problem: I don't know how to get the data (for example the name) on the screen on which the details are to be displayed.
I have a onTap function, which calls goToDetailPage()
That`s the goToDetailPage() function:
void goToDetailPage() {
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => new detailPage()));
}
That's the code for "drawing" the marker:
void initMarker(specify, specifyId) async {
var markerIdVal = specifyId;
final MarkerId markerId = MarkerId(markerIdVal);
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(specify['location'].latitude, specify['location'].longitude),
infoWindow: InfoWindow(title: specify['name'], snippet: 'Shop'),
onTap: () {
goToDetailPage();
});
print(specify['location'].latitude);
nameTest = specify['name'];
setState(() {
markers[markerId] = marker;
print(markerId.toString() + '__________________________');
});
}
That's the code from the detail Page:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Detail Page'),
),
body: Column(children: <Widget>[Text(specify['name'])]),
);
The problem is that the specify from the specify['name'] in the detail page is red underlined and I don`t know what's the reason for that.
In the second page:
class Tester extends StatefulWidget {
String dataFromLastPage;
Tester({Key key, this.dataFromLastPage}) : super(key: key);
#override
_TesterState createState() => _TesterState();
}
class _TesterState extends State<Tester> {
#override
Widget build(BuildContext context) {
return Text(widget.dataFromLastPage); //"thisWillGoToTheNextPage!"
}
}
When navigating to that page, send data like this:
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => new Tester(dataFromLastPage: 'thisWillGoToTheNextPage!')));
Try and go accoording to what the docs say : https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes
However , there are several things the doc doesn't specify like
To use firestore first , you need to initialize you firestore app , for that you just write a specific piece of code in the main method of your dart file
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
To connect to your firestore database , simply type in your widget build function
CollectionReference <Variable name> = FirebaseFirestore.instance.collection(<Name of collection as string>);
Now if you are creating a list , you can simply use a StreamBuilder function and get the data in your document via (initialize snapshot as given in the documentation)
snapshot.data.docs[index].data()[<Identifier of your document such as
'Name' or 'location'>]

How to display a Firebase list in REAL TIME?

I have a TODO List function (Alarmas), but I feel I'm not taking advantage of Firebase's Realtime features enough.
The Widget displays the list very well, however when someone puts a new task from another cell phone, I am not being able to show it automatically, but I must call the build again by clicking on the "TODO button" in the BottomNavigationBar.
Is there a way that the new tasks are automatically displayed without doing anything?
I'm using streams to get the list...
#override
Widget build(BuildContext context) {
alarmaBloc.cargarAlarmas();
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
if (tareasList.length == 0) return _imagenInicial(context);
return ListView(
children: [
for (var itemPendiente in tareasList)
_crearItem(context, alarmaBloc, itemPendiente),
//more widgets
],
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center (child: Image(image: AssetImage('Preloader.gif'), height: 200.0,));
},
),
And, I read the Firebase Data in this way...
Future<List<AlarmaModel>> cargarAlarmas() async {
final List<AlarmaModel> alarmaList = new List();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp); // element.snapshot.value.
});
await resp.once().then((snapshot) {
print("Total list was loaded - ${alarmaList.length}");
}); //I'm using this await to be sure that the full list was loaded, so I can order and process it later
return alarmaList;
}
How can I display a List from Firebase in "true" Real Time?
To properly manage the state of asynchronously loaded data, you should:
Start loading/listening to the data in initState()
Set the data into the state (with setState()) when you receive it or it is updated.
Then render it from the state in the build method.
So in your code that'd be something like:
final List<AlarmaModel> alarmaList = new List(); // this is now a field in the `State` object
#override
void initState() {
super.initState();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp);
setState(() {
alarmaList = alarmaList;
})
});
}
#override
Widget build(BuildContext context) {
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
If you only want to repaint once you've gotten a complete update from the database, you can put the call to setState() in a value listener, just use onValue in that instead of once(), as you also want to catch the updates.

Initialize a variable with local database value

I want to initialize a variable with a value that I have in my local sql database. This is the code:
class _SettingsState extends State<Settings>{
String morning = 'empty';
#override
void initState() {
super.initState();
initAsync();
}
void initAsync() async{
morning = await DatabaseHelper.instance.queryOrario(1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text(morning),
),
);
}
The problem is that when I first arrive on this page, I have this error:
A non-null String must be provided to a Text widget.
referred to the line: child: Text(morning),
Then, when I hot reload the page, the code works fine. Why? What am I doing wrong? There is a way to do what I want to do?
(I'm assuming that morning's default value of 'empty' is something you added to your code before posting it here, and it's actuall default value is null. Otherwise, it would make no sense for you to be getting the error you are getting.)
The problem is that your widget is initializing and building before the future has a chance to finish. By the time you have assigned a value for morning, the widget has already been built. As such, when the widget does build, morning is still null. And Flutter heavily frowns on giving a null value to a Text widget.
Instead of depending on a race condition (where two parts of your code are racing to complete first and you are hoping they will complete in a particular order), use a FutureBuilder so you can safely execute futures and have your widget update itself when they are complete:
class _SettingsState extends State<Settings>{
#override
void initState() {
super.initState();
}
Future<String> initAsync() async {
String result = await DatabaseHelper.instance.queryOrario(1);
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: initAsync(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Text(snapshot.data);
},
),
);
}
}

Want to show a list of cards with text which is coming from textfield on a screen when the send button is pressed

I want to show a card with text containing in it. The text value is coming from the TextField input and it should instantly display the values on a new Card whenever the button is pressed.
I have created two separate files :
notestream.dart to display the cards, and notetextfield.dart to send the value to the database
notetextfield.dart
TextField
TextField(
controller: _textEditingController,
textInputAction: TextInputAction.newline,
onChanged: (value) {
messageText = value;
noteText = value;
},
......
......
),
onPressed
IconButton(
onPressed: () {
_textEditingController.clear();
/Implement send functionality.
final newNote = Note(noteText: noteText);
if (newNote.noteText.isNotEmpty) {
/*Create new Note object and make sure
the Note textis not empty,
because what's the point of saving empty
Note
*/
noteBloc.addNote(newNote);
noteBloc.getNotes();
}
},
notestream.dart
The card will be generate with the help of a separate file containing code for the cards.
final NoteBloc noteBloc = NoteBloc();
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: noteBloc.notes,
builder: (
BuildContext context,
AsyncSnapshot<List<Note>>snapshot
) {
if (snapshot.hasData) {
/*Also handles whenever there's stream
but returned returned 0 records of Note from DB.
If that the case show user that you have empty Notes
*/
return snapshot.data.length != 0
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, itemPosition) {
Note note = snapshot.data[itemPosition];
return NoteCard(
noteText: note.noteText,
noteImagePath: note.noteImagePath,
);
})
bloc.dart
class NoteBloc {
//Get instance of the Repository
final _noteRepository = NoteRepository();
final _noteController = StreamController<List<Note>>.broadcast();
get notes => _noteController.stream;
NoteBloc() {
getNotes();
}
getNotes({String query}) async {
//sink is a way of adding data reactively to the stream
//by registering a new event
_noteController.sink.add(await _noteRepository.getAllNotes(query:
query));
}
addNote(Note note) async {
await _noteRepository.insertNote(note);
getNotes();
}
updateTodo(Note note) async {
await _noteRepository.updateNote(note);
getNotes();
}
dispose() {
_noteController.close();
}
}
Whenever I am pressing the onPressed button from the notetextfield.dart file the list of cards are not getting displayed on the screen.
Looks like you're using a different instance of NoteBloc on each file, you should have one single source of truth.
Try using the provider library to provide instances on parent and consume it on its children.
you would provide the bloc on the parent Widget like this
Provider<NoteBloc>(
builder: (context) => NoteBloc(noteRepository: NoteRepository()),
dispose: (context, value) => value.dispose()
child: ParentWidget(),
)
and you can consume it on any child like this
final bloc = Provider.of<NoteBloc>(BuildContext context)
or this if you prefer to wrap the Widget
Consumer<NoteBloc>(
builder: (context, bloc, _) => ChildWidget()

Resources