Changing the displayed data based on a user selected Date in Swift? - swift

I am new to Swift and am building a nutrient tracking application for a project. I need the users foods to be saved to the day that they add them to their food diary section which displays the foods added to each meal.
So the user adds an apple for example, and this would be saved to today, and once the day passes, the displayed data is stored to the date.
I currently have the food items saving with a Date using CoreData and this is displayed in a list:
func saveBreakfast() {
let newBreakfastItem = BreakfastItem(context: self.moc)
newBreakfastItem.id = UUID()
newBreakfastItem.name = self.item.name
newBreakfastItem.calories = Int32(self.totalCalories)
newBreakfastItem.carbs = Int32(self.totalCarbs)
newBreakfastItem.protein = Int32(self.totalProtein)
newBreakfastItem.fat = Int32(self.totalFats)
newBreakfastItem.date = self.dateAdded
do {
if self.mocB.hasChanges { // saves only if changes are made
try? self.mocB.save()
}
}
}
ForEach(self.BreakfastItems, id: \.id) { newBreakfastItems in
HStack{
Text(newBreakfastItems.name ?? "Unknown")
.font(.headline)
.font(.headline)
HStack {
Text("Cal: \(Int32(newBreakfastItems.calories))")
.fontWeight(.light)
.foregroundColor(Color("Green Font"))
Text("F: \(Int32(newBreakfastItems.fat))")
.fontWeight(.ultraLight)
Text("P: \(Int32(newBreakfastItems.protein))")
.fontWeight(.ultraLight)
Text("C: \(Int32(newBreakfastItems.carbs))")
.fontWeight(.ultraLight)
However, Im not sure how to view the food saved to past days, as currently the app is just adding to the same list all the time, regardless of the day its saved on.
Would some form of ForEach Date statement work to cycle through views based on the day selected from the calendar?
Thanks in advance!

If you want to fetch the items for a special day, you could just use a predicate to your FetchRequest with a specific date. However, then you might have many fetch requests for each day. (not sure how you display it in your app)
Second approach, you can just fetch all your items, and filter them based on a date and show them. Here is an example filtering your items in the ForEach.
ForEach(self.BreakfastItems.filter { 
$0.date == Date()
}, id: \.id) { newBreakfastItems in
This will only display items, where the date is equals the current Date(). You might need to check same date only, regardless of the time.
You can add more filters based on the current date you want to show.

Related

Core Data: How can one get updates to entities of a relationship?

I know that you can observe changes to entities you've fetched with #ObservedObject.
The problem is best illustrated with an example: If I had an entity school with a one to many relationship to students and each student has a property numberOfPencils then I want to recalculate the sum of all pencils within a school whenever a student in the school changes his numberOfPencils property.
If I access all the students through the schools entity (which is an #ObservedObject) then I only get updates to the sum if a new student is added but not when an existing student changes his numberOfPencils because student objects can only be observed one by one as far as I know.
The only thing I could come up with is setting up a NSFetchedResultsControllerDelegate with a fetchRequestPredicate that gets all the students associated with a school through some keyPath from student to school which uniquely identifies the school. But that seems like a hacky way to do it and there surely must be a better solution that is eluding me.
Basically I want to get notified of changes to any of the students associated with a school.
Derived properties is definely a good way to go.
But to clarify: #ObservedObject can also get updated on change of the item. You just have to manually trigger the update with objectWillChange.send()
Say your School cell looks like this:
struct SchoolCell: View {
#ObservedObject var school: School
var body: some View {
HStack {
let pencils = school.pupilsArray.reduce(0, {$0 + $1.numberOfPencils})
Text(school.name)
Spacer()
Text(verbatim: "\(pencils) pencils")
.foregroundColor(.secondary)
}
}
}
then the Pupil modify would work like this:
// ...
#ObservedObject var school: School
// ...
Button("+10 Pencils") {
school.objectWillChange.send()
pupil.numberOfPencils += 10
try? viewContext.save()
}
It is important though that SchoolCell IS an own struct, so it gets refreshed when its #ObservedObject school changes.

Flutter: Reading and merging firestore data by date

I am looking for a way to merge the data I'm reading from Cloud Firestore by date. My aim is to read all the documents from Cloud Firestore, then place all documents of a certain date under ONE ExpansionTile. i.e. One ExpansionTile per date, and each ExpansionTile may contain many individual documents depending on how much was uploaded that day.
I'm having no issues reading the data from Cloud Firestore, but I am struggling to figure out how to organise and merge the documents by date once I have the information at hand. Keeping in mind that there will be multiple different dates and all with different amounts of documents per date.
As seen in the picture below, I currently have three documents with the same date, and therefore three different ExpansionTiles when creating them in the app. I am looking to convert these three into one ExpansionTile
I am currently reading the data from Cloud Firestore by date using the flutter_bloc package:
//Retrieves firebase data of all orders done in the past
Stream<OrderHistoryState> _loadOrderHistory() async* {
try {
_orderSubscription?.cancel();
var orderHistory = Firestore.instance
.collection('orders')
.orderBy('date', descending: true);
_orderSubscription = orderHistory.snapshots().listen(
(event) {
if (event.documents.length > 0) {
add(OrderHistoryUpdate(
documents: event.documents,
));
} else {
print("No Order to Load");
add(NoOrderHistory());
}
},
);
} catch (e) {
print("Error with Tracking Status, $e");
add(ErrorOrderHistory());
}
}
For those of you who may be curious, this was my solution to the problem. It may be a bit long winded, so any suggestions to shorten/improve it are still greatly appreciated:
//Make sure that there are documents within firebase
if (state.orderHistoryDocuments.length != 0) {
//Adding the first document manually so that
//the forloop section has something to compare dates
//This listForEachDate is for storing a collection
//of all documents of one date
listForEachDate = [state.orderHistoryDocuments[0].data];
//listOfDateLists is a list of all listForEachDate
//This gives us a list of lists with the documents
//separated by date i.e.
//[[date1, date1], [date2], [date3, date3, date3], etc]
listOfDateLists = [];
//i = 1 because index 0 already added above
for (int i = 1;
i < state.orderHistoryDocuments.length;
i++) {
//If the current index's date matches that of the previous
//index's date, then add it to the listForEachDate
if (state.orderHistoryDocuments[i]
.data["dateCreated"] ==
state.orderHistoryDocuments[i - 1]
.data["dateCreated"]) {
listForEachDate
.add(state.orderHistoryDocuments[i].data);
//If [index]date does not match [index - 1]date
//Then add the current listForEachDate to the
//listOfDateLists i.e. add sublist to list of lists
} else {
listOfDateLists.add(listForEachDate);
//Clear the listForEachDate so that we can create
//a new clean list of new dates
listForEachDate = [];
//Add the new date to the listForEachDate
//so that the process can start again
listForEachDate
.add(state.orderHistoryDocuments[i].data);
}
}
//Once the document has been iterated through,
//Add to the big list.
listOfDateLists.add(listForEachDate);
}

when I physically update my firestore database fields, my app is not able to access those new fields, and displays the old fields

here is my database
I have a users collection with documents as each user
the issue is that, whenever I change a field by using the actual database editing tool, my app is not able to access data
when I query these fields I get the data that was there before I edited it
if let document = document {
if let username = document.get("user_username") as? String,
let action = document.get("status") as? String,
let description = document.get("status_desc") as? String,
let companion = document.get("user_username") as? String {
ViewController.myName = username
self.helloLabel.text = username + " "
self.actionArray.append(action)
self.actionArray.append(description)
self.actionArray.append(companion)
} else {
print("missing fields in the status")
}
} else {
print("Document does not exist in cache")
}
}
when I print the status field now, I get the string that was there before, which was not "sleeping"
I am also confused because when I update the fields through the app itself, the database would look exactly the same, but the app is capable of detecting the change and shows the new field
can some one explain why this is happening, or how I would fix this issue
Set a listener to receive data-change events in order to listen to realtime changes of a documents with the onSnapshot() method instead of get().
The listener will receive an initial snapshot of the data and another snapshot every time the content changes.
link:
GET realtime updates with Cloud Firestore

How to know which index a child was added to an ordered FirebaseQuery

Using Firebase and Swift SDK
I just started with Firebase and wanted to display a list of Conversations ordered by last_update. The following query works fine :
let query = Database.database().reference().child("chat").child("channels").queryOrdered(byChild: "last_update")
query.observe(DataEventType.childAdded) { (snapshot: DataSnapshot) in
if let value = snapshot.value as? [String: Any?] {
log.debug("added channel: \(snapshot.key) : \(value)")
//add object to array, insertRow in tableview
}
}
The first time my view is loaded, each item arrives in the correct order specified by the query, so the display is ok. But if I create a new channel, it does appear at the end of the tableview, because I just add it at the end of the array and just call insertRow on my table view. My question is : is there any mecanism that give us the new inserted position of the DataSnapshot ?
Same question for DataEventType.childMoved : we get to know that a snapshot has moved, but how to know where it has moved ??
I finally end up using FirebaseUI, especially the submodule FirebaseDatabaseUI (see the github repo here)
They provide a FUITableViewDataSource, you just create a query and pass it to it, it will handle everything like sorting etc. I also used FUIArray, which is simply an array backed by a query, with a delegate for added / deleted / moved / update events (and proper indexes).

Should I create a separate Realm object for custom grouping?

I have been using Realm in an app and love it. Thank you! I have a question I would like to run by you folks and get some advice.
Lets say you have a realm object that contains a date field (simplified example):
class Appointment: Object {
dynamic var type = ""
dynamic var date = Date()
}
Now, suppose you have saved thousands of Appointments, and you are going to display these on a tableview or collectionview, grouped by week for example. So the datasource for your view would be something like this.
struct AppointmentsInWeek {
var startDate: Date?
var endDate: Date?
var appointments: [Appointment]
}
So, I have two options in mind I am thinking through with various pros and cons:

A) Make AppointmentsInWeek a subclass of Object, save it in the Realm, and use that as the datasource.
PROS:
Data for table will be lazy loaded from Realm
Simple to use at the moment it is needed.
CONS:
Keeping this up to date seems like a challenge. I would probably have some kind of observable looking at the Appointment in Realm and as any are added put them in the appropriate AppointmentWeek
B) Upon loading the screen with the tableview, fetch all appointments, or a subset of them, group them by their appropriate start and end date, and create an AppointmentsInWeek struct to use as the datasource.
PROS:
AppointmentsInWeek will always be up to date because it is created on the fly as needed
CONS:
We would have to keep all of this in memory, limiting the amount of appointments we could realistically display at once.
I started with option B but I am thinking now it might be better to go with option A. If I do, the biggest issue is making sure the Realm is always up to date when new appointments are added.
Questions
Are there other options I did not consider?
Which sounds like a better option?
Assuming I go with option A, would it make sense to have a class, that lives throughout the life of the app, in charge of observing the Appointments in Realm and when one is added (or changed), add it also to the appropriate AppointmentWeek?
Both options are fine, but I suggest option A. There are a few ways to approach option A.
First of all, you don't need to keep Appointment and AppointmentsInWeek in sync manually. You can use some combination of object properties, list properties, and linking objects properties to model connections between Appointments and AppointmentsInWeeks. Exactly how you might implement this depends on your app's specific needs, but here's one possibility:
class Appointment : Object {
dynamic var type = ""
dynamic var date = NSDate()
// Link to the week this appointment lives in, if desired
var week: AppointmentsInWeek? = nil
}
class AppointmentsInWeek : Object {
dynamic var startDate = NSDate()
dynamic var endDate = NSDate()
// Add appointment to List whenever it's created
let appointments = List<Appointment>()
}
A second possibility is to not use relationships at all, but to use queries instead. Realm supports queries through the Results class. You could add an ignored property on your AppointmentsInWeek class that queries the Realm for all appointments that fall within its date range:
class Appointment : Object {
dynamic var type = ""
dynamic var date = NSDate()
}
class AppointmentsInWeek : Object {
dynamic var startDate = NSDate()
dynamic var endDate = NSDate()
lazy var appointments: Results<Appointment> = {
// This implementation assumes you can get a reference
// to the Realm storing your Appointments somehow
return appointmentsRealm.objects(Appointments.self).filter(NSPredicate(format: "(date >= %#) AND (date < %#)", self.startDate, self.endDate))
}()
override static func ignoredProperties() -> [String] {
return ["appointments"]
}
}
In either case Realms, lists, and results all update automatically whenever the runloop of the thread they are on (usually the main thread) runs. Realm also supports a variety of notifications in case you need to react to changes manually.