Why does my Firestore StreamBuilder skip when scrolling up if it's put inside a CustomScrollView? - firebase

For some reason, my StreamBuilder skips a whole Container whenever I try scrolling up. However, it only starts skipping Containers when I start scrolling up from a certain point in my Firestore stream. Maybe like 2000 or so pixels down? Either way, the important aspect is that this does NOT happen when I put a normal container in the same scenario-only with StreamBuilders. This only issue only happens when I put it inside a CustomScrollView. (See video below)
This whole thing occurred after I was trying to wrap my StreamBuilder in a Column/ListView so that I could put other Containers above the Stream Containers. Wrapping it in a Column/ListView didn't work because, in a Column, the other container would not scroll down with the rest of the stream. In a ListView, the StreamBuilder had its own scrolling system that was separate.
Basically, my goal is to have one big list of widgets that scroll. There will be some at the top that will be separate from the ones that came from the Firestore (which will be at the bottom). Upon clicking on one of those Firestore Containers, I would be taken to another page.
This is the closest I've gotten to get my goal, but if there's another way, I'd love to hear it.
Video:
Only the GREEN Containers are from the Firestore StreamBuilder. NOT the red or blue ones.
https://youtu.be/jOo08oLhZP8
Code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
class GeneralTopicsPage extends StatefulWidget {
GeneralTopicsPage({this.user, this.googleSignIn, this.index});
final FirebaseUser user;
final GoogleSignIn googleSignIn;
var index;
#override
_GeneralTopicsPageState createState() => new _GeneralTopicsPageState();
}
class _GeneralTopicsPageState extends State<GeneralTopicsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
title: Text(
"Testing",
),
),
SliverPadding(
//Works perfectly fine.
padding: EdgeInsets.all(16.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
margin: EdgeInsets.all(20.0),
color: Colors.red,
height: 400.0,
child: Center(
child: Text(
"This is a normal, hardcoded Container in red and it works perfectly fine. Notice the lack of skipping when scrolling up.",
style: TextStyle(
color: Colors.white
),
textAlign: TextAlign.center,
),
),
);
},
childCount: 7,
),
),
),
SliverPadding(
//This is where it doesn't work as intended. Its behavior is separate from the containers located above and below it because it suddenly includes a StreamBuilder.
padding: EdgeInsets.all(16.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return StreamBuilder(
stream: Firestore.instance.collection("content").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return Container(
margin: EdgeInsets.all(20.0),
color: Colors.green,
height: 400.0,
child: Center(
child: Text(
snapshot.data.documents[index]['title'],
style: TextStyle(
color: Colors.white
),
textAlign: TextAlign.center,
),
),
);
}
},
);
},
childCount: 8,
),
),
),
SliverPadding(
//Works perfectly fine.
padding: EdgeInsets.all(16.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
margin: EdgeInsets.all(20.0),
color: Colors.blue,
height: 400.0,
child: Center(
child: Text(
"This is a normal, hardcoded Container in blue and it works perfectly fine. Notice the lack of skipping when scrolling up.",
style: TextStyle(
color: Colors.white
),
textAlign: TextAlign.center,
),
),
);
},
childCount: 3,
),
),
),
],
),
);
}
}

Related

Flutter/Firebase - Right Track?

At 54 I'm self-learning Flutter and have come a long way. I am stuck (again) with a speed-bump concerning my implementation of Firebase. Although I am getting the very basic of displaying a line of data for each record the problem is getting to the details of each record. I believe the issue is that I am not referencing the record ID.
When looking at endless examples I also believe my code to display a ListView of records is bloated making it more difficult to grab a record and display all fields (edit screen).
I want to click the "Walsh-Test" links to view all fields and update. I can create a new edit/update screen my initial problem is opening the screen to the selected record.
Any advice would be greatly appreciated.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/rendering.dart';
class MeterReadHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Color(0xff4367b1),
automaticallyImplyLeading: false,
leading: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
color: Colors.white,
),
),
title: Text(
"WelakaOne",
style: TextStyle(
color: Colors.white,
),
),
),
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('meter_reads')
.orderBy('accountname')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return Padding(
padding: const EdgeInsets.fromLTRB(6, 20, 0, 0),
child: Container(
child: GestureDetector(
onTap: () {},
child: ListView(
children: snapshot.data.docs.map(
(document) {
return Row(
children: [
Container(
width: 50,
child: Icon(
Icons.access_alarm,
size: 36,
color: Color(0xff4367b1),
),
),
Container(
child: Text(
document['accountname'],
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xff4367b1),
),
),
),
SizedBox(height: 44),
],
);
},
).toList(),
),
),
),
);
},
),
);
}
}
[![Flutter/Firebase/Open to Selected Record][1]][1]
I understand that you want to go to another page (Edit Screen) onTapping the alarm tile.
So, my suggestion is to use A ListTile widget instead of the row widget you are using.
You can read about ListTile's onTap property, here
Regarding your routing to a new screen/page, you'll have to make use of MaterialPageRoute to create a new route and using Navigator's push method you can push it onto your Material App.
Attaching an example code that uses Navigator to go to different pages, you need the BuildContext of your app. Here is an example of how you can get it:
import 'package:flutter/material.dart';
import 'package:rate_your_professor/screens/firstScreen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Some App',
home: SomeApp(),
);
}
}
class SomeApp extends StatelessWidget {
Widget getListView(BuildContext context) {
var listView = ListView(
children: <Widget>[
Text(
"XXXXXXXXXXXXXXX",
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
),
ListTile(
leading: Icon(Icons.location_city),
title: Text("XXXXX ", textDirection: TextDirection.rtl),
subtitle: Text(
"XXXXXXXXXX",
textDirection: TextDirection.rtl,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => YourNewPage(),
),
);
},
),
],
);
return listView;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: getListView(context));
}
}

