HealthKit: Is it possible to sum two quantities by Query? - ios

In my app, I want to sum two quantities ActiveEnergyBurned and BasalEnergyBurned per day and show it. Is it possible to do this using a single HKQuery? Or do I have to fetch them separately and add them together?
If so, How do I join the results of two query? Any help is appreciated
var startDate = DateTime.Now.Date;
var endDate = startDate.AddDays(1);
var predicate = HKQuery.GetPredicateForSamples((NSDate)startDate,
(NSDate)endDate, HKQueryOptions.None);
HKStatisticsOptions sumOptions = HKStatisticsOptions.CumulativeSum;
HKStatisticsQuery sQuery = new HKStatisticsQuery(qType, predicate,
sumOptions, (hQuery, result, error) => {
HKQuantity sum = result.SumQuantity();
var date = result.EndDate;
var dateTime = DateHelperIOS.NSDateToDateTime(date);
double value = sum.GetDoubleValue(hkUnit);
});
HKHealthStore HStore = new HKHealthStore();
HStore.ExecuteQuery(sQuery);

HealthKit queries, including HKStatisticsQuery can query only one quantity type at a time(*).
You can execute several queries in parallel in the background. This is probably faster than executing the queries sequentially.
(*) There is one exception, HKActivitySummary, that returns several values, but not all you want.

Related

Airtable Scripting block - Batch copy a field to another field in same table (10,000 records)

