class CalendarEvents extends StatefulWidget {
#override
CalendarEventsState createState() => CalendarEventsState();
}
class CalendarEventsState extends State<CalendarEvents> {
final GoogleSignIn _googleSignIn = GoogleSignIn(
clientId:
'571891879876-rcjtmo4lds0qlhvi9ar5sk78gubnub7f.apps.googleusercontent.com',
scopes: <String>[
googleAPI.CalendarApi.calendarScope,
],
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text('Event Calendar'),
),
body: Container(
child: FutureBuilder(
future: getGoogleEventsData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
child: Stack(
children: [
Container(
child: SfCalendar(
view: CalendarView.month,
initialDisplayDate: DateTime(2020,7,15,9,0,0),
dataSource: GoogleDataSource(events: snapshot.data),
monthViewSettings: MonthViewSettings(
appointmentDisplayMode:
MonthAppointmentDisplayMode.appointment),
),
),
snapshot.data != null
? Container()
: Center(
child: CircularProgressIndicator(),
)
],
));
},
),
),
);
}
#override
void dispose(){
if(_googleSignIn.currentUser != null) {
_googleSignIn.disconnect();
_googleSignIn.signOut();
}
super.dispose();
}
Future<List<googleAPI.Event>> getGoogleEventsData() async {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final GoogleAPIClient httpClient =
GoogleAPIClient(await googleUser!.authHeaders);
final googleAPI.CalendarApi calendarAPI = googleAPI.CalendarApi(httpClient);
final googleAPI.Events calEvents = await calendarAPI.events.list(
"primary",
);
final List<googleAPI.Event> appointments = <googleAPI.Event>[];
if (calEvents.items != null) {
for (int i = 0; i < calEvents.items!.length; i++) {
final googleAPI.Event event = calEvents.items![i];
if (event.start == null) {
continue;
}
appointments.add(event);
}
}
return appointments;
}
}
class GoogleDataSource extends CalendarDataSource {
GoogleDataSource({required List<googleAPI.Event>? events}) {
this.appointments = events;
}
#override
DateTime getStartTime(int index) {
final googleAPI.Event event = appointments![index];
return event.start!.date ?? event.start!.dateTime!.toLocal();
}
#override
bool isAllDay(int index) {
return appointments![index].start.date != null;
}
#override
DateTime getEndTime(int index) {
final googleAPI.Event event = appointments![index];
return event.endTimeUnspecified != null && event.endTimeUnspecified!
? (event.start!.date ?? event.start!.dateTime!.toLocal())
: (event.end!.date != null
? event.end!.date!.add(Duration(days: -1))
: event.end!.dateTime!.toLocal());
}
#override
String getLocation(int index) {
return appointments![index].location;
}
#override
String getNotes(int index) {
return appointments![index].description;
}
#override
String getSubject(int index) {
final googleAPI.Event event = appointments![index];
return event.summary!;
}
}
class GoogleAPIClient extends IOClient {
Map<String, String> _headers;
GoogleAPIClient(this._headers) : super();
#override
Future<IOStreamedResponse> send(BaseRequest request) =>
super.send(request..headers.addAll(_headers));
#override
Future<Response> head(Object url, { Map<String, String>? headers}) {
var uri = Uri.parse("$url");
super.head(uri, headers: headers!..addAll(_headers));
throw '';
}
}
I want to retrieve all the google calendar events that are happening in google id. I want to display all the events in the syncfusion calendar with the help of firebase and google calendar api. I found this code on syncfusion blog. But it isn't working and only circularprogressindicator shows upon the screen after google login. Can someone check the code?
Related
This piece of code works fine for just one document ID which is passed statically.
class _ImagesHomeTileState extends State<ImagesHomeTile> {
List<dynamic> pointList = [];
bool kuchhai = false;
getdata() async {
await FirebaseFirestore.instance
.collection("Past Papers")
.doc('XZ15GgU1vuXRDUauOb5w')
.get()
.then((value) {
setState(() {
value.data() == null ? kuchhai = false : kuchhai = true;
value.data() == null
? kuchhai = false
: pointList = value.data()!['ImagesLinks'];
});
});
}
#override
void initState() {
getdata();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: kuchhai
? GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 4.0,
mainAxisSpacing: 8.0,
children: [Text(pointList.toString())])
: const Center(
child: CircularProgressIndicator(),
));
}
}
This returns the actual values in the array but the following code returns nothing why?
class ImagesHomeTile extends StatefulWidget {
final String docId;
ImagesHomeTile({required this.docId});
#override
State<ImagesHomeTile> createState() => _ImagesHomeTileState();
}
class _ImagesHomeTileState extends State<ImagesHomeTile> {
List<dynamic> pointList = [];
bool kuchhai = false;
getdata() async {
await FirebaseFirestore.instance
.collection("Past Papers")
.doc(widget.docId)
.get()
.then((value) {
setState(() {
value.data() == null ? kuchhai = false : kuchhai = true;
value.data() == null
? kuchhai = false
: pointList = value.data()!['ImagesLinks'];
});
});
}
#override
void initState() {
getdata();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: kuchhai
? GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 4.0,
mainAxisSpacing: 8.0,
children: [Text(pointList.toString())])
: const Center(
child: CircularProgressIndicator(),
));
}
}
Getting values from Firestore gives not in dynamic values. What could be the possible error?
My problem is that when I receive information from Firestore, I see it in the console that it prints but my UI does not update. But until I press the icon that shows my screen again. The screen where my list of widgets is contained in a BottomNavigationBar.
What I hope will happen with the code is that when I select the tab that will contain my screen in the BottomNavigationBar, the list of Widgets appears with the names of the DocumentIDs. Well, currently I must select the tab again so that they appear.
I attach my code.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument();
}
Future getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget),
)
);
}
}
An easy fix : use then to call a callback function and inside callback function use setState to update the UI.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument().then(()=>updateUI()).catchError((error)=>print(error));
}
Future<void> getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
void updateUI()
{
setState((){});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget.isEmpty?[Text("Waiting...")]:myListWidget),
)
);
}
}
You can fix this by calling setState(() {}) method; setState
Notify the framework that the internal state of this object has changed.
Future getallDocument() async {
final QuerySnapshot result =
await Firestore.instance.collection("Users").getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++) {
myListWidget.add(Text("${myData[i]}"));
}
setState(() {});
}
I have this Flutter bloc that takes a Firebase stream of restaurants and depending on the position relative to the user will filter only the closest ones depending on the restaurant location. It works fine but I have to refresh with a RefreshIndicator if I want to see any changes in restaurant documents. What am I missing? Thanks in advance.
class NearestRestaurant {
final String id;
final Restaurant restaurant;
final double distance;
NearestRestaurant({this.id, this.restaurant, this.distance});
}
class NearRestaurantBloc {
final Future<List<Restaurant>> source;
final Position userCoordinates;
final _stream = StreamController<List<Restaurant>>();
NearRestaurantBloc({
this.source,
this.userCoordinates,
}) {
List<Restaurant> resList = List<Restaurant>();
source.then((rest) {
rest.forEach((res) async {
await Geolocator().distanceBetween(
userCoordinates.latitude,
userCoordinates.longitude,
res.coordinates.latitude,
res.coordinates.longitude,
).then((distance) {
if (res.active && distance < res.deliveryRadius) {
resList.add(res);
}
});
_stream.add(resList);
});
});
}
Stream<List<Restaurant>> get stream => _stream.stream;
void dispose() {
_stream.close();
}
}
class RestaurantQuery extends StatefulWidget {
#override
_RestaurantQueryState createState() => _RestaurantQueryState();
}
class _RestaurantQueryState extends State<RestaurantQuery> {
NearRestaurantBloc bloc;
#override
Widget build(BuildContext context) {
final database = Provider.of<Database>(context, listen: true);
final session = Provider.of<Session>(context);
final userCoordinates = session.position;
bloc = NearRestaurantBloc(
source: database.patronRestaurants(),
userCoordinates: userCoordinates,
);
return StreamBuilder<List<Restaurant>>(
stream: bloc.stream,
builder: (context, snapshot) {
bool stillLoading = true;
var restaurantList = List<Restaurant>();
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData && snapshot.data.length > 0) {
restaurantList = snapshot.data;
}
stillLoading = false;
}
return Scaffold(
appBar: AppBar(
title: Text(
'Restaurants near you',
style: TextStyle(color: Theme.of(context).appBarTheme.color),
),
elevation: 2.0,
),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: RefreshIndicator(
onRefresh: () async {
setState(() {
});
},
child: RestaurantList(
nearbyRestaurantsList: restaurantList,
stillLoading: stillLoading,
),
),
);
},
);
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
}
In the build method under _RestaurantQueryState, you are returning the scaffold outside the builder method. Initially, restaurantList is null. Therefore, you don't produce the list. Whenever the stream updates, you get the snapshot data to update the restaurantList.
The problem occurs here. Even though the restaurantList is updated, the widget RestaurantList is not updated because it is outside the builder method. You can use the following code. Here we create a Widget that holds the RestaurantList widget. The widget gets updated whenever the stream updates.
class _RestaurantQueryState extends State<RestaurantQuery> {
NearRestaurantBloc bloc;
#override
Widget build(BuildContext context) {
final database = Provider.of<Database>(context, listen: true);
final session = Provider.of<Session>(context);
final userCoordinates = session.position;
//////////////////////////////////
//initialize RestaurantList widget
//////////////////////////////////
Widget restaurantWidget = RestaurantList(
nearbyRestaurantsList: [],
stillLoading: false,
);
bloc = NearRestaurantBloc(
source: database.patronRestaurants(),
userCoordinates: userCoordinates,
);
return StreamBuilder<List<Restaurant>>(
stream: bloc.stream,
builder: (context, snapshot) {
bool stillLoading = true;
var restaurantList = List<Restaurant>();
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData && snapshot.data.length > 0) {
restaurantList = snapshot.data;
/////////////////////////////
//update the restaurant widget
//////////////////////////////
restaurantWidget = RestaurantList(
nearbyRestaurantsList: restaurantList,
stillLoading: stillLoading,
);
}
stillLoading = false;
}
return Scaffold(
appBar: AppBar(
title: Text(
'Restaurants near you',
style: TextStyle(color: Theme.of(context).appBarTheme.color),
),
elevation: 2.0,
),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
///////////////////////////
//use the restaurant Widget
///////////////////////////
body: restaurantWidget,
),
);
},
);
}
My mistake. I was listening to a future instead of a stream. Here's the updated bloc code:
class NearestRestaurant {
final String id;
final Restaurant restaurant;
final double distance;
NearestRestaurant({this.id, this.restaurant, this.distance});
}
class NearRestaurantBloc {
final Stream<List<Restaurant>> source;
final Position userCoordinates;
final _stream = StreamController<List<Restaurant>>();
NearRestaurantBloc({
this.source,
this.userCoordinates,
}) {
List<Restaurant> resList = List<Restaurant>();
source.forEach((rest) {
resList.clear();
rest.forEach((res) async {
await Geolocator().distanceBetween(
userCoordinates.latitude,
userCoordinates.longitude,
res.coordinates.latitude,
res.coordinates.longitude,
).then((distance) {
if (res.active && distance < res.deliveryRadius) {
resList.add(res);
}
});
_stream.add(resList);
});
});
}
Stream<List<Restaurant>> get stream => _stream.stream;
}
I am attempting to combine two streams using RxDart and display the stream within a ListView.
I modeled this technique based on this post How do I join data from two Firestore collections in Flutter?, however, when I run this code, I am returning an empty dataset.
Any help would be greatly appreciated!
import 'package:rxdart/rxdart.dart';
class _HomeScreenState extends State<HomeScreen> {
Stream<List<CombineStream>> _combineStream;
String currentUserId = 'id12345'
#override
void initState() {
super.initState();
_combineStream =
usersChatsRef.child(currentUserId).onValue.map((userChatStream) {
return (userChatStream.snapshot.value).map((f) {
Stream<UsersChats> uDetail =
f.map<UsersChats>((node) => UsersChats.fromMap(node));
Stream<Chat> sDetail = chatsDetailsRef
.child(f.key)
.onValue
.map<Chat>((node2) => Chat.fromMap(node2.snapshot.value));
return Rx.combineLatest2(
uDetail, sDetail, (u, s) => CombineStream(u, s));
});
}).switchMap((streams) {
return (streams.length) > 0
? streams.combineLatestList(streams)
: streams.just([]);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: StreamBuilder(
stream: _combineStream,
builder:
(BuildContext context, AsyncSnapshot<List<CombineStream>> snap) {
if (snap.hasData && !snap.hasError) {
return ListView.builder(
itemCount: snap.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snap.data[index].combineUserChats.id),
subtitle: Text(myChats[index].combineUserChats.id),
);
},
);
} else {
return Center(child: Text("No data"));
}
},
),
);
}
}
class CombineStream {
final UsersChats combineUserChats;
final Chat combineChat;
CombineStream(this.combineUserChats, this.combineChat);
}
class Chat {
String id;
String name;
Chat({String id, String name}) {
this.id = id;
this.name = name;
}
factory Chat.fromMap(Map data) {
return Chat(
name: data['name'] ?? '');
}
}
class UsersChats {
String id;
String lastUpdate;
UsersChats(
{this.id,
this.lastUpdate});
factory UsersChats.fromMap(MapEntry<dynamic, dynamic> data) {
return UsersChats(
id: data.key ?? '',
lastUpdate: data.value['lastUpdate'] ?? '');
}
}
I am trying to paginate in Flutter with firebase realtime databse.
I have tried this in Firestore and it works fine there but I want this with realtime database.
I am fetching data for the first time like this.
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: _firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.limitToFirst(15)
.onValue,
builder: (context, AsyncSnapshot<Event> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor)));
} else {
if (snapshot.data.snapshot.value != null) {
listMessage = Map.from(snapshot.data.snapshot.value)
.values
.toList()
..sort(
(a, b) => a['timestamp'].compareTo(b['timestamp']));
if (lastVisible == null) {
lastVisible = listMessage.last;
listMessage.removeLast();
}
}
return ListView.builder(
...
);
}
},
),
);
}
After that to paginate I am using a listener with ScrollController
void _scrollListener() async {
if (listScrollController.position.pixels ==
listScrollController.position.maxScrollExtent) {
_fetchMore();
}
}
and finally
_fetchMore() {
_firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.startAt(lastVisible['timestamp'])
.limitToFirst(5)
.once()
.then((snapshot) {
List snapList = Map.from(snapshot.value).values.toList()
..sort((a, b) => a['timestamp'].compareTo(b['timestamp']));
if (snapList.isNotEmpty) {
print(snapList.length.toString());
if (!noMore) {
listMessage.removeLast();
//Problem is here.....??
setState(() {
listMessage..addAll(snapList);
});
lastVisible = snapList.last;
print(lastVisible['content']);
}
if (snapList.length < 5) {
noMore = true;
}
}
});
}
Its working fine as realtime communication but when I try to paginate in _fetchMore() setState is called but it refreshes the state of whole widget and restarts the StreamBuilder again and all data is replaced by only new query. How can I prevent this??
Calling setState will redraw your whole widget and your list view. Now, since you supplying the steam that provides the first page, after redraw it just loads it. To avoid that you could use your own stream and supply new content to it. Then your StreamBuilder will handle the update automatically.
You need to store the full list of your items as a separate variable, update it and then sink to your stream.
final _list = List<Event>();
final _listController = StreamController<List<Event>>.broadcast();
Stream<List<Event>> get listStream => _listController.stream;
#override
void initState() {
super.initState();
// Here you need to load your first page and then add to your stream
...
_list.addAll(firstPageItems);
_listController.sink.add(_list);
}
#override
void dispose() {
super.dispose();
}
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: listStream
...
}
_fetchMore() {
...
// Do your fetch and then just add items to the stream
_list.addAll(snapList);
_listController.sink.add(_list);
...
}
Try this one
pagination for RealTime list
class FireStoreRepository {
final CollectionReference _chatCollectionReference =
Firestore.instance.collection('Chat');
final StreamController<List<ChatModel>> _chatController =
StreamController<List<ChatModel>>.broadcast();
List<List<ChatModel>> _allPagedResults = List<List<ChatModel>>();
static const int chatLimit = 10;
DocumentSnapshot _lastDocument;
bool _hasMoreData = true;
Stream listenToChatsRealTime() {
_requestChats();
return _chatController.stream;
}
void _requestChats() {
var pagechatQuery = _chatCollectionReference
.orderBy('timestamp', descending: true)
.limit(chatLimit);
if (_lastDocument != null) {
pagechatQuery =
pagechatQuery.startAfterDocument(_lastDocument);
}
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot) {
if (snapshot.documents.isNotEmpty) {
var generalChats = snapshot.documents
.map((snapshot) => ChatModel.fromMap(snapshot.data))
.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists) {
_allPagedResults[currentRequestIndex] = generalChats;
} else {
_allPagedResults.add(generalChats);
}
var allChats = _allPagedResults.fold<List<ChatModel>>(
List<ChatModel>(),
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1) {
_lastDocument = snapshot.documents.last;
}
_hasMoreData = generalChats.length == chatLimit;
}
},
);
}
void requestMoreData() => _requestChats();
}
ChatListView
class ChatView extends StatefulWidget {
ChatView({Key key}) : super(key: key);
#override
_ChatViewState createState() => _ChatViewState();
}
class _ChatViewState extends State<ChatView> {
FireStoreRepository _fireStoreRepository;
final ScrollController _listScrollController = new ScrollController();
#override
void initState() {
super.initState();
_fireStoreRepository = FireStoreRepository();
_listScrollController.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Flexible(
child: StreamBuilder<List<ChatModel>>(
stream: _fireStoreRepository.listenToChatsRealTime(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
controller: _listScrollController,
shrinkWrap: true,
reverse: true,
itemBuilder: (context, index) {
...
}
);
}
)
),
);
}
void _scrollListener() {
if (_listScrollController.offset >=
_listScrollController.position.maxScrollExtent &&
!_listScrollController.position.outOfRange) {
_fireStoreRepository.requestMoreData();
}
}
}
ChatModel Class
class ChatModel {
final String userID;
final String message;
final DateTime timeStamp;
ChatModel({this.userID, this.message, this.timeStamp});
//send
Map<String, dynamic> toMap() {
return {
'userid': userID,
'message': message,
'timestamp': timeStamp,
};
}
//fetch
static ChatModel fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return ChatModel(
userID: map['userid'],
message: map['message'],
timeStamp: DateTime.fromMicrosecondsSinceEpoch(map['timestamp'] * 1000),
);
}
}