query not working - React Native app + Xcode - objective-c

My HealthKit query is not working.
I am trying to get the steps count and I am confused on which part of my code is in-correct.
When I build the app, it either throws "updateStepsCount is not a recognised objc method" or "Unhandled promise rejection".
Where am I going wrong here!!!
Controller.swift:
#objc
func updateStepsCount(_ statisticsCollection: HKStatisticsCollection, _ resolve: #escaping RCTPromiseResolveBlock,
rejecter reject: #escaping (RCTPromiseRejectBlock) -> Void) {
let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())
let anchorDate = Date.mondayAt12AM()
let daily = DateComponents(day: 1)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum, anchorDate: anchorDate, intervalComponents: daily)
healthStore.execute(query)
struct Step {
let id = UUID()
var count: Int?
var date: Date?
}
let endDate = Date()
statisticsCollection.enumerateStatistics(from: startDate!, to: endDate) { (statistics, stop) in
let count = statistics.sumQuantity()?.doubleValue(for: .count())
let steps = Int(count ?? 0)
var stepCount = [Step]()
var tempStepCount = Step(count: steps, date: Date())
tempStepCount.count = steps
tempStepCount.date = startDate
stepCount.append(tempStepCount)
}
resolve(Step())
}
Controller.m
RCT_EXTERN_METHOD(updateStepsCount: (HKStatisticsCollection)someStatisticsCollection
resolve: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
JS side:
clickHandler = async() => {
const login = HealthkitController.updateStepsCount()
.then(result => console.warn(result));
}

Thats because your updateStepsCount method requires argument statisticsCollection as a first param. When you try to call updateStepsCount() in js it converts to updateStepsCount in objc bridge but you don't have this method you have updateStepsCount: and this one has a param so you have to pass a param.
NOTE: React Native bridge works only with standard JSON types so input parameters can be standard types (number, string, dictionary etc.) but you can also use RCTConvert to map they to custom ones. Result of your promise must be standard type as well so you can not return your struct. (https://reactnative.dev/docs/native-modules-ios#argument-types)

Related

What is the preferred way of working with dates and JavaScript?

When working with a Faunadb record that contains a date value, I struggled with using that date in JavaScript. Eventually I got it working like so:
project.shipDate = new Date(await client.query(q.Format('%t', project.shipDate)));
This seems fine, but I also noticed I could do this:
let test = JSON.parse(JSON.stringify(project.created));
console.log(test);
datetest = new Date(test["#date"]);
Which seems wonky (grin), but may be quicker as it's not using the Fauna client library. Which should I prefer?
The JS driver has several helper classes that let you cast the javascript Time and Date objects to their FQL counterparts.
Here is an example.
From Fauna to JS
You can create a new document that contains a date value.
const project = await client.query(
q.Create(
q.Collection("projects"),
{ data: { shipDate: q.ToDate(q.Now()) } }
)
)
You can retrieve the date value and use as a javascript Date object by using the value property
const shipDate = new Date(project.data.shipDate.value)
From JS to Fauna
The date value can be modified however you want, and you can pass it back to FQL using the values.FaunaDate class.
const { values } = require('faunadb')
/* ... */
let nextDay = new Date(shipDate.getTime() + 86400000)
const faunaDate = new values.FaunaDate(nextDay)
Full Example
const project = await client.query(
q.Create(
q.Collection("projects"),
{ data: { shipDate: q.ToDate(q.Now()) } }
)
)
console.log(project)
const shipDate = new Date(project.data.shipDate.value)
console.log(shipDate)
let nextDay = new Date(shipDate.getTime() + 86400000)
console.log(nextDay)
const projectRef = project.ref
const projectUpdate = await client.query(
q.Update(
projectRef,
{ data: { shipDate: new values.FaunaDate(nextDay) } }
)
)
console.log(projectUpdate)
{
ref: Ref(Collection("projects"), "307924674409398337"),
ts: 1629918703380000,
data: { shipDate: Date("2021-08-25") }
}
2021-08-25T00:00:00.000Z
2021-08-26T00:00:00.000Z
{
ref: Ref(Collection("projects"), "307924674409398337"),
ts: 1629918703470000,
data: { shipDate: Date("2021-08-26") }
}

TypeError: expected dynamic type 'double' but had type 'string'

I'm running into this error when trying to use datePickerAndroid and setting the state afterwards.
Error Message
I feel like the problem may be from the initial state being a new Date() but not 100% sure.
_isAndroid = async () => {
let {action, year, month, day} = await DatePickerAndroid.open({
date: this.props.startDate,
});
if (action === DatePickerAndroid.dismissedAction) {
this.props.closeModal()
} else {
const date = year + "-" + month + "-" + day
this.props.onDateChange(date)
}
}
Prop Function:
onDateChange = (newDate) => {
this.setState({currentDate: newDate}) // <- This one is breaking
let dates = this.state.dates;
let index = this.state.currentIndex;
if (this.state.currentKey === "start") {
dates[index].start = newDate
} else {
dates[index].end = newDate
}
this.setState({dates: dates});
}
I've narrowed it down to the first setState, and I've tried making the initial state a string as well as setting the state to a simple string but still getting the error.
For DatePickerAndroid
expected dynamic type double
In other words native component wants time, but you put string. You this.props.startDate is string, transfer time in time format. Example:
const {action, year, month, day} = await DatePickerAndroid.open({
date: new Date() // <- This is Date, not string!
});
Good luck!

Use of unresolved identifier 'MapTasks' in Swift

I am following tutorial, as it is pretty old tutorial and they actually used GoogleMaps framework package instead of pods which I followed and everything was going smooth till I reached Spotting a Custom Location. In that section they asked to update func geocodeAddress as below, and add var mapTasks = MapTasks() in ViewController.swift file which I did but it gives me error.
Use of unresolved identifier 'MapTasks'
error
func geocodeAddress(address: String!, withCompletionHandler completionHandler: ((status: String, success: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
geocodeURLString = geocodeURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let geocodeURL = NSURL(string: geocodeURLString)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let geocodingResultsData = NSData(contentsOfURL: geocodeURL!)
var error: NSError?
let dictionary: Dictionary<NSObject, AnyObject> = NSJSONSerialization.JSONObjectWithData(geocodingResultsData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as Dictionary<NSObject, AnyObject>
if (error != nil) {
println(error)
completionHandler(status: "", success: false)
}
else {
// Get the response status.
let status = dictionary["status"] as String
if status == "OK" {
let allResults = dictionary["results"] as Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as String
let geometry = self.lookupAddressResults["geometry"] as Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as Dictionary<NSObject, AnyObject>)["lng"] as NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as Dictionary<NSObject, AnyObject>)["lat"] as NSNumber).doubleValue
completionHandler(status: status, success: true)
}
else {
completionHandler(status: status, success: false)
}
}
})
}
else {
completionHandler(status: "No valid address.", success: false)
}
}
Here is my GitHub repository
Thank you in advance.
If you fully read that tutorial, you will find in the instruction that you need to create a file name MapTasks which is a class.
You can just copy this file from GitHub and add it to your project.

