Related
I am trying to update an array in flutter. I found out that it is not possible directly.
I have understood that I must first create a list[], transfer my document fields value into the created list, then I must update the information in my list and only then can I can update my Firebase Firestore array with my updated list.
My code is below. Problem is, when I use my function the simulator crashes and I am getting the error lost connection.
I am looking for any advice. Many thanks.
List listTest =[];
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
class DetailScreen_CheckList_V3 extends StatefulWidget {
//final Map listName;
final docID;
const DetailScreen_CheckList_V3( this.docID,{
Key key}) : super(key: key);
#override
_DetailScreen_CheckList_V3State createState() => _DetailScreen_CheckList_V3State(docID);
}
class _DetailScreen_CheckList_V3State extends State<DetailScreen_CheckList_V3> {
// Map listName;
var docID;
_DetailScreen_CheckList_V3State( //this.listName,
this.docID);
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Your list items'),
leading:
InkWell(
child:
Icon(Icons.fast_rewind_outlined),
onTap: () {
Navigator.pop(context);
},),
),
body: MyBody(context, docID),
floatingActionButton: FloatingActionButton(
onPressed: () {
showAddNewItemToAList();
setState(() {});
},
child: const Icon(Icons.add),
backgroundColor: Colors.blue,
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
);
}
Widget MyBody(BuildContext context, var docID) {
return SingleChildScrollView(
child: Column(
children: [
Container(
height: MediaQuery
.of(context)
.size
.height / 1.4,
width: MediaQuery
.of(context)
.size
.width,
child: StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
DocumentSnapshot data = snapshot.requireData;
return ListView.builder(
itemCount: data['allItems'].length,
itemBuilder: (context, index) {
return Card(
child:
InkWell(
child: ListTile(
leading: data['allItems'][index]['itemChecked'] ==
'Yes' ? Icon(
Icons.check_box,
color: Colors.blue,) : Icon(
Icons.check_box_outline_blank),
title:
Text(
(data['allItems'][index]['itemName'])),
onTap: () {
String checked = data['allItems'][index]['itemChecked'];
String myItemName = data['allItems'][index]['itemName'];
String myListName = data['listName'];
listTest.addAll(data['allItems']);
print('before');
print (listTest);
setState(() {
if (checked == 'Yes') {
checked = 'No';
listTest[index]['itemChecked'] = checked;
print('after');
print(listTest);
myTest(myListName,index);
}
else {
checked = 'Yes';
listTest[index]['itemChecked'] = checked;
print('after');
print(listTest);
myTest(myListName,index);
}
});
}
),
onTap: () {
},)
);
});
}
}))
]),
);
}
void showAddNewItemToAList() {
TextEditingController _noteField = new TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return CustomAlertDialog(
content: Container(
width: MediaQuery
.of(context)
.size
.width / 1.3,
height: MediaQuery
.of(context)
.size
.height / 4,
child: Column(
children: [
TextField(
controller: _noteField,
maxLines: 4,
decoration: InputDecoration(
border: const OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.black, width: 1.0),
),
),
),
SizedBox(height: 10),
Material(
elevation: 5.0,
borderRadius: BorderRadius.circular(25.0),
color: Colors.white,
child: MaterialButton(
minWidth: MediaQuery
.of(context)
.size
.width / 1.5,
onPressed: () {
if (_noteField.text != '') {
setState(() {
AddObjectItemToArray(_noteField.text);
});
Navigator.of(context).pop();
}
else {
return;
}
},
padding: EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 15.0),
child: Text(
'Add Item',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
)
],
),
),
);
});
}
Future AddObjectItemToArray(newItemName,) async {
AllItems _allItems = AllItems(newItemName, 'No');
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID).update({
"allItems": FieldValue.arrayUnion([_allItems.toMap()])
},);
}
Future ModifyCheckedStatus(newItemName, newCheckedStatus,
currentListName) async {
AllItems _allItems = AllItems(newItemName, newCheckedStatus);
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID).update(
{'listName': currentListName,
"allItems": ([_allItems.toMap()]),
}, //SetOptions(merge: true),
);
}
Future myTest(currentListName,index) async {
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID).set(
{'listName': currentListName,
"allItems": [listTest],
},//SetOptions(merge: true)
);
}
}
So I guess your AddObjectToArray method is working well, you are correctly using arrayUnion. But then the ModifyCheckStatus is not working as expected because you are still using arrayUnion.
ArrayUnion adds an object to the array. What you have to do in ModifyCheckStatus is to extract all items, toggle the checked status of a particular item, then update the entire items list in Firebase (instead of using arrayUnion). This should only be in ModifyCheckStatus.
Something like the following
Future ModifyCheckedStatus(newItemName, newCheckedStatus,
currentListName) async {
// TODO: obtain all items
// changed the checked status of a particular item
allItems.where((i) => i.name == item.name).forEach((i) {
i.checked = !i.checked;
});
// update as you did
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID)
.update({"allItems": allItems});
}
I'm building a generalised flutter widget based on the flutter alertDialog, I want to have this as a separate widget which can be called with onPressed method in other widgets.
Currently the alertDialog opens with the onPressed method which is part of the current widget within ElevatedButton widget. I want to get rid of this ElevatedButton as the button to open alertDialog is part of other widget.
Class AppAlertDialog extends StatelessWidget {
const AppAlertDialog({
required this.buttonName,
required this.title,
required this.content,
required this.secondaryButtonName,
required this.primaryButtonName,
Key? key,
}) : super(key: key);
final String buttonName;
final String title;
final String content;
final String secondaryButtonName;
final String primaryButtonName;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => _showAlertDialog(context),
child: Text(buttonName),
);
//Get rid of this ElevatedButton and call the _showAlertDialog method to open the
//alertDialog from other onPressed methods in other files
}
_showAlertDialog(BuildContext context) {
final titleTextStyle = Theme.of(context).textTheme.headline5!;
const buttonPadding = EdgeInsets.all(20);
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(
title,
style: titleTextStyle,
),
content: Text(content),
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 24),
actions: <Widget>[
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
padding: buttonPadding,
primary: SharedColorsButton.secondaryButtonBgColor,
onPrimary: SharedColorsButton.secondaryButtonFgColor,
side: const BorderSide(
color: SharedColorsInputDecoration.borderColor,
),
),
child: Text(secondaryButtonName),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: buttonPadding,
),
onPressed: () => Navigator.pop(context),
child: Text(primaryButtonName),
),
],
actionsPadding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
),
);
}
}
Please see the example.
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 24),
title: Text("New request"),
content: Container(
height: double.maxFinite,
width: double.maxFinite,
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('requests')
.where('accepted', isEqualTo: 0)
.snapshots(),
builder: (context, snapshot) {
print("Second ride data ==> ${snapshot.hasData}");
if (snapshot.hasData) {
final requests = snapshot.data.docs;
if (User.homeModel == null) {
return Container(width: 0, height: 0);
}
if (User.homeModel.vehicleDetails == null) {
return Container(width: 0, height: 0);
}
List<RequestCard> allTrains = [];
for (var doc in requests) {
print(
"Second ride data ==> ID ${doc['request_id']}");
if (Home.removeRequests.contains(doc['request_id']))
continue;
//seats compare
int seatCapacity =
User.homeModel.vehicleDetails.passengerCapacity;
print('seatCapacity => $seatCapacity');
var seatRequest = doc['seats'];
print('seatRequest => $seatRequest');
//baby_seats compare
var babySeatsCapacity =
User.homeModel.vehicleDetails.children;
print('babySeatsCapacity => $babySeatsCapacity');
var babySeatsRequest = doc['baby_seats'];
print('babySeatsRequest => $babySeatsRequest');
//WheelChair compare
var hasWheelChair =
User.homeModel.vehicleDetails.wheelchair == '1'
? true
: false;
print('hasWheelChair => $hasWheelChair');
var needWheelChair = doc['wheelchair'];
print('needWheelChair => $needWheelChair');
//compare vehicles with requests
if (seatRequest <= seatCapacity &&
babySeatsRequest <= babySeatsCapacity &&
(needWheelChair == hasWheelChair ||
hasWheelChair == true)) {
print('Vehicle Condition Satisfy');
final _rideReq = RideRequest(
userName: doc['user_name'],
currentLocation: doc['pick_up_location'],
destination: doc['drop_off_location'],
fare: doc['bid_amount'],
desLatitude: doc['drop_off_lat'].toString(),
desLongitude: doc['drop_off_long'].toString(),
distance: doc['distance'],
image: doc['user_image'],
latitude: doc['pick_up_lat'].toString(),
longitude: doc['pick_up_long'].toString(),
phone: doc['phone'],
pickUpPoint: doc['pick_up_location'],
userId: doc['user_id'],
requestId: doc['request_id']);
final requestCard = RequestCard(
onAcceptFunction: onAcceptRequest,
onRejectFunction: onRejectRequest,
rideRequest: _rideReq,
);
allTrains.add(requestCard);
}
}
if (allTrains.length > 0) {
return ListView(
children: allTrains,
);
} else {
Future.delayed(Duration.zero)
.then((value) => Navigator.pop(context));
}
}
return Container(width: 0, height: 0);
}),
),
actions: <Widget>[
FlatButton(
onPressed: () {
HomeBottomNavigationBar._isNewRequestOpenDialog = false;
Navigator.pop(context, true);
},
child: Text("Close"),
),
FlatButton(
onPressed: () {
changeTabs(0);
HomeBottomNavigationBar._isNewRequestOpenDialog = false;
Navigator.pop(context, true);
},
child: Text("Go to Home"),
),
],
);
},
);
},
)
So here is what I want to achieve: I want that when I click on the suggestions of my SearchDelegate it closes the search and move me on the map to the position searched
return FutureBuilder(
future: chargerMonument(),
builder: (context, snaphot) {
if (snaphot.hasData) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: CustomSearchDelegate(
monuments, updatelocation));
}),
],
key: _scaffoldKey,
body: Map(),
}
updatelocation is the function I try to pass to the SearchDelegate to achieve what I want
My map looks like this :
#override
Widget build(BuildContext context)
{
FutureBuilder(
future: test(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = [
Flexible(
child: FlutterMap(
mapController: mapController,
children: [
LocationMarkerLayerWidget(
plugin: LocationMarkerPlugin(
centerCurrentLocationStream:
_centerCurrentLocationStreamController
.stream,
//_centerOnLocationUpdate
centerOnLocationUpdate:
_centerOnLocationUpdate),
)
],
options: MapOptions(
onPositionChanged:
(MapPosition position, bool hasGesture) {
if (hasGesture) {
setState(() => _centerOnLocationUpdate =
CenterOnLocationUpdate.never);
}
},
plugins: [
PopupMarkerPlugin(),
ZoomButtonsPlugin(),
LocationPlugin(),
TappablePolylineMapPlugin()
],
interactiveFlags:
InteractiveFlag.all & ~InteractiveFlag.rotate,
zoom: 18.0,
center: LatLng(0, 0),
onTap: (_) => _popupLayerController
.hidePopup(), // Hide popup when the map is tapped.
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
),
TappablePolylineLayerOptions(
// Will only render visible polylines, increasing performance
polylineCulling: true,
pointerDistanceTolerance: 20,
polylines: [
TaggedPolyline(
points: polylines,
isDotted: true,
color: Colors.blue[300],
strokeWidth: 4.0)
],
onTap: (TaggedPolyline polyline) async {
await showDialog(
context: context,
builder: (_) => new AlertDialog(
title: Text(destination),
content: Text("Vous etes à " +
distance.round().toString() +
"km de votre distination !"),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("D'accord !")),
TextButton(
onPressed: () {
setState(() {
polylines = [];
refresh = 360000;
});
Navigator.of(context).pop();
},
child:
Text("Effacer ce chemin"))
]));
},
onMiss: () {
print('No polyline was tapped');
}),
PopupMarkerLayerOptions(
markers: _markers,
popupSnap: PopupSnap.markerTop,
popupController: _popupLayerController,
popupBuilder: (BuildContext _, Marker marker) =>
ExamplePopup(
marker, marker.point, drawpolyline)),
ZoomButtonsPluginOption(
minZoom: 4,
maxZoom: 22,
mini: true,
padding: 10,
alignment: Alignment.bottomRight),
LocationOptions(onLocationUpdate: (LatLngData ld) {
}, onLocationRequested: (LatLngData ld) {
if (ld == null) {
return;
}
mapController.move(ld.location, 16.0);
},
/* onLocationUpdate: (LatLngData ld) {},
onLocationRequested: (LatLngData ld) {
if (ld == null || ld.location == null) {
return;
}
/* return;
}
mapController?.move(ld.location, 16.0);*/
mapController.onReady.then((result) {
print("I AM READY");
mapController.move(ld.location, 16);
});
*/
buttonBuilder: (BuildContext context,
ValueNotifier<LocationServiceStatus> status,
Function onPressed) {
return Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(
bottom: 14.0, right: 60.0),
child: Container(
height: 38,
width: 38,
child: FloatingActionButton(
backgroundColor: Colors.grey,
child: ValueListenableBuilder<
LocationServiceStatus>(
valueListenable: status,
builder: (BuildContext context,
LocationServiceStatus value,
Widget child) {
switch (value) {
case LocationServiceStatus
.disabled:
case LocationServiceStatus
.permissionDenied:
case LocationServiceStatus
.unsubscribed:
return const Icon(
Icons.location_disabled,
color: Colors.white,
);
break;
default:
return const Icon(
Icons.my_location,
color: Colors.white,
);
break;
}
}),
onPressed: () {
setState(() =>
_centerOnLocationUpdate =
CenterOnLocationUpdate
.always);
_centerCurrentLocationStreamController
.add(18);
}),
)));
})
],
),
),
];
} else if (snapshot.hasError) {
children = <Widget>[
Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
});
});
}
and the method I want updatelocation to do is something like this :
void update(double lat,double long){
setState(() {
_centerOnLocationUpdate = CenterOnLocationUpdate.never;
});
mapController.move(LatLng(lat,long), 18);
}
how can I from the parent change the child value _centerOnLocationUpdate and
call on mapController move
I tried to use a ValueListenableBuilder but never found out how to make it works, thank you for your time.
I finally found the solution I just had to declare the function in the Map state, and use a global key to access it from the parent widget.
After migrating to null safety I'm getting an error on ListView as "The argument type 'Object?' can't be assigned to the parameter type 'List'."
I'm getting error on return ListView(children: snapshot.data,);
Can anyone help me to fix this error and build a ListView for activityfeeditem in my app?
Here is my code for activity_feed.dart,
class ActivityFeed extends StatefulWidget {
#override
_ActivityFeedState createState() => _ActivityFeedState();
}
class _ActivityFeedState extends State<ActivityFeed> {
getActivityFeed() async {
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: FutureBuilder(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
// Here I'm getting error on `snapshot.data`
);
},
),
),
);
}
}
Widget? mediaPreview;
String? activityItemText;
class ActivityFeedItem extends StatelessWidget {
final String? username;
final String? userId;
final String? type; // 'like', 'follow', 'comment'
final String? mediaUrl;
final String? postId;
final String? userProfileImg;
final String? commentData;
final Timestamp? timestamp;
ActivityFeedItem({
this.username,
this.userId,
this.type,
this.mediaUrl,
this.postId,
this.userProfileImg,
this.commentData,
this.timestamp,
});
factory ActivityFeedItem.fromDocument(DocumentSnapshot doc) {
return ActivityFeedItem(
username: doc['username'],
userId: doc['userId'],
type: doc['type'],
postId: doc['postId'],
userProfileImg: doc['userProfileImg'],
commentData: doc['commentData'],
timestamp: doc['timestamp'],
mediaUrl: doc['mediaUrl'],
);
}
showPost(context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostScreen(postId: postId, userId: userId)));
}
configureMediaPreview(context) {
if (type == "like" || type == 'comment') {
mediaPreview = GestureDetector(
onTap: () => showPost(context),
child: Container(
height: 50.0,
width: 50.0,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(mediaUrl!),
),
),
)),
),
);
} else {
mediaPreview = Text('');
}
if (type == 'like') {
activityItemText = "liked your post";
} else if (type == 'follow') {
activityItemText = "is following you";
} else if (type == 'comment') {
activityItemText = 'replied: $commentData';
} else {
activityItemText = "Error: Unknown type '$type'";
}
}
#override
Widget build(BuildContext context) {
configureMediaPreview(context);
return Padding(
padding: EdgeInsets.only(bottom: 2.0),
child: Container(
color: Colors.white54,
child: ListTile(
title: GestureDetector(
onTap: () => showProfile(context, profileId: userId),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: username,
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(userProfileImg!),
),
subtitle: Text(
timeago.format(timestamp!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
),
);
}
}
showProfile(BuildContext context, {String? profileId}) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
profileId: profileId,
),
),
);
}
I have tried many ways but I counldn't figure out how I can fix this
New code for list
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: StreamBuilder<QuerySnapshot>(
stream: activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: circularProgress(),
);
} else
return ListView(
children: snapshot.data!.docs.map((doc) {
return Card(
child: ListTile(
title: GestureDetector(
onTap: () =>
showProfile(context, profileId: doc['userId']),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: doc['username'],
style: TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
doc['userProfileImg']!),
),
subtitle: Text(
timeago.format(doc['timestamp']!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
);
}).toList(),
);
}),
));
}
Chage your getActivityFeed
Future<List<ActivityFeedItem>> getActivityFeed() async {
try{
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
catch (error) {
print(error);
return <ActivityFeedItem>[];
}}
change you FutureBuilder as follows
FutureBuilder<List<ActivityFeedItem>>(
future: getActivityFeed(),
builder: (BuildContextcontext, AsyncSnapshot<List<ActivityFeedItem>> snapshot) {
if (snapshot.hasError){
return Center(child: Text("You have an error in loading
data"));
}
if (snapshot.hasData) {
return ListView(
children: snapshot.data!,
);
}
return CirclularProgressIndicator();
You can also use as.
ListView(
children: object as List<Widget>,
)
I see that you are using a StreamBuilder instead of FutureBuilder, but for what its worth, I believe I have found a solution to the original FutureBuilder problem.
First of all: Using the following print statements, you can troubleshoot the issue better, I found that I was Querying for paths that didnt exist with combinations of wrong .doc(userId) and .doc(ownerId) in posts.dart so the deserialization process wasn't working correctly for me when switching between users during debugging (used a provider package to remedy this eventually) but the below print statements did help me identify some issues that I had (that may or may not have contributed to the problem for you, but worth the look).
getActivityFeed() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('feed')
.doc(_auth.currentUser!.uid)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.id}');
print('Activity Feed Item: ${doc.data()}');
});
// return feedItems;
return snapshot.docs;
Then I found that the deserialization process wasn't working correctly due to the difference between 'likes' and 'comments', due to the 'likes' having 7 inputs and 'comments' having 8 inputs. Comments have the extra 'commentData' input which I set up manually for the 'likes' in the addLikeToActivityFeed() part, and set it to an empty string as such:
'commentData': '',
Finally, I added a Dynamic type to the FutureBuilder to get rid of the => argument type 'Object?' can't be assigned to the parameter type 'List' error...
Container(
child: FutureBuilder<dynamic>(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
);
},
),
),
I´m using flutter with a calendar carousel (https://pub.dev/packages/flutter_calendar_carousel)
For each day for which there is an entry in the database, I want to display an icon in the calendar. What is the best way to do this?
That´s my current code:
Please check the part with the customDayBuilder
class _CalendarScreenState extends State<CalendarScreen> {
DateTime _currentDate;
openNewEntryDialog(BuildContext context, date) {
setState(() {
_currentDate = date;
});
showBarModalBottomSheet(
context: context,
builder: (BuildContext context, scrollController) {
return AddCalendarEntry(
scrollController: scrollController,
currentDate: _currentDate,
);
});
}
#override
Widget build(BuildContext context) {
final calendarEntriesData = Provider.of<CalendarEntries>(context);
void initState() {
_currentDate = widget._currentDate;
super.initState();
}
dayPressed(date, events) {
this.setState(() => _currentDate = date);
}
return Material(
child: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
trailing: IconButton(
icon: Icon(Icons.add),
color: Colors.white,
onPressed: () => openNewEntryDialog(context, DateTime.now())),
middle: Text("Juni 2020",
style: Theme.of(context).appBarTheme.textTheme.headline1),
backgroundColor: Theme.of(context).primaryColor,
),
child: Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15.0),
child: Column(
children: <Widget>[
Expanded(
child: CalendarCarousel(
markedDateIconBorderColor: Theme.of(context).primaryColor,
weekdayTextStyle:
TextStyle(color: Theme.of(context).primaryColor),
daysTextStyle:
TextStyle(color: Theme.of(context).primaryColor),
todayButtonColor: Theme.of(context).primaryColor,
weekendTextStyle: TextStyle(color: Colors.black),
locale: "de",
selectedDayButtonColor: Colors.grey.shade100,
selectedDateTime: _currentDate,
headerTextStyle: TextStyle(
color: Theme.of(context).primaryColor, fontSize: 25),
onDayPressed: (DateTime date, List<Event> events) =>
dayPressed(date, events),
onDayLongPressed: (DateTime date) =>
openNewEntryDialog(context, date),
customDayBuilder: (bool isSelectable,
int index,
bool isSelectedDay,
bool isToday,
bool isPrevMonthDay,
TextStyle textStyle,
bool isNextMonthDay,
bool isThisMonthDay,
DateTime day) {
return FutureBuilder(
future: calendarEntriesData.getAll(),
builder: (BuildContext context,
AsyncSnapshot<List<CalendarEntry>> snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
for (final entry in snapshot.data) {
var temp =
DateTime.parse(entry.dateTime).toUtc();
var d1 = DateTime.utc(
temp.year, temp.month, temp.day);
var d2 = DateTime.utc(
day.year, day.month, day.day);
if (d2.compareTo(d1) == 0) {
return Center(
child: Icon(Icons.local_airport));
}
}
}
});
},
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(top: 35),
child: FutureBuilder<List<CalendarEntry>>(
future: calendarEntriesData
.getCurrentMonthEntries(_currentDate != null
? _currentDate
: DateTime.now()),
builder: (BuildContext context,
AsyncSnapshot<List<CalendarEntry>> snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
height: 100,
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context,
int index) {
return ListTile(
title: Text(snapshot
.data[index].servicePartner
.toString()),
subtitle: snapshot.data[index]
.dateTime ==
null
? Text("Unbekannt")
: Text(DateFormat(
"dd.MM.yyyy")
.format(DateTime.parse(
snapshot.data[index]
.dateTime))),
trailing: Text((snapshot
.data[index]
.minutes /
60)
.toString() +
" Stunden"),
);
}));
}
})))
],
),
)));
}
}
How you can see, I´m using a FutureBuilder to check all database entries. And if a day matches, I show an Icon on this day. This works in general, but
I have some errors on the screen
The performance is very bad, because there is some flickering..for each click on another day the widget renders completely. I don´t want this.
How could I improve my code? How could I do this better?
Thanks so much for your help!
Please use the button and button style for this to generate a clickable.
Also resolved your issue.
Widget renderDay(
bool isSelectable,
int index,
bool isSelectedDay,
//bool isToday,
bool isPrevMonthDay,
TextStyle? textStyle,
TextStyle defaultTextStyle,
bool isNextMonthDay,
bool isThisMonthDay,
DateTime now,
) {
final EventList<T>? markedDatesMap = widget.markedDatesMap;
List<Event> markedEvents =
widget.markedDatesMap!.getEvents(now) as List<Event>? ?? [];
return Container(
child: ElevatedButtonTheme(
data: ElevatedButtonThemeData(
style: ButtonStyle(
side: MaterialStateProperty.resolveWith<BorderSide>((states) =>
BorderSide(
color: ColorConstants.WHITE)),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(states) => markedEvents.length > 0 &&
!isPrevMonthDay &&
!isNextMonthDay
? _getStatusColor(
markedEvents[0].dayStatus!.toLowerCase())
: isSelectedDay && widget.selectedDayButtonColor != null
? widget.selectedDayButtonColor
: widget.dayButtonColor,
),
shape: MaterialStateProperty.resolveWith<OutlinedBorder>((_) {
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(80));
}),
textStyle: MaterialStateProperty.resolveWith<TextStyle>(
(states) =>
TextStyle(color: ColorConstants.BUTTON_BG_COLOR)),
padding: MaterialStateProperty.all(
EdgeInsets.all(widget.dayPadding),
),
),
),
child: ElevatedButton(
onPressed:
widget.disableDayPressed ? null : () => _onDayPressed(now),
child: Stack(
children: <Widget>[
getDayContainer(
isSelectable,
index,
isSelectedDay,
// isToday,
isPrevMonthDay,
textStyle,
defaultTextStyle,
isNextMonthDay,
isThisMonthDay,
now),
],
),
),
),
);
}