firestore map to StreamBuilder => ListView.Builder

i want to show the songs list inside document (singer that user clicked on). Every song should load in list tile but all of them load in one tile.
and it loads the 'songs list' from all documents(all singers).
this is the FireStore DB
this is list of singers to choose from.
this should show only the songs from selected singer each one in a tile but shows all songs from all singers. and every singers song in one tile
class SongsList extends StatefulWidget {
#override
_SongsListState createState() => _SongsListState();
}
class _SongsListState extends State<SongsList> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance.collection('singers').snapshots(),
builder: (
context,
snapshot,
) {
if (snapshot.data == null)
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.red,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.teal),
),
);
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/back.png'), fit: BoxFit.contain)),
child: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
var result = snapshot.data.documents[index]['songs list'];
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(
left: 10, right: 10, top: 10, bottom: 0),
child: Container(
height: 50,
width: 300,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.5),
spreadRadius: 1.5,
blurRadius: 1.5,
//offset: Offset(0, 1), // changes position of shadow
),
],
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.red[200],
width: 0.5,
style: BorderStyle.solid)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (var res in result.entries)
Text(
res.key,
style: TextStyle(
fontSize: 20, color: Colors.red[500]),
),
]),
),
),
);
}),
);
},
),
);
}
}
If you want to get only the songs of one singer, then you need to specify the document id to retrieve one document, change this:
stream: Firestore.instance.collection('singers').snapshots(),
into this:
stream: Firestore.instance.collection('singers').document('aryana sayeed').snapshots(),
List tile has a corresponding index. I think you might have to build a list tile instead of a container. If you need a container, you have to write a code that would specifically get the singers name (documentID) wired on each container

How to place dummy image in CircleAvatar before retrieval of the main image from cloud firestore database?

Placing a dummy image inside a CircleAvatar. The dummy image is replaced by the main image which is fetched from cloud firestore database.
Here I am trying to make Profile-image which contain User-Profile for that I have used CircleAvatar which will store image in it. Retrieval of image is done well & working properly. But now I have mixed up with some "Question" if technically the profile URL is not saved in firestore database then it will throw error. So to stop the prevention of the error I need to use dummy image which will be replaced by the main image fetched from cloud firestore database.
Now the Q:- How can I place dummy image inside a CircleAvatar and that dummy image is replace by the main image
class DetailPage extends StatefulWidget {
final DocumentSnapshot post;
DetailPage({this.post});
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Detail'),
),
backgroundColor: Colors.white,
body:
Form(
child: ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
SizedBox(
height: 20.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.center,
child: CircleAvatar(
radius:73 ,
backgroundColor: Colors.blue,
child: ClipOval(
child: new SizedBox(
width: 125,
height:125,
child: Image.network(widget.post.data['image'],height: 108,fit: BoxFit.fill,),
),
),
),
),
],
),
],
),
),
);
}
}
You can Look into this. this supports the functionality you require.
https://pub.dev/packages/cached_network_image
you can put any widget in the placeholder. and it also supports erros indicators
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),

How to get data from two collections in firebase using Flutter

