How to use #FetchRequest data in Provider struct in iOS 14 Widget [duplicate] - swiftui

I want to display data fetched from Core Data in a widget. But #FetchRequest doesn’t work on widgets.
As I understand, we have to create an app group and make a shared persistent container.
What I want to know is how to read (fetch) data on widgets from that shared persistent container or simply, how to display data fetched from Core Data in widgets.

First you need to create an AppGroup which will be used to create a Core Data Persistent Container (here is a good explanation how to do it)
Then you need to create your own CoreData stack (an example can be found when you create a new empty project with CoreData enabled).
Accessing Core Data Stack in MVVM application
Assuming you have already created your Core Data model (here called DataModel), you now need to set the container url to your custom shared container location:
Share data between main App and Widget in SwiftUI for iOS 14
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: <your_app_group>)!
let storeURL = containerURL.appendingPathComponent("DataModel.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)
let container = NSPersistentContainer(name: "DataModel")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { ... }
Now you can get the managedObjectContext from your shared Persistent Container:
let moc = CoreDataStack.shared.managedObjectContext
and perform a fetch request with it (more information here)
let predicate = NSPredicate(format: "attribute1 == %#", "test")
let request = NSFetchRequest<SomeItem>(entityName: "SomeItem")
let result = try moc.fetch(request)
Apart from all the links above I recommend you also read this tutorial about Core Data:
Core Data with SwiftUI Tutorial: Getting Started
Here is a GitHub repository with different Widget examples including the Core Data Widget.

For people who did all the work above, and finally can get the connection to your Core Data (e.g. you can get the count of request), but can't fetch the request, is mostly because that the entity you're fetching contains transformable type, and for some reason this error occurred: Cannot decode object of class, try fix this.

Related

How do I fetch HomeKit values for usage in iOS 14 Widgets?

I am writing a HomeKit app that successfully shows live data from my supported accessories in-app. I can read single values (HMCharacteristic.readValue) or use notifications to stay updated (HMCharacteristic.enableNotification).
Now I want to implement Widgets that show this data on the user's Home Screen. This consists of four steps:
A dynamic Intent fetches all the registered (and supported) Accessories from the HMHomeManager and enables the user to select one of them to be shown on the Widget.
Inside the IntentTimelineProvider's getTimeline function I can then again use the HMHomeManager to retrieve the Accessory I want to display on the Widget (based on the Accessory's UUID which is stored inside the getTimeline's configuration parameter - the Intent).
Still inside the getTimeline function I can choose the Services and Characteristics I need for displaying the Accessory's Widget from the HMHomeManager.
Up until here everything works fine.
However, when I try to read the values from the Characteristics I chose before using HMCharacteristic.readValue, the callback contains an error stating
Error Domain=HMErrorDomain Code=80 "Missing entitlement for API."
The Widget's Info.plist contains the 'Privacy - HomeKit Usage Description' field and the Target has the HomeKit capability.
After some research I came up with the following theory: Obviously the whole WidgetKit API runs my code in background. And it seems like HomeKit does not allow access from a background context. Well, it does allow access to Homes/Services/Characteristics, but it does not allow reading or writing on Characteristics (I guess to make sure App developers use HomeKit Automations and don't try to implement custom automations that are controlled by some background process of their app running on the iPhone).
My (simplified) getTimeline code:
func getTimeline(for configuration: SelectAccessoryIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
// id stores the uuid of the accessory that was chosen by the user using the dynamic Intent
if let id = configuration.accessory?.identifier {
// Step 2.: fetch the accessory
// hm is a HMHomeManager
let hm = HomeStore.shared.homeManager
// take a short nap until the connection to the local HomeKit instance is established (otherwise hm.homes will create an empty array on first call)
sleep(1)
let accessories = hm.homes.flatMap({ h in h.accessories })
if let a = accessories.filter({ a in a.uniqueIdentifier.uuidString == id }).first {
// a holds our HMAccessory
// Step 3.: select the characteristic I want
// obviously the real code chooses a specific characteristic
let s: HMService = a.services.first!
let c: HMCharacteristic = s.characteristics.first!
// Step 4.: read the characteristic's value
c.readValue(completionHandler: {err in
if let error = err {
print(error)
} else {
print(c.value ?? "nil")
}
// complete with timeline
completion(Timeline(entries: [RenderAccessoryEntry(date: Date(), configuration: configuration, value: c.value)], policy: .atEnd))
})
}
}
}
}
My questions:
First: Is my theory correct?
If so: What can I do? Are there any entitlements that allow me to access HomeKit in background or similar? Do I need to perform the readValue call elsewhere? Or is it just impossible to use the HomeKit API with WidgetKit with the current versions of HomeKit/WidgetKit/iOS and best I can do is hope they introduce this capability at some point in the future?
If not: What am I missing?

How (and when) do I use iCloud's encodeSystemFields method on CKRecord?

encodeSystemFields is supposed to be used when I keep records locally, in a database.
Once I export that data, must I do anything special when de-serializing it?
What scenarios should I act upon information in that data?
As a variation (and if not covered in the previous question), what does this information help me guard against? (data corruption I assume)
encodeSystemFields is useful to avoid having to fetch a CKRecord from CloudKit again to update it (barring record conflicts).
The idea is:
When you are storing the data for a record retrieved from CloudKit (for example, retrieved via CKFetchRecordZoneChangesOperation to sync record changes to a local store):
1.) Archive the CKRecord to NSData:
let record = ...
// archive CKRecord to NSData
let archivedData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: archivedData)
archiver.requiresSecureCoding = true
record.encodeSystemFieldsWithCoder(with: archiver)
archiver.finishEncoding()
2.) Store the archivedData locally (for example, in your database) associated with your local record.
When you want to save changes made to your local record back to CloudKit:
1.) Unarchive the CKRecord from the NSData you stored:
let archivedData = ... // TODO: retrieved from your local store
// unarchive CKRecord from NSData
let unarchiver = NSKeyedUnarchiver(forReadingWithData: archivedData)
unarchiver.requiresSecureCoding = true
let record = CKRecord(coder: unarchiver)
2.) Use that unarchived record as the base for your changes. (i.e. set the changed values on it)
record["City"] = "newCity"
3.) Save the record(s) to CloudKit, via CKModifyRecordsOperation.
Why?
From Apple:
Storing Records Locally
If you store records in a local database, use the encodeSystemFields(with:) method to encode and store the record’s metadata. The metadata contains the record ID and change tag which is needed later to sync records in a local database with those stored by CloudKit.
When you save changes to a CKRecord in CloudKit, you need to save the changes to the server's record.
You can't just create a new CKRecord with the same recordID, set the values on it, and save it. If you do, you'll receive a "Server Record Changed" error - which, in this case, is because the existing server record contains metadata that your local record (created from scratch) is missing.
So you have two options to solve this:
Request the CKRecord from CloudKit (using the recordID), make changes to that CKRecord, then save it back to CloudKit.
Use encodeSystemFields, and store the metadata locally, unarchiving it to create a "base" CKRecord that has all the appropriate metadata for saving changes to said CKRecord back to CloudKit.
#2 saves you network round-trips*.
*Assuming another device hasn't modified the record in the meantime - which is also what this data helps you guard against. If another device modifies the record between the time you last retrieved it and the time you try to save it, CloudKit will (by default) reject your record save attempt with "Server Record Changed". This is your clue to perform conflict resolution in the way that is appropriate for your app and data model. (Often, by fetching the new server record from CloudKit and re-applying appropriate value changes to that CKRecord before attempting the save again.)
NOTE: Any time you save/retrieve an updated CKRecord to/from CloudKit, you must remember to update your locally-stored archived CKRecord.
As of iOS 15 / Swift 5.5 this extension might be helpful:
public extension CKRecord {
var systemFieldsData: Data {
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
encodeSystemFields(with: archiver)
archiver.finishEncoding()
return archiver.encodedData
}
convenience init?(systemFieldsData: Data) {
guard let una = try? NSKeyedUnarchiver(forReadingFrom: systemFieldsData) else {
return nil
}
self.init(coder: una)
}
}