I'm trying to copy one field to another field in the same table with 10,000 + records, in batches of 50 using the Scripting App.
What am I doing wrong in this code block? It only copies the first record. If I remove the await, it'll copy 15 records then stop.
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
let records = query.records;
updateLotsOfRecords(records);
async function updateLotsOfRecords(records) {
let i = 0;
while (i < records.length) {
const recordBatch = records.slice(i, i + 50);
for (let record of recordBatch) {
let sourceValue = record.getCellValue('Merchant');
await table.updateRecordAsync(record, { 'LogoBase64': sourceValue });
}
i += 50;
}
}
you should use updateRecordsAsync function, not updateRecordAsync
When using single update function in loop, there is no sense to divide it into batches.
You exceed some limit of calls per second, that's why it stops.
For multiple updates, you need to use updateRecordsAsync, like this
while (recordsToWrite.length > 0) {
await updates.updateRecordsAsync(recordsToWrite.slice(0, 50));
recordsToWrite = recordsToWrite.slice(50);
}
Data that you should pass to it, more complex. I learned JS for 3 months and still have difficulties understandins all these "arrays of arrays of objects, passed via object's property". But that's the key to unerstand JS.
It's quite hard to leave basic/pascal habits, with plenty of inserted FOR loops, and GOTO sometimes))
I think, you already found the answer for 2 months, so my answer may be useless, but when i write it here, maybe i understand it better for myself. And help to some beginners also.
For single write, you pass (record, Object), where object is {field:'Value}
For multiple, you should pass
Array of Objects, where
Object is {id:recordID, fields:{object2}} , where
object2 is array of obj3 [ {obj3},{obj3}, {obj3} ], where
obj3 is a { 'Name or ID of field': fieldvalue }
you script might be:
let query = await view.selectRecordsAsync();
let updates=query.records.map(rec=>{
Map method can be applied for arrays, and 'query.records' is array of records. Here
'rec' is loop variable inside this "arrowfunction"
now let's create obj3 , in our case { 'Name or ID of field': fieldvalue }
{'LogoBase64':rec.getCellValue('Merchant')}
wrap it into fields property
fields:{'LogoBase64':rec.getCellValue('Merchant')}
and add record id
wrapping as Object.
To avoid complex string with linebreaks, and to make object creation easier, we can do it with function:
{rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
fuction myObj(rec){return {rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
map(rec=>myObj(rec)) - can be written as map(myObj)
we need array of objects, and map method gets first array, doing something with each element and return other array, of results. like we need.
and now finally we get
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
function myObj(rec){return {'id':rec.id,'fields':{'Logobase64':rec.getCellValue('Merchant')}}};
let updates=query.records.map(myObj);
while (updates.length > 0) {
await table.updateRecordsAsync(updates.slice(0, 50));
updates = updates.slice(50); }

How avoid having double loop?

From healthkit, I will receive some datas, for example steps data.
I save in my server this datas. Il have then an array of datas:
1.- startDate, endDate, value
2.- startDate, endDate, value
etc
It can be very lot of values in my server.
Then I get the values in the Healthkit. I have lot of values. Values who are in the server and new values.
I want to upload to the server only the new values.
I do so:
for each value in my server {
for each value in healthkit{
if(startDate, endDate and value are not equal to the value in the server){
then save the value in the server
}
}
}
The algo will work, but it's very very slow. I can have lot of values in the two systems. Most of them the same in the two places.
Have you an idea how to do better?
I cannot save a flag in the healthKit.
I'm using ionic with angular 4 and typescript.
This answer assumes that the data on your server and the data from healthKit are sorted in the same way, or that you can sort them without affecting anything else.
For example you can sort your data on the server and the data from healthKit by StartDate, break ties by EndDate, then break ties by value.
Now you have two sorted arrays that you want to merge. The idea is to use the merge function of merge sort explained here.
You will end up with an array containing all the data without repetitions, which you can save on your server.
Edit:
void mergeArrays(int arr1[], int arr2[], int n1,
int n2, int arr3[])
{
int i = 0, j = 0, k = 0;
// Traverse both array
while (i<n1 && j <n2)
{
// Check if current element of first
// array is smaller than current element
// of second array. If yes, store first
// array element and increment first array
// index. Otherwise do same with second array
if (arr1[i] < arr2[j])
arr3[k++] = arr1[i++];
else if (arr2[j] < arr1[i])
j++;
else
j++,i++;
}
// Store remaining elements of first array (healthKit Array)
while (i < n1)
arr3[k++] = arr1[i++];
}
I think I need a bit more context to understand the problem. By "new" values, are you including entries that have been modified, or just new entries that have been added?
If you only need the new values, and they are being added to the end of the array on the client side, then you can just take them from the end of the array.
const new_count = healthkit.length - myserver.length, // number of new entries
index_start = healthkit.length - new_count, // index in healthkit array where new entries start
new_values = healthkit.slice(index_start); // new array containing only new entries
addNewValues(new_values);
Now you have the new values in a separate array, and you can update them to the server.
If it is the case that you do need to update values that have changed, you can iterate over both arrays (only once, and at the same time) and find the differences. I am going to assume that the entries are in the same index for both arrays. I'm also assuming the "value" key is the only one you want to compare. You can do the following to find the values that have been modified.
const modified_values = [];
myserver.forEach(function(entry, i) { // iterate over server array
let healthkit_entry = healthkit[i]; // get healthkit entry with same index
if(entry.value !== healthkit_entry.value) { // see if value has changed
modified_values.push(healthkit_entry); // if changed, add to modified array
}
});
updateModifiedValues(modified_values);
Now the modified_values array has all of the modified entries from the healthkit array.

Inserting into Array and comparing Dates Swift iOS Code

I am attempting to create an array that will store 365 integers, it must be filled completely. I am using Healthkit to figure out the users steps from a year back, hence the array size. Every integer represents 1 day.
I have done this in android already and it worked perfectly, I got 365 integers back with 0's for the days with no steps, however, the problem is with iOS health kit I get nothing from days with no data, which I need. In order to do this I thought I would compare the date variable I get with the date of the current day + 1 and loop through the array to see if it find any matching cases, if not put a 0 into it at the end.
So in order to do this I created an array of 365, at the line var ID = 0 is where I attempt to store the integers correctly into the array. I am using Swift 4.
struct stepy {
static var step = [365]
}
This is where I enumerate through the stepData, first at var ID I attempt to compare the date I get in the enumerate loop with the current date (basically index 0 in the array, which represents the first day, the current day).
However I got a problem, currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration? Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
stepsQuery.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -365, to: endDate as Date, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
var date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
var id = 0
var dateToInsert = date
var today = Date()
var todaytwo = Date()
for index in 0..<stepy.step.count {
if dateToInsert != today {
id = index + 1
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(0)
}
if date == dateToInsert as Date {
today.add(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(Int(steps))
id = index + 1
}
}
}
}
}
}
static var step = [365]
The above doesn't make sense. It does not create an array of 365 integers, it creates an array with one integer in it that is 365. What you need is
static var step: [Int] = []
which creates an empty array you can just append your results to
currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration?
because your code appends to the array, which is the same as in Java: myArrayList.add(element), this is not a problem.
Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
Correct, it doesn't. Also this line:
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
does not make any sense. That should be causing a compiler error to.
Anyway, I don't see what the point of all that is. Your outer loop presumably loops through the statistics, one per day, so just do your calculation and append to the array. It'll be oldest first, but you can then just reverse the array to get newest first.

NSSet filtered objects are removed from previous fetched object from Core Data

I have a one to many relationship setup in my Core Data:
Day <----->> Transactions
A day object will contain an NSSet of transactions and a Date.
At some point I want to fetch the latest Day and get its Transactions filtered by a period (monthly and yearly) and add them to another Day object and save this.
Here is my code:
// get latest day from core data
let lastDay = fetchDays(context: context) { request in
request.sortDescriptors = Day.defaultSortDescriptors
request.fetchLimit = 1
}.first
This will get me the correct Day object. After this I want to get the filtered transactions:
let filteredTransactions = lastDay?.transactions?.filtered(using: NSPredicate(format: "period IN %#", [.monthly, .yearly]))
After I have the filtered transactions I want to add them to a new Day object and save that in Core Data:
context.performChanges {
let day = Day(context: self.context)
day.date = NSDate.today()
day.transactions = filteredTransactions as NSSet?
}
This will also correctly save inside Core Data, but after this point, the transactions that I filtered out (monthly and yearly) have been removed from the lastDay object.
If I fetch that object again, the transactions are gone.
And I have no idea what is going on. It seems that any type of transactions that are filtered are also removed.
I tried all day long to figure out what is going on, but I can't seem to figure out the problem.
Any feedback would be appreciated.
Create a full new list of transactions and set the day object of each transaction to the new Day record:
context.performChanges {
let day = Day(context: self.context)
day.date = NSDate.today()
if let transactions = filteredTransactions {
for t in Array(transactions) {
let transaction = Transactions(context: self.context)
transaction.clone(from: t) // write extension method to Transactions
transaction.day = day
}
}
}

How to get a query retrieving two objects from Parse via Swift

I have a parse database as in the picture.
I need to retrieve with a query code, two objects, for example the last created dates of type 2 and 3.
I am trying the codes below, but dont know how to merge these two queries (query2 and query3)? Or might there be another way to retrieve these two objects as one table?
var query2 = PFQuery(className: "stories")
query2.whereKey("Type", equalTo: 2)
query2.addDescendingOrder("createdAt")
query2.getFirstObject()
var query3 = PFQuery(className: "stories")
query3.whereKey("Type", equalTo: 3)
query3.addDescendingOrder("createdAt")
query3.getFirstObject()
I don't think you could do exactly what you currently have with 1 query. You could combine them but only to get back an array of all 2 and 3 types and then split them out yourself.
If the issue is making 2 different network requests then you could create a cloud code function to run the 2 queries and return the results in a single response.
you can do this one query as well
var query2 = PFQuery(className: "stories")
query2.whereKey("Type", equalTo: 2)
query2.whereKey("Type", equalTo: 3)
query2.addDescendingOrder("createdAt")
You can change you approach too , you can send array to compare and sort results
query2.whereKey("Type", containedIn: #[2,3])
query2.addDescendingOrder("createdAt")
After this don't use getFirstObject ; get all array and get your desired results using predicate or any else , this will save one network call as well.
This is for getting 2 values from Array
NSArray *fullArray= All objects from the parse ;
NSMutableArray *selectedObjectsArray = [NSMutableArray array];
for(int i=0 ; i<fullArray.count ; i++){
if (selectedObjectsArray.count==1 && ![[[selectedObjectsArray objectAtIndex:0] objcetForKey:#"type"] isEqualToString:[[Full array objectAtIndex:i] objcetForKey:#"type"]]) {
[selectedObjectsArray addObject:[fullArray objectAtIndex]];
break ;
}else{
[selectedObjectsArray addObject:[fullArray objectAtIndex]];
}
}
In Swift (i m not too good in swift so double check swift code before using)
for object in fullArray {
if (selectedObjectsArray.count==1 && ![[selectedObjectsArray[0][#"type"] isEqualToString:fullArray[i][#"type"]) {
selectedObjectsArray[1]=[fullArray[i];
break ;
}else{
selectedObjectsArray[0]=[fullArray[i];
}
}

Resources