This is my problem:
I have a ListPost StatefulWidget where I want to display a list of widgets that contains the user's account image, the user's name, and the user's posts images(similar to Facebook feeds), however, I have gotten to the point that I need to get that data from two different collections in Firebase (see my firebase collections image below).
The good thing is that I have been able to get that data only from one collection(userFeed) and display that data in my ListPost file in different widgets, however, I do not know how to get data from another collection in Firebase using the same streamBuilder and display all that data I want to display in other widgets in my ListPost screen.
So, my specific question is:
How can I make my ListPost screen to populate data from 2 different collections in Firebase using a stream builder or another type of implementation?
This is the firebase image
This is the complete code for the ListPost screen
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'models/post_model.dart';
final _stream = Firestore.instance.collection('userFeed').snapshots();
class ListPosts extends StatefulWidget {
#override
_ListPostsState createState() => _ListPostsState();
}
class _ListPostsState extends State<ListPosts> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//this is the Streambuilder to get the data however it only lets me to get one collection
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int data) {
//here I get the data from the userFeed colecction
Post post = Post.fromDoc(snapshot.data.documents[data]);
return Column(
children: <Widget>[
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
child: Row(
children: <Widget>[
CircleAvatar(
radius: 25.0,
backgroundColor: Colors.grey,
backgroundImage: post.imageUrl.isEmpty
? AssetImage(
'assets/images/user_placeholder.jpg')
: CachedNetworkImageProvider(post.imageUrl),
),
SizedBox(width: 8.0),
Text(
post.caption,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
GestureDetector(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image:
CachedNetworkImageProvider(post.imageUrl),
fit: BoxFit.cover,
),
),
),
],
),
),
],
);
},
);
},
),
),
);
}
}
UPDATE 05-22-2020 HOW I FIXED THE ISSUE
Credits to the user griffins, he helped me to fix this issue.
This is what I do:
I nested my StreamBuilder so I can use 2 streams at the same time
return StreamBuilder(
stream: _stream,
builder: (context, snapshot1) {
return StreamBuilder(
stream: _stream2,
builder: (context, snapshot2) {
if (!snapshot2.hasData) return const Text('Loading...');
if (!snapshot1.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot2.data.documents.length,
itemBuilder: (BuildContext context, int data) {
User user = User.fromDoc(snapshot2.data.documents[data]);
Post post = Post.fromDoc(snapshot1.data.documents[data]);
return buildbody(user, post, context);
},
);
},
);
},
);
You can can make you body take a widget ListView and for the Listview children have both your lists.
example
body: ListView(
children: <Widget>[
---list1----
--list2-----
]);
or you can use a custom scroll view
return new Scaffold(
appBar: new AppBar(
title: new Text("Project Details"),
backgroundColor: Colors.blue[800]),
body:
new CustomScrollView(
slivers: <Widget>[
new SliverPadding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,
top: 10.0,bottom: 0.0),
sliver: new SliverList(delegate:
new SliverChildListDelegate(getTopWidgets())),
),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getSfListTiles()
))),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getWorkStatementTiles()
))),
]
)
);
update
from #RĂ©mi Rousselet answer You can nest StreamBuilder
StreamBuilder(
stream: stream1,
builder: (context, snapshot1) {
return StreamBuilder(
stream: stream2,
builder: (context, snapshot2) {
// do some stuff with both streams here
},
);
},
)

Flutter Gridview button functionality to new screen with Firebase

I've made a Gridview using Firebase and Streambuilder and Gridview.builder. This grid displays album titles, the album cover art for each album, and the artists that make each album. I'd like for each grid tile to be able to be pressed and navigate to a separate page with its specific album details. The plan was on press, the app would be able to identify the entire document the grid tile was referring to, move to a new page, and display the document in full to unveil the album details. The thing is, I don't know how to do that. Since snapshot.data.documents[index]['Title'] worked when iterating though all the documents to create the gridview, I thought that typing snapshot.data.documents[index] would work, but it just displays Instance of 'DocumentSnapshot' in the debug console. I'm out of ideas on how to tackle this, so any suggestions are welcome
My code is shown below
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final Services services = Services();
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bgcolour,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: Icon(Icons.menu),
title: Text("Home"),
actions: <Widget>[
Padding(padding: EdgeInsets.all(10), child: Icon(Icons.more_vert))
],
),
body: StreamBuilder(
stream: Firestore.instance.collection('music').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loading...");
return GridView.builder(
itemCount: snapshot.data.documents.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, childAspectRatio: 0.655172413),
//cacheExtent: 1000.0,
itemBuilder: (BuildContext context, int index) {
var url = snapshot.data.documents[index]['Cover Art'];
return GestureDetector(
child: Container(
width: 190.0,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32)),
color: hexToColor(
snapshot.data.documents[index]['Palette'][0]),
elevation: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(21.0),
child: Image.network(url,
height: 180.0, width: 180)),
SizedBox(height: 10),
Text(
snapshot.data.documents[index]['Artist']
.join(', '),
textAlign: TextAlign.center,
style: GoogleFonts.montserrat(
textStyle: TextStyle(color: Colors.white),
fontSize: 14,
fontWeight: FontWeight.w300)),
SizedBox(height: 10),
Text(snapshot.data.documents[index]['Title'],
style: GoogleFonts.montserrat(
textStyle: TextStyle(color: Colors.white),
fontSize: 16,
fontWeight: FontWeight.w600),
textAlign: TextAlign.center),
],
),
),
),
onTap: () {
print("Tapped ${snapshot.data.documents[index]}");
},
);
},
);
}
),
);
}
}
Is there an ID for your snapshot.data.documents[index]? If yes, add it to the end.
onTap: () {
print("Tapped ${snapshot.data.documents[index]['the property you want']}");
},

Resources