EmberJS client side record management (ember-data)

I have just started trying to use ember-data. I have an ember app for which I need to produce all the data on the client side and then save it all at once. So my object graph has a "Project" as the root object, then a project can have many "Sections" and then each section can have many "Items".
I am up to the stage where I am trying to create Item records on the client side and add them to the correct Section. When I am ready to save the data I just want to use project.save() and have it go and save the object graph instead of saving every time the model changes.
I am trying to look up the section to place the items in by name using store.filter({name:"section1"}) but ember keeps trying to go to the server to look them up. I see this in the console: GET http://localhost:4200/sections?name=Section1 404 (Not Found).
This is what I am trying to do:
store.filter('section', {name:'Section1'}, function(section) {
return section;
}).then(function(section)
{
var record;
//create a record
section.pushObject(record);
});
You are doing server side filtering and you want client side filtering.
Please read this article carefully.
In short, you should do
store.filter('section', function(section) {
return section.get('name') == 'Section1';
});

Flex 4 component is created before dataprovider gets data

I have a custom component (List) which gets the dataprovider from external xml which calls a service again.Dataprovider is set to custom list by ID.
For the first time when screen loads the list is generated with data but later after if I refresh the screen the list is loaded with empty because list is getting created before dataprovider gets values from xml and service.
Every time if I run in debug mode I will get the list generated as I will wait till dataprovider gets data..but if I run in normal mode I could see empty list some times.
You can try Binding the Dataprovider to the list. So even if it is updated late it will automatically update the list.
You can use metadata tag something like:
[Bindable]
private var arrayCollection:ArrayCollection;
and update the 'arrayCollection' when you get data dynamically.
Hope it helps.
The data binding could be the solution for your problem.
Other way could be set the dataprovider of the list when the service gets the result
- Call the service
- The service obtains the data
- list.dataProvider = result of the service
Anyway, data binding seems the best solution
[Bindable]
private var arrayCollection:ArrayCollection;
<s:List dataProvider="{arrayCollection}" ....

