I'm developing a realtime chat app with fisebase. The problem is that the time of a sent message does not update and I don't know what else can I do.
Here's some part of my code:
import 'package:flutter/material.dart';
import 'package:heyou/helper/constants.dart';
import 'package:heyou/screens/conversation_screen/send_menu_items.dart';
import 'package:heyou/services/database.dart';
import 'package:intl/intl.dart';
class ConversationScreen extends StatefulWidget {
final String chatScreenId;
ConversationScreen(this.chatScreenId);
#override
_ConversationScreenState createState() => _ConversationScreenState();
}
class _ConversationScreenState extends State<ConversationScreen> {
DateTime _currentDate = new DateTime.now();
DatabaseMethods databaseMethods = new DatabaseMethods();
TextEditingController messageController = new TextEditingController();
Stream chatMessageStream;
Widget chatMessageList() {
return StreamBuilder(
stream: chatMessageStream,
builder: (context, snapshot) {
return snapshot.hasData ? ListView.builder(
padding: EdgeInsets.only(bottom: 70.0),
itemCount: snapshot.data.documents.length,
reverse: true,
itemBuilder: (context, index) {
return MessageTile(
snapshot.data.documents[index].data['message'],
snapshot.data.documents[index].data['sendBy'] == Constants.myName,
snapshot.data.documents[index].data['time'],
snapshot.data.documents[index].data['messageTimeTile'],
);
}
) : Container();
},
);
}
sendMessage() {
if(messageController.text.isNotEmpty) {
Map<String, dynamic> messageMap = {
'message': messageController.text,
'sendBy': Constants.myName,
'time': DateTime.now().toString(),
'messageTimeTile': new DateFormat.Hms().format(_currentDate).toString(),
};
databaseMethods.addConversationMessages(widget.chatScreenId, messageMap);
messageController.text = '';
}
}
#override
void initState(){
databaseMethods.getConversationMessages(widget.chatScreenId).then((value) {
setState(() {
chatMessageStream = value;
});
});
super.initState();
}
}
And here's my database code:
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseMethods {
getUserByUsername(String username) async {
return await Firestore.instance.collection('users').where('name', isEqualTo: username).getDocuments();
}
getUserByUserEmail(String userEmail) async {
return await Firestore.instance.collection('users').where('email', isEqualTo: userEmail).getDocuments();
}
uploadUserInfo(userMap) {
Firestore.instance.collection('users').add(userMap);
}
createChatScreen(String chatScreenId, chatScreenMap) {
Firestore.instance.collection('ChatScreen').document(chatScreenId).setData(chatScreenMap).catchError((e){
print(e.toString());
});
}
addConversationMessages(String chatScreenId, messageMap) {
Firestore.instance.collection('ChatScreen').document(chatScreenId).collection('chats').add(messageMap)
.catchError((e){
print(e.toString());
});
}
getConversationMessages(String chatScreenId) async {
return Firestore.instance.collection('ChatScreen').document(chatScreenId).collection('chats')
.orderBy('time', descending: true).snapshots();
}
getHomeScreen(String userName) async {
return Firestore.instance.collection('ChatScreen').where('users', arrayContains: userName).snapshots();
}
}
I'm trying to fix it by myself but I can't, that's why I'm here.
When you create _currentDate, it is never updated. Consequently, it will always be the time at which the State object was created. You can either use Midhun MP's suggestion in the comments, or replacing DateTime _currentDate = new DateTime.now(); with DateTime get _currentDate => DateTime.now(); to always get a copy of the current date, in case you use it in multiple places.
Related
I'm trying to paginate by using Firestore and I read the document and it implement like this in Swift
let first = db.collection("cities")
.order(by: "population")
.limit(to: 25)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
// Construct a new query starting after this document,
// retrieving the next 25 cities.
let next = db.collection("cities")
.order(by: "population")
.start(afterDocument: lastSnapshot)
// Use the query for pagination.
// ...
}
Just for practice, I tried fetched three documents and if button tapped, fetch one more document.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').limit(3).getDocuments().then((snapshot) {
_lastDocument = snapshot.documents.last;
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
After button tapped tried like this.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
But console says this.
The following assertion was thrown while handling a gesture: type
'DocumentSnapshot' is not a subtype of type 'List[dynamic]'
Why do I have to pass list?
Does anyone know how to fix this?
UPDATE
I was able to paginate like so.
class PaginationExample extends StatefulWidget {
#override
_PaginationExampleState createState() => _PaginationExampleState();
}
class _PaginationExampleState extends State<PaginationExample> {
var _restaurants = <Restaurant>[];
var _nomore = false;
var _isFetching = false;
DocumentSnapshot _lastDocument;
ScrollController _controller;
void _fetchDocuments() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').limit(8).getDocuments();
// your logic here
}
Future<Null> _fetchFromLast() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').startAfter([_lastDocument['likes']]).limit(4).getDocuments();
if (querySnapshot.documents.length < 4) {
_nomore = true;
return;
}
_lastDocument = querySnapshot.documents.last;
for (final DocumentSnapshot snapshot in querySnapshot.documents) {
final Restaurant re = Restaurant(snapshot);
_restaurants.add(re);
}
setState(() {});
}
void _scrollListener() async {
if (_nomore) return;
if (_controller.position.pixels == _controller.position.maxScrollExtent && _isFetching == false) {
_isFetching = true;
await _fetchFromLast();
_isFetching = false;
}
}
#override
void initState() {
_fetchDocuments();
_controller = new ScrollController()..addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
);
}
}
There is an error here:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
startAfter method expects a List value params and you are passing a DocumentSnapshot.
Takes a list of [values], creates and returns a new [Query] that
starts after the provided fields relative to the order of the query.
You could try something like this:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter([{'name': 'Tom'}]).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
Paginate just with 2 attrubutes, itemBuilder and query using this package - paginate_firestore
For example,
PaginateFirestore(
itemBuilder: (context, documentSnapshot) => ListTile(
leading: CircleAvatar(child: Icon(Icons.person)),
title: Text(documentSnapshot.data['name']),
subtitle: Text(documentSnapshot.documentID),
),
// orderBy is compulsary to enable pagination
query: Firestore.instance.collection('users').orderBy('name'),
)
This works for me giving realtime pagination
defining functions to fetch data
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../../core/constants/firebase_constants.dart';
class FirebaseProvider {
final FirebaseFirestore _firestore;
FirebaseProvider({required FirebaseFirestore firestore})
: _firestore = firestore;
CollectionReference get _posts =>
_firestore.collection(FirebaseConstants.postsCollection);
Future<List<DocumentSnapshot>> fetchFirstList(
String fromgst, String postType) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.limit(5)
.get())
.docs;
}
Future<List<DocumentSnapshot>> fetchNextList(String fromgst, String postType,
List<DocumentSnapshot> documentList) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.startAfterDocument(documentList[documentList.length - 1])
.limit(5)
.get())
.docs;
}
}
separate class to handle pagination
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:growmore/features/home/repository/firebase_provider.dart';
import 'package:rxdart/rxdart.dart';
class PostListBloc {
List<DocumentSnapshot>? documentList;
bool showIndicator = false;
FirebaseProvider? firebaseProvider;
BehaviorSubject<List<DocumentSnapshot>>? postController;
BehaviorSubject<bool>? showIndicatorController;
PostListBloc() {
postController = BehaviorSubject<List<DocumentSnapshot>>();
showIndicatorController = BehaviorSubject<bool>();
firebaseProvider = FirebaseProvider(firestore: FirebaseFirestore.instance);
}
Stream get getShowIndicatorStream => showIndicatorController!.stream;
Stream<List<DocumentSnapshot>> get postStream => postController!.stream;
// This method will automatically fetch first 10 elements from the document list
Future fetchFirstList(String fromgst, String postType) async {
try {
documentList = await firebaseProvider?.fetchFirstList(fromgst, postType);
print("documentList$documentList");
postController?.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController?.sink.addError("No Data Available");
}
} catch (e) {
print(e);
}
} on SocketException {
postController?.sink.addError(SocketException("No Internet Connection"));
} catch (e) {
print(e.toString());
postController?.sink.addError(e);
}
}
//This will automatically fetch the next 10 elements from the list
fetchNextPosts(String fromgst, String postType) async {
try {
updateIndicator(true);
List<DocumentSnapshot> newDocumentList = await firebaseProvider!
.fetchNextList(fromgst, postType, documentList!);
print('asca$newDocumentList');
documentList!.addAll(newDocumentList);
postController!.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController!.sink.addError("No Data Available");
updateIndicator(false);
}
} catch (e) {
updateIndicator(false);
}
} on SocketException {
postController!.sink.addError(SocketException("No Internet Connection"));
updateIndicator(false);
} catch (e) {
updateIndicator(false);
print(e.toString());
postController!.sink.addError(e);
}
}
//For updating the indicator below every list and paginate*
updateIndicator(bool value) async {
showIndicator = value;
showIndicatorController!.sink.add(value);
}
void dispose() {
postController!.close();
showIndicatorController!.close();
}
}
the ui part
ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
postListBloc = PostListBloc();
print("dvvfe${widget.fromgst}");
postListBloc!.fetchFirstList(widget.fromgst, widget.postType);
controller.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<DocumentSnapshot>>(
stream: postListBloc!.postStream,
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data?.length,
shrinkWrap: true,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(snapshot.data![index]['description']),
),
),
);
},
);
} else {
return const CircularProgressIndicator();
}
},
),
);
}
void _scrollListener() {
if (controller.offset >= controller.position.maxScrollExtent &&
!controller.position.outOfRange) {
print("Cavc$controller");
print("at the end of list");
postListBloc!.fetchNextPosts(widget.fromgst, widget.postType);
}
}
}
I found it not open source github repo
I have a backend Laravel application that uses Pusher for notifications. I would like to show notifications in my Flutter app (both iOS and Android). I found that https://pub.dev/packages/pusher_websocket_flutter/ package has the best score, but I can't get it to work. I've followed this tutorial, and I get no errors (whatever I put for my APP_KEY, which must be wrong), but I never get anything shown.
Has anyone managed to get this working, or should I switch to firebase?
This is my pusher_service.dart:
import 'package:flutter/services.dart';
import 'package:pusher_websocket_flutter/pusher.dart';
import 'dart:async';
class PusherService {
Event lastEvent;
String lastConnectionState;
Channel channel;
StreamController<String> _eventData = StreamController<String>();
Sink get _inEventData => _eventData.sink;
Stream get eventStream => _eventData.stream;
Future<void> initPusher() async {
try {
await Pusher.init('XXX', PusherOptions(cluster: 'XX'), enableLogging: true);
print("Pusher initialized");
}
on PlatformException catch (e) {
print(e.message);
}
}
void connectPusher() {
Pusher.connect(
onConnectionStateChange: (ConnectionStateChange connectionState) async {
lastConnectionState = connectionState.currentState;
print("Pusher connected");
}, onError: (ConnectionError e) {
print("Error: ${e.message}");
});
}
Future<void> subscribePusher(String channelName) async {
channel = await Pusher.subscribe(channelName);
print("Pusher subscribed to channel");
}
void unSubscribePusher(String channelName) {
Pusher.unsubscribe(channelName);
}
void bindEvent(String eventName) {
channel.bind(eventName, (last) {
final String data = last.data;
_inEventData.add(data);
});
print("Pusher data binded");
}
void unbindEvent(String eventName) {
channel.unbind(eventName);
_eventData.close();
}
Future<void> firePusher(String channelName, String eventName) async {
await initPusher();
connectPusher();
await subscribePusher(channelName);
bindEvent(eventName);
}
}
My pusher_test.dart:
import 'package:flutter/material.dart';
import 'package:chalet/services/pusher_service.dart';
import 'package:pusher/pusher.dart';
import 'dart:async';
class PusherTest extends StatefulWidget {
#override
_PusherTestState createState() => _PusherTestState();
}
class _PusherTestState extends State<PusherTest> {
PusherService pusherService = PusherService();
#override
void initState() {
pusherService = PusherService();
pusherService.firePusher('public', 'create');
testPusher();
super.initState();
}
#override
void dispose() {
pusherService.unbindEvent('create');
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: StreamBuilder(
stream: pusherService.eventStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Container(
child: Text(snapshot.data),
);
},
),
),
);
}
}
I've checked and my snapshot.connectionState is always waiting.
Try this:
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:pusher_client/pusher_client.dart';
//instantiate Pusher Class
class PusherController {
static final PusherController _pusherController =
PusherController._internal();
factory PusherController() {
return _pusherController;
}
PusherController._internal();
PusherClient pusher;
Channel channel;
StreamController<String> _eventData = StreamController<String>.broadcast();
Sink get _inEventData => _eventData.sink;
Stream get eventStream => _eventData.stream;
String channelName = "";
String prevChannelName = "";
String eventName = "";
void initPusher() {
PusherOptions options = PusherOptions(
cluster: "eu",
);
pusher = new PusherClient("key", options,
autoConnect: true, enableLogging: true);
}
void setChannelName(String name) {
channelName = name;
print("channelName: ${channelName}");
}
void setEventName(String name) {
eventName = name;
print("eventName: ${eventName}");
}
void subscribePusher() {
channel = pusher.subscribe(channelName);
pusher.onConnectionStateChange((state) {
log("previousState: ${state.previousState}, currentState: ${state.currentState}");
});
pusher.onConnectionError((error) {
log("error: ${error.message}");
});
//Bind to listen for events called and sent to channel
channel.bind(eventName, (PusherEvent event) {
print("xxxxxxxxx From pusher xxxxxxxxx");
print('xxxxx This is Event name - $eventName xxxx');
print('xxxxx This is Event gotten - ${event.data} xxx');
_inEventData.add(event.data);
prevChannelName = eventName;
});
}
void connectPusher() {
pusher.connect();
}
void disconnectPusher() async {
await channel.unbind(eventName);
await pusher.disconnect();
}
}
Then use streamBuilder and stream from evenStream.
I have a function called getAllUsers() that returns all users from a database. The problem is that I want GridView.builder() to display all the users except the current user, but despite all the research I did, nothing seems to work out.
If i use the if condition like if(snapshot.data.documents[i].data["username"] != currentUserId within itemBuilder:, it returns a blank tile which represents the current user which creates a gap within the grid view. Thus, it makes the grid view look really bad.
I believe this problem could have been solved if I knew how to include the inequality query in the getAllUsers() method. But my understanding is that Firestore has yet to provide this function/argument.
HomeFragment class
Database _database = Database();
Stream _stream;
String currentUserId;
#override
void initState() {
getCurrentUserId();
getAllUsers();
super.initState();
}
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted)
setState(() => _stream = val);
});
}
getCurrentUserId() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
currentUserId = currentUser.uid;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
return snapshot.data == null ? Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, i) {
return Container(
child: Text(snapshot.data.documents[i].data["username"])
);
}
// etc etc..
Database class
getAllUsers() async {
return await _firestore.collection("users").snapshots();
}
I tried to use this, but _stream2 returns null
Stream _stream, _stream2;
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted) {
List<String> list;
setState(() {
_stream = val;
_stream2 = _stream.where((snapshot) {
_querySnapshot = snapshot;
for (int i = 0; i < _querySnapshot.documents.length; i++)
list.add(_querySnapshot.documents[i].data["userId"]);
return list.contains(currentUserId) == false;
});
});
}
});
}
I also tried this, it is not working
getAllUsers() async {
Stream<QuerySnapshot> snapshots = await _database.getAllUsers();
_stream = snapshots.map((snapshot) {
snapshot.documents.where((documentSnapshot) {
return documentSnapshot.data["userId"] != currentUserId;
});
});
}
Maybe you can try something like this. You filter the query result:
getAllUsers() async {
final Stream<QuerySnapshot> snapshots = await _firestore.collection("users").snapshots();
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => User.fromMap(snapshot.data)
.where((user) => user.id != currentUser.id)
.toList();
return result;
}
}
If you do not have an User class, you can replace some lines with this. But the result will be a list of Map<String, dynamic> instead of a list of User objects.
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => snapshot.data
.where((user) => user['id'] != currentUser.id)
.toList();
return result;
This solution worked well for me.
firestore.collection('your collection').where('x', isNotEqualTo: auth.currentUser!.uid).snapshots();
from this link on my web server as
http://instamaker.ir/api/v1/getPersons
i'm trying to get result and printing avatar from that result, unfortunately my implementation with rxDart and Bloc don't get result from this response and i don't get any error
server response this simplified result:
{
"active": 1,
"name": "my name",
"email": " 3 ",
"loginType": " 3 ",
"mobile_number": " 3 ",
...
"api_token": "1yK3PvAsBA6r",
"created_at": "2019-02-12 19:06:34",
"updated_at": "2019-02-12 19:06:34"
}
main.dart file: (click on button to get result from server)
StreamBuilder(
stream: bloc.login,
builder: (context,
AsyncSnapshot<UserInfo>
snapshot) {
if (snapshot.hasData) {
parseResponse(snapshot);
}
},
);
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.avatar);
}
LoginBlock class:
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async{
UserInfo userInfo = await _repository.userInfo();
_login_fetcher.sink.add(userInfo);
}
dispose(){
_login_fetcher.close();
}
}
final bloc = LoginBlock();
Repository class:
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin();
}
my model:
class UserInfo {
int _active;
String _name;
...
UserInfo.fromJsonMap(Map<String, dynamic> map)
: _active = map["active"],
_name = map["name"],
...
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['active'] = _active;
data['name'] = _name;
...
return data;
}
//GETTERS
}
BaseUrl class:
class BaseUrl {
static const url = 'http://instamaker.ir';
}
and then InstagramApiProviders class:
class InstagramApiProviders {
Client client = Client();
Future<UserInfo> checkUserLogin() async {
final response = await client.get(BaseUrl.url+'/api/v1/getPersons');
print("entered "+BaseUrl.url+'/api/v1/getPersons');
if (response.statusCode == 200) {
return UserInfo.fromJsonMap(json.decode(response.body));
} else
throw Exception('Failed to load');
}
}
Well the answer here is part of the test that I make to get this done. I can put my all test here but I think that the problem cause was because as StreamBuilder is a widget his builder method callback is only called when the widget is in flutter widget tree. As in your sample you're just creating a StreamBuilder the builder method will never be called bacause this widget isn't in widget tree.
As advice first test your code changing only UI layer... do somenthing like:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
Here we're putting the StreamBuilder in widget tree so the builder callback is called and maybe you will see the results. If it fails, please comment that I update my answer with my full test code with this working.
Updating the answer with sources that I made tests.
Basic model
class UserInfo {
int _active;
String name;
UserInfo.fromJsonMap(Map<String, dynamic> map) {
_active = map["active"];
name = map["name"];
}
Map<String, dynamic> toJson() => {
'active' : _active,
'name' : name,
};
}
The provider class
class InstagramApiProviders {
Future<UserInfo> checkUserLogin() async {
UserInfo info;
try {
http.Response resp = await http.get("http://instamaker.ir/api/v1/getPersons");
if (resp.statusCode == 200){
print('get response');
print( resp.body );
info = UserInfo.fromJsonMap( Map.from( json.decode(resp.body ) ));
}
}
catch (ex) {
throw ex;
}
print('returning $info');
return info;
}
}
Repository
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin().then((user) => user);
}
BLoC class
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async {
UserInfo info = await _repository.userInfo();
_login_fetcher.sink.add(info);
}
dispose(){
_login_fetcher.close();
}
}
Widget UI
This starts showing There is no data message but when you hit appBar button wait a little and then the data is fetched and updates the UI.
class WidgetToShowData extends StatelessWidget {
final LoginBlock bloc = LoginBlock();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
}
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.name);
}
}
I'm trying to paginate by using Firestore and I read the document and it implement like this in Swift
let first = db.collection("cities")
.order(by: "population")
.limit(to: 25)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retrieving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
// Construct a new query starting after this document,
// retrieving the next 25 cities.
let next = db.collection("cities")
.order(by: "population")
.start(afterDocument: lastSnapshot)
// Use the query for pagination.
// ...
}
Just for practice, I tried fetched three documents and if button tapped, fetch one more document.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').limit(3).getDocuments().then((snapshot) {
_lastDocument = snapshot.documents.last;
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
After button tapped tried like this.
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
But console says this.
The following assertion was thrown while handling a gesture: type
'DocumentSnapshot' is not a subtype of type 'List[dynamic]'
Why do I have to pass list?
Does anyone know how to fix this?
UPDATE
I was able to paginate like so.
class PaginationExample extends StatefulWidget {
#override
_PaginationExampleState createState() => _PaginationExampleState();
}
class _PaginationExampleState extends State<PaginationExample> {
var _restaurants = <Restaurant>[];
var _nomore = false;
var _isFetching = false;
DocumentSnapshot _lastDocument;
ScrollController _controller;
void _fetchDocuments() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').limit(8).getDocuments();
// your logic here
}
Future<Null> _fetchFromLast() async {
final QuerySnapshot querySnapshot = await Firestore.instance.collection('restaurants').orderBy('likes').startAfter([_lastDocument['likes']]).limit(4).getDocuments();
if (querySnapshot.documents.length < 4) {
_nomore = true;
return;
}
_lastDocument = querySnapshot.documents.last;
for (final DocumentSnapshot snapshot in querySnapshot.documents) {
final Restaurant re = Restaurant(snapshot);
_restaurants.add(re);
}
setState(() {});
}
void _scrollListener() async {
if (_nomore) return;
if (_controller.position.pixels == _controller.position.maxScrollExtent && _isFetching == false) {
_isFetching = true;
await _fetchFromLast();
_isFetching = false;
}
}
#override
void initState() {
_fetchDocuments();
_controller = new ScrollController()..addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
);
}
}
There is an error here:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter(_lastDocument).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
startAfter method expects a List value params and you are passing a DocumentSnapshot.
Takes a list of [values], creates and returns a new [Query] that
starts after the provided fields relative to the order of the query.
You could try something like this:
Firestore.instance.collection('user').where('name', isEqualTo: 'Tom').orderBy('age').startAfter([{'name': 'Tom'}]).limit(1).getDocuments().then((snapshot) {
snapshot.documents.forEach((snap) {
print(snap.data);
});
});
Paginate just with 2 attrubutes, itemBuilder and query using this package - paginate_firestore
For example,
PaginateFirestore(
itemBuilder: (context, documentSnapshot) => ListTile(
leading: CircleAvatar(child: Icon(Icons.person)),
title: Text(documentSnapshot.data['name']),
subtitle: Text(documentSnapshot.documentID),
),
// orderBy is compulsary to enable pagination
query: Firestore.instance.collection('users').orderBy('name'),
)
This works for me giving realtime pagination
defining functions to fetch data
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../../core/constants/firebase_constants.dart';
class FirebaseProvider {
final FirebaseFirestore _firestore;
FirebaseProvider({required FirebaseFirestore firestore})
: _firestore = firestore;
CollectionReference get _posts =>
_firestore.collection(FirebaseConstants.postsCollection);
Future<List<DocumentSnapshot>> fetchFirstList(
String fromgst, String postType) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.limit(5)
.get())
.docs;
}
Future<List<DocumentSnapshot>> fetchNextList(String fromgst, String postType,
List<DocumentSnapshot> documentList) async {
return (await _posts
.where("fromgst", isEqualTo: fromgst)
.where("postType", isEqualTo: postType)
.orderBy("date", descending: true)
.startAfterDocument(documentList[documentList.length - 1])
.limit(5)
.get())
.docs;
}
}
separate class to handle pagination
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:growmore/features/home/repository/firebase_provider.dart';
import 'package:rxdart/rxdart.dart';
class PostListBloc {
List<DocumentSnapshot>? documentList;
bool showIndicator = false;
FirebaseProvider? firebaseProvider;
BehaviorSubject<List<DocumentSnapshot>>? postController;
BehaviorSubject<bool>? showIndicatorController;
PostListBloc() {
postController = BehaviorSubject<List<DocumentSnapshot>>();
showIndicatorController = BehaviorSubject<bool>();
firebaseProvider = FirebaseProvider(firestore: FirebaseFirestore.instance);
}
Stream get getShowIndicatorStream => showIndicatorController!.stream;
Stream<List<DocumentSnapshot>> get postStream => postController!.stream;
// This method will automatically fetch first 10 elements from the document list
Future fetchFirstList(String fromgst, String postType) async {
try {
documentList = await firebaseProvider?.fetchFirstList(fromgst, postType);
print("documentList$documentList");
postController?.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController?.sink.addError("No Data Available");
}
} catch (e) {
print(e);
}
} on SocketException {
postController?.sink.addError(SocketException("No Internet Connection"));
} catch (e) {
print(e.toString());
postController?.sink.addError(e);
}
}
//This will automatically fetch the next 10 elements from the list
fetchNextPosts(String fromgst, String postType) async {
try {
updateIndicator(true);
List<DocumentSnapshot> newDocumentList = await firebaseProvider!
.fetchNextList(fromgst, postType, documentList!);
print('asca$newDocumentList');
documentList!.addAll(newDocumentList);
postController!.sink.add(documentList!);
try {
if (documentList!.isEmpty) {
postController!.sink.addError("No Data Available");
updateIndicator(false);
}
} catch (e) {
updateIndicator(false);
}
} on SocketException {
postController!.sink.addError(SocketException("No Internet Connection"));
updateIndicator(false);
} catch (e) {
updateIndicator(false);
print(e.toString());
postController!.sink.addError(e);
}
}
//For updating the indicator below every list and paginate*
updateIndicator(bool value) async {
showIndicator = value;
showIndicatorController!.sink.add(value);
}
void dispose() {
postController!.close();
showIndicatorController!.close();
}
}
the ui part
ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
postListBloc = PostListBloc();
print("dvvfe${widget.fromgst}");
postListBloc!.fetchFirstList(widget.fromgst, widget.postType);
controller.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<DocumentSnapshot>>(
stream: postListBloc!.postStream,
builder: (context, snapshot) {
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data?.length,
shrinkWrap: true,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(snapshot.data![index]['description']),
),
),
);
},
);
} else {
return const CircularProgressIndicator();
}
},
),
);
}
void _scrollListener() {
if (controller.offset >= controller.position.maxScrollExtent &&
!controller.position.outOfRange) {
print("Cavc$controller");
print("at the end of list");
postListBloc!.fetchNextPosts(widget.fromgst, widget.postType);
}
}
}
I found it not open source github repo