How can we reading / coerce CFArray and CFString etc values from within OS X JXA?

If you experiment with reading/setting the OSX input language through the ObjC bridge, writing snippets like:
(function () {
'use strict';
ObjC.import('Carbon');
ObjC.import('stdio');
var sourceList = $.TISCreateInputSourceList(null, false);
var current_source = $.TISCopyCurrentKeyboardInputSource();
var cfs = $.TISGetInputSourceProperty(current_source, $.kTISPropertyInputSourceID);
var cfn = $.TISGetInputSourceProperty(current_source, $.kTISPropertyLocalizedName)
var sourceCount = $.CFArrayGetCount(sourceList)
return $.CFArrayGetValueAtIndex(sourceList, 0)
})();
we soon get obj reference return values of CF types. In ObjC itself these can be coerced to NS values. Any sense of how that can be achieved in JavaScript for Applications ?
(I am getting CF object reference return values from which I haven't succeeded in extract string or other primitive values)
You can coerce a CF type to an NS type by first re-binding the CFMakeCollectable function so that it takes 'void *' and returns 'id', and then using that function to perform the coercion:
ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString); // => $("foo")
To make this easier to use in your code, you might define a .toNS() function on the Ref prototype:
Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }
Here is how you would use this new function with the TIS* functions:
ObjC.import('Carbon');
var current_source = $.TISCopyCurrentKeyboardInputSource();
var cfs = $.TISGetInputSourceProperty(current_source, $.kTISPropertyInputSourceID);
cfs.toNS() // => $("com.apple.keylayout.US")

Angular http testing

I have a fairly simple controller that gets a simple json list of objects ...
function ProductGroupsCtrl($scope, $http, $routeParams, sharedService, popupService) {
$scope.list = null;
$scope.selectedItem = null;
$scope.selectedItemJsonString = '';
$scope.selectItem = function (item) {
$scope.selectedItem = item;
$scope.selectedItemJsonString = JSON.stringify(item);
//alert(JSON.stringify(item));
};
$scope.closePopup = function () {
$scope.selectedItem = null;
$scope.selectedItemJsonString = '';
};
// sharedService.prepForBroadcast($routeParams.anotherVar);
$http({
method: 'GET',
url: '/ProductGroup'
}).success(function (data) {
$scope.list = data;
}).
error(function (data) {
$scope.message = 'There was an error with the data request.';
});
}
I then try to mock the request in the test class:
var scope, ctrl, $httpBackend, sharedServiceMock = {}, popupServiceMock = {};
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httsypBackend.expectGET('/ProductGroup').
respond([{
ProductGroupID: 5,
MenuTitle: "Promotional Products",
AlternativeText: "Coming soon - a collection of environmentally friendly Promotional Products",
OrdinalPosition: 5,
Active: false
}]);
scope = $rootScope.$new();
ctrl = $controller(ProductGroupsCtrl, {
$scope: scope,
$http: $httpBackend,
sharedService: sharedServiceMock,
popupService: popupServiceMock
});}));
However I receive an error in the testacular window object undefined. What have I done wrong here?
Found the answer. If I remove the error callback function from the $http.get method then it works, i.e. remove the following ...
error(function (data) {
$scope.message = 'There was an error with the data request.';
}
I have to say Angular sure is a steep learning curve for someone who is not a day to day JavaScript programmer (although I seem to be doing more and more). Thanks for the help anyway KatieK :-)