Flash Builder (Mobile) - Dynamic Web Service URL

For my Flash Builder 4.6 Project I have a http service defined which looks at a url from our website.
What I'd like to be able to do though is to change the web service url on the fly within the app. i.e. using the existing url as default but having an admin/settings screen to change where the web service points (either stored in our sqlite database or in local memory).
This would be so that we could allow our customers to host their own version of the website/database but still be able to use/download the app through the app stores.
Has anyone had any experience with doing this?
EDIT: Adding some more details after the comments below.
When I created the HTTP Service through the FlashBuilder wizard it creates two web service classes a super class and a sub class which inherits from the super class. All of the code that the wizard populates goes into the super class.
I can assume that the code I need to put in would be in the sub class. But I do not know which function I'd put it in or how.
Below is a sample of the Super's constructor:
// initialize service control
_serviceControl = new mx.rpc.http.HTTPMultiService("websitehere");
var operations:Array = new Array();
var operation:mx.rpc.http.Operation;
var argsArray:Array;
operation = new mx.rpc.http.Operation(null, "loginRequest");
operation.url = "login.php";
operation.method = "GET";
argsArray = new Array("un","pw");
operation.argumentNames = argsArray;
operation.serializationFilter = serializer0;
operation.properties = new Object();
operation.properties["xPath"] = "/";
operation.contentType = "application/x-www-form-urlencoded";
operation.resultType = valueObjects.Data;
operations.push(operation);
_serviceControl.operationList = operations;
I'm not sure what property of the _serviceControl variable I would need to alter.
Also when I search for my website in my code it brings back a .fml file inside a .model directory which seems to get auto refreshed if I change the service url through the wizard. Would this not cause an issue?
I then have the challenge of accessing the user defined url. Within the app we use an sqlite database to store data but I think it would probably be better to use a 'SharedObject' which we also use to know what account they are logged into. How reliable is this? I assume I would be able to access this via the Service?
Though the awkward thing is that we were planning to have this configurable on a settings screen that would have been accessed after logging in. But to log in it would already need to know which server to point to.
if im reading your question correctly then your main ambition is to dynamically change the url for the services based on a user defined variable.
This is very easy to accomplish and even easier to accomplish if you are using parsley / spicelib.
a few points
dont change the code in the super file, this will get overwritten whenever the service gets refreshed. change everything in its generated sub-Class.
Shared Objects are very good for small quantities of data but should never be used for massive datasets i.e storing a big arraycollection.
Anyway here is how i achieve this.
In the SubClass you can change the constructor function.
Here is how i change my urls based on a config variable but you can just as easily use a SharedObject instead.
public function SubClassConstructor(){
if(CONFIG::DOMAIN_IDENT == "development" || CONFIG::DOMAIN_IDENT == "dev" || CONFIG::DOMAIN_IDENT == "d"){
_serviceControl.endpoint = "http://yoururl1";
}
else if(CONFIG::DOMAIN_IDENT == "production" || CONFIG::DOMAIN_IDENT == "prod" || CONFIG::DOMAIN_IDENT == "p"){
_serviceControl.endpoint = "http://yoururl2";
}
}
Of course this isn't exactly what your looking for but its a working solution, of course you can use Bindings to a Global ApplicationModel or direct reference to the SharedObject i guess you already know how to use the SharedObject.
Ask if you need any further help or guidance.
As cghrmauritius' solution didn't quite work for me, I am posting up the final solution that did work in my situation.
public function subConstructor()
{
super();
_serviceControl.baseURL = "http://url1";
}
Obviously for my final solution I need to implement the shareobject as well but overriding the url was my main priority.