I'm trying to read workouts from a WatchOS App. Unfortunately, the returned workouts are always empty when I read the workouts on WatchOS.
This is how it's currently implemented:
func fetchWorkouts(limit: WorkoutLimit) async throws -> [HKWorkout] {
let activityType = PulseConfiguration.activityType
// 1. Get all workouts with the configured activity type.
let walkingPredictate = HKQuery.predicateForWorkouts(with: activityType)
// 2. Get all workouts that only came from this app.
let sourcePredicate = HKQuery.predicateForObjects(from: .default())
// 3. Combine the predicates into a single predicate.
let compound = NSCompoundPredicate(andPredicateWithSubpredicates:
[walkingPredictate, sourcePredicate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
typealias WorkoutsContinuation = CheckedContinuation<[HKWorkout], Error>
return try await withCheckedThrowingContinuation { (continuation: WorkoutsContinuation) in
let query = HKSampleQuery(
sampleType: .workoutType(),
predicate: compound,
limit: limit.count,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
guard let samples = samples as? [HKWorkout], error == nil else {
if let error = error {
continuation.resume(throwing: error)
}
return
}
continuation.resume(returning: samples)
}
healthStore.execute(query)
}
}
I noticed that the HKSource returned by .default (WatchOS) is different than the source associated to the saved workouts (Which contains the bundle identifier from the iPhone App).
Is there some other way to access the workouts from the WatchOS App?
Thanks in advance. Josh.
Watch has a limited subset of health data available. You can use HKHealthStore.earliestPermittedSampleDate to see how far back data you can query for.
Related
I was struggling yesterday to handle in a SWIFT app a Firestore map object that had a variable size so I though to share my solution.
The document contained a map like this:
{"07:45"=9;"09:56"=4;"13:12"=16}
The problem was that there could be more or less fields and the field names are unknown in advance, so it was not possible to know how many elements there were or even the names of the fields. For this reason I couldn't address the fields directly by just reading it into a dictionary. On top of that, as dictionaries is not ordered, I couldn't just address the elements in the dictionary by index.
I solved this by first reading the map into a dictionary, sorting it into an array, and then addressing the index of the array I wanted.
db.document(eventID).getDocument{ (document, error) in
if let document = document, document.exists {
let let data = document.data()
let timeslots = data!["times"] as! Dictionary<String, Int>
let self.count = timeslots.count. // To use elsewhere
let sortedTimes = self.timeslots.sorted(by: { $0.0 < $1.0 }) // Sort by key value which contains times as strings.
print(sortedTimes[0].value) // Just to print the value of the first tuple. NB sortedTimes is an Array of key,value pairs.
} else {
print("Document does not exist")
}
}
I am new to flutter and I am sure there is a simple way of doing this. Let me first give you a background. I have 2 tables(collections). The first one store a mapping. Therefore it returns a key based on an id which will be used to query the second table and retrieve the data from firebase.
I have written 2 data models and 2 functions which return Future<> data. They are as follows-
Future<SpecificDevice> getSpecificDevice(String deviceId) {
Future<SpecificDevice> obj =_database.reference().child("deviceMapping").orderByChild("deviceId").equalTo(deviceId).once().then((snapshot) {
SpecificDevice specificDevice = new SpecificDevice(deviceId, "XXXX", new List<String> ());
if(snapshot.value.isNotEmpty){
print(snapshot.value);
snapshot.value.forEach((key,values) {
if(values["deviceId"] == deviceId) {
specificDevice.deviceKey = values["deviceDesc"];
specificDevice.vendorList = List.from(values["vendorList"]);
}
});
}
return specificDevice;
});
return obj;
}
This function gets the mapping deviceId -> deviceKey.
This is the key of record stored in another table. Following is the function for it.
Future<Device> getDeviceDescription(String deviceKey) {
Future<Device> device = _database.reference().child("deviceDescription").once().then((snapshot) {
Device deviceObj = new Device("YYYY", "YYYY", "YYY", "YYYY", "YYYY");
if(snapshot.value.isNotEmpty){
print(snapshot.value);
//Future<SpecificDevice> obj = getSpecificDevice(deviceId);
//obj.then((value) {
snapshot.value.forEach((key,values) {
if(key == deviceKey) { // compare with value.deviceKey instead
print(values["deviceDescription"]); // I get the correct data here.
deviceObj.manual = values["deviceManual"];
deviceObj.deviceType = values["deviceType"];
deviceObj.description = values["deviceDescription"];
deviceObj.brand = values["deviceBrand"];
deviceObj.picture = values["devicePicture"];
}
// });
});
}
return deviceObj;
});
return device;
}
Now both of these functions work. I want to make it work one after the other. In the above function, if I uncomment the lines of code, the data is retrieved properly in the inner function but it returns initial default values set because the values get returned before setting the obj of SpecificDevice.
Here is where I am getting the error. I am calling the second function in FutureBuilder<> code with the above lines uncommented and taking input param as deviceId.
return FutureBuilder<Device>(
future: getDeviceDescription(deviceId),
builder:(BuildContext context,AsyncSnapshot snapshot){... // using snapshot.data in its child.
Here in snapshot.data. would give me YYYY. But it should get me the value from the database.
I am stuck with this for a while. Any help in fixing this? or if what I am trying to do is clear then please suggest me a better way to approach this. Thanks in advance!
The answer is rather simple:
first and foremost - you forgot to use async / await keywords, which will guarantee synchronous data retrieval from the database. Always use them, if you are connecting to any network service
to make one command work after another - use .then((value) {}). It will get data from the first function (which you pass using return) and use it in the second function.
Solved the problem by changing the calling function to -
return FutureBuilder<Device>(
future: getSpecificDevice(deviceId).then((value){
return getDeviceDescription(value.deviceKey);
}),
builder:(BuildContext context,AsyncSnapshot snapshot){
I have a large Firestore collection with 10,000 documents.
I want to show these documents in a table by paging and filtering the results at 25 at a time.
My idea, to limit the "reads" (and therefore the costs), was to request only 25 documents at a time (using the 'limit' method), and to load the next 25 documents at the page change.
But there's a problem. In order to show the number of pages I have to know the total number of documents and I would be forced to query all the documents to find that number.
I could opt for an infinite scroll, but even in this case I would never know the total number of results that my filter has found.
Another option would be to request all documents at the beginning and then paging and filtering using the client.
so, what is the best way to show data in this type of situation by optimizing performance and costs?
Thanks!
You will find in the Firestore documentation a page dedicated to Paginating data with query cursors.
I paste here the example which "combines query cursors with the limit() method".
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
If you opt for an infinite scroll, you can easily know if you have reached the end of the collection by looking at the value of documentSnapshots.size. If it is under 25 (the value used in the example), you know that you have reached the end of the collection.
If you want to show the total number of documents in the collection, the best is to use a distributed counter which holds the number of documents, as explained in this answer: https://stackoverflow.com/a/61250956/3371862
Firestore does not provide a way to know how many results would be returned by a query without actually executing the query and reading each document. If you need a total count, you will have to somehow track that yourself in another document. There are plenty of suggestions on Stack Overflow about counting documents in collections.
Cloud Firestore collection count
How to get a count of number of documents in a collection with Cloud Firestore
However, the paging API itself will not help you. You need to track it on your own, which is just not very easy, especially for flexible queries that could have any number of filters.
My guess is you would be using Mat-Paginator and the next button is disabled because you cannot specify the exact length? In that case or not, a simple workaround for this is to get (pageSize +1) documents each time from the Firestore sorted by a field (such as createdAt), so that after a new page is loaded, you will always have one document in the next page which will enable the "next" button on the paginator.
What worked best for me:
Create a simple query
Create a simple pagination query
Combine both (after validating each one works separately)
Simple Pagination Query
const queryHandler = query(
db.collection('YOUR-COLLECTION-NAME'),
orderBy('ORDER-FIELD-GOES-HERE'),
startAt(0),
limit(50)
)
const result = await getDocs(queryHandler)
which will return the first 50 results (ordered by your criteria)
Simple Query
const queryHandler = query(
db.collection('YOUR-COLLECTION-NAME'),
where('FIELD-NAME', 'OPERATOR', 'VALUE')
)
const result = await getDocs(queryHandler)
Note that the result object has both query field (with relevant query) and docs field (to populate actual data)
So... combining both will result with:
const queryHandler = query(
db.collection('YOUR-COLLECTION-NAME'),
where('FIELD-NAME', 'OPERATOR', 'VALUE'),
orderBy('FIELD-NAME'),
startAt(0),
limit(50)
)
const result = await getDocs(queryHandler)
Please note that the field in the where clause and in orderBy must be the same one! Also, it is worth mentioning that you may be required to create an index (for some use cases) or that this operation will fail while using equality operators and so on.
My tip: inspect the error itself where you will find a detailed description describing why the operation failed and what should be done in order to fix it (see an example output using js client in image below)
Firebase V9 functional approach. Don't forget to enable persistence so you won't get huge bills. Don't forget to use where() function if some documents have restrictions in rules. Firestore will throw error if even one document is not allowed to read by user. In case bellow documents has to have isPublic = true.
firebase.ts
function paginatedCollection(collectionPath: string, initDocumentsLimit: number, initQueryConstraint: QueryConstraint[]) {
const data = vueRef<any[]>([]) // Vue 3 Ref<T> object You can change it to even simple array.
let snapshot: QuerySnapshot<DocumentData>
let firstDoc: QueryDocumentSnapshot<DocumentData>
let unSubSnap: Unsubscribe
let docsLimit: number = initDocumentsLimit
let queryConst: QueryConstraint[] = initQueryConstraint
const onPagination = (option?: "endBefore" | "startAfter" | "startAt") => {
if (option && !snapshot) throw new Error("Your first onPagination invoked function has to have no arguments.")
let que = queryConst
option === "endBefore" ? que = [...que, limitToLast(docsLimit), endBefore(snapshot.docs[0])] : que = [...que, limit(docsLimit)]
if (option === "startAfter") que = [...que, startAfter(snapshot.docs[snapshot.docs.length - 1])]
if (option === "startAt") que = [...que, startAt(snapshot.docs[0])]
const q = query(collection(db, collectionPath), ...que)
const unSubscribtion = onSnapshot(q, snap => {
if (!snap.empty && !option) { firstDoc = snap.docs[0] }
if (option === "endBefore") {
const firstDocInSnap = JSON.stringify(snap.docs[0])
const firstSaved = JSON.stringify(firstDoc)
if (firstDocInSnap === firstSaved || snap.empty || snap.docs.length < docsLimit) {
return onPagination()
}
}
if (option === "startAfter" && snap.empty) {
onPagination("startAt")
}
if (!snap.empty) {
snapshot = snap
data.value = []
snap.forEach(docSnap => {
const doc = docSnap.data()
doc.id = docSnap.id
data.value = [...data.value, doc]
})
}
})
if (unSubSnap) unSubSnap()
unSubSnap = unSubscribtion
}
function setLimit(documentsLimit: number) {
docsLimit = documentsLimit
}
function setQueryConstraint(queryConstraint: QueryConstraint[]) {
queryConst = queryConstraint
}
function unSub() {
if (unSubSnap) unSubSnap()
}
return { data, onPagination, unSub, setLimit, setQueryConstraint }
}
export { paginatedCollection }
How to use example in Vue 3 in TypeScript
const { data, onPagination, unSub } = paginatedCollection("posts", 8, [where("isPublic", "==", true), where("category", "==", "Blog"), orderBy("createdAt", "desc")])
onMounted(() => onPagination()) // Lifecycle function
onUnmounted(() => unSub()) // Lifecycle function
function next() {
onPagination('startAfter')
window.scrollTo({ top: 0, behavior: 'smooth' })
}
function prev() {
onPagination('endBefore')
window.scrollTo({ top: 0, behavior: 'smooth' })
}
You might have problem with knowing which document is last one for example to disable button.
I am trying to get the depth data associated with an image in the PhotoLibrary.
I can get the image, and the URL, but I can't seem to get the aux data associated with it. The call to CGImageSourceCreateWithURL returns a source, but the call to CGImageSourceCopyAuxiliaryDataInfoAtIndex returns nil for both kCGImageAuxiliaryDataTypeDisparity and kCGImageAuxiliaryDataTypeDepth.
Is there something I am missing here?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage]
let url = info[UIImagePickerControllerImageURL]
print("url=",url)
guard let source = CGImageSourceCreateWithURL(url as! CFURL, nil) else {
return
}
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return
}
}
I struggled with this for a whole day! I finally figured it out, though, after watching the first half of the WWDC Video titled "Editing Images With Depth."
My problem was using a URL for the image that was not from the PHAsset.
Here is the link:
LINK TO WWDC VIDEO
If you don't feel like watching it, check out this function that I wrote that does pretty much exactly what is done in the video.
You have to provide the function the [info] that is returned from the DID_FINISH_PICKING_IMAGE_WITH_INFO function from the UIImagePickerDelegate.
Before using this function - note that is actually doesn't work! It is great to look at though, because it shows the steps clearly. But due to asynchronous behavior, the function will always return nil before it has a chance to set the local depth variable to the AVDepthData.
My solution was to break the function apart and use Grand Central Dispatch to create a Dispatch Group, enter it, retrieve the imageURL from the PHAsset, and then leave the Dispatch Group. Upon leaving the Dispatch Group, the DispatchGroup.NOTIFIED function then proceeded with the rest of the process.
I hope this helps!!!
func returndepthdata(usingimageinfo: [UIImagePickerController.InfoKey : Any]) -> AVDepthData? {
var depthdata: AVDepthData! = nil
if let photoasset = usingimageinfo[.phAsset] as? PHAsset {
let input = photoasset.requestContentEditingInput(with: nil, completionHandler: { (input, info) in
if let imageurl = input?.fullSizeImageURL {
if let source = CGImageSourceCreateWithURL(imageurl as CFURL, nil) {
if let imageproperties = CGImageSourceCopyProperties(source, nil) {
if let disparityinfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) {
if let truedepthdata = try? AVDepthData(fromDictionaryRepresentation: disparityinfo as! [AnyHashable : Any]) {
depthdata = truedepthdata
}
}
}
}
}
})
}
return depthdata
}
The image URL supplied by UIImagePickerController does not include any of the metadata associated with depth. To get this information, you must access the PHAsset using the PhotoBook API.
First, import the API:
import Photos
Before you display your image picker, request user access to the photo book. You'll need to add an info dictionary key for Photo Library Usage for this to work:
switch PHPhotoLibrary.authorizationStatus() {
case .notDetermined:
PHPhotoLibrary.requestAuthorization { (status) in
if status == .authorized {
DispatchQueue.main.async {
// Display image picker here
}
}
}
case .authorized: // Display image picker here
case .denied, .restricted: // Display appropriate error here
}
Now, in your image picker delegate you can do this:
if let asset = info[.phAsset] as? PHAsset {
PHImageManager.default().requestImageData(for: asset, options: nil) { (imageData, dataType, orientation, info) in
let url = info?["PHImageFileURLKey"] as? URL
// Pass this URL to your existing code.
}
}
Note that the file may contain depth or disparity information. You can convert between these easily enough, but you may need to check which one you have using CGImageSourceCopyProperties(). Also look out for the new supplementary depth data, kCGImageAuxiliaryDataTypePortraitEffectsMatte, which gives a much higher resolution mask for just the subject person in portrait images and is great for doing greenscreen-style effects.
I can not make a simple select on a SQLite database, via GRDB.swift
Here is my code:
override func viewDidLoad()
{
super.viewDidLoad()
do
{
let dbQueue = try DatabaseQueue(path: "games.sqlite")
try dbQueue.inDatabase
{
db in try db.execute("SELECT * FROM games")
}
}
catch
{
print("Erreur !")
}
}
I do not find in the documentation a simple way to perform a simple select, the doc speaks of model, and I desire just for the moment to make direct queries.
The documentation speaks only for error on my part of model.
Thank you all for your help.
My environment :
Xcode8.2.1
swift3
cocoapods 1.2.0.rc.1
The GRDB documentation says:
Once granted with a database connection, the execute method executes the SQL statements that do not return any database row, such as CREATE TABLE, INSERT, DELETE, ALTER, etc.
So if you want to fetch rows, you picked the wrong method.
To fetch rows, you use a fetching method (which starts with fetch), as documented in the Row Queries documentation chapter, that I encourage you to read.
For example:
let rows = try dbQueue.inDatabase { db in
try Row.fetchAll(db, "SELECT * FROM games")
}
// Now we're back to the main thread: use rows:
for row in rows {
let name: String = row.value(named: "name")
let year: Int = row.value(named: "year")
print(name)
print(year)
}
That's what it gives "without going through models".
Yet models are still handy:
let games = try dbQueue.inDatabase { db -> [Game] in
let rows = try Row.fetchAll(db, "SELECT * FROM games")
return rows.map { row in
Game(
name: row.value(named: "name"),
year: row.value(named: "year"))
}
}
// Now we're back to the main thread: use games
for game in games {
print(game.name)
print(game.year)
}
If you have your Game type (struct or class) adopt the RowConvertible protocol, you can even write:
let games = try dbQueue.inDatabase { db in
try Game.fetchAll(db, "SELECT * FROM games")
}
And if Game also adopts TableMapping protocol, you get:
let games = try dbQueue.inDatabase { try Game.fetchAll($0) }
See Record for the documentation of those protocols.