How to fetch Coredata List in IntentHandler class (WidgetKit-SwiftUI)? - swiftui

I am not able to fetch data in my IntentHandler class. My Goal is select Todo from the CoreData list and display it in Widget.
I am trying to display a list from CoreData in Widget Intent and I am expecting to resolve this issue.
extension IntentHandler : ConfigurationIntentHandling {
func provideTodoNameOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: #escaping (INObjectCollection<TodoData>?, Error?) -> Void) {
var arrTodoData = [TodoData]()
coreDH.getAllTodos().forEach { todos in
let todoIntent = TodoData(identifier: todos.id?.uuidString, display: todos.name ?? "")
arrTodoData.append(todoIntent)
}
let collection = INObjectCollection(items: arrTodoData)
completion(collection, nil)
}
}
class IntentHandler: INExtension{
let coreDH = CoreDataHandler.shared
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
}

If your IntentHandler doesn't get called:
Ensure your Widget uses IntentConfiguration not StaticConfiguration
Ensure your time line provider conforms to IntentTimelineProvider
Run the app scheme, then run the intent scheme, you should be able to debug and breakpoints would work.
Widgets Code-along, part 3: Advancing timelines (5:31)
Add Configuration and Intelligence to Your Widgets
If you need to share data between your app and extension:
App's data is sandboxed and is not accessible by extension
Configure App Groups and you could create core data file in the shared container to be able to access in your extension
If App and Extension use exactly the same data you could use the same sqlite file in the shared container (both app and extension would have access to it)
If App and Extension use different data and there is only a small portion that is common, then use History Tracking
Configuring App Groups
Consuming Relevant Store Changes

Related

What is the difference between `FileDocument` and `ReferenceFileDocument` for `DocumentGroups` in SwiftUI?

I'm trying to setup a DocumentGroup in my app, but there's no examples out there yet ReferenceFileDocument is for. I know what a FileDocument is, but how are ReferenceFileDocuments different.
In the docs all it says is:
Conformance to ReferenceFileDocument is expected to be thread-safe,
and deserialization and serialization will be done on a background
thread.
There's a hint in the name: ReferenceFileDocument is a document that's a reference type (ie, a class). FileDocument is for a struct based document.
This has an effect on how documents are saved because SwiftUI can just make a copy of the reference type and save it without worrying about you coming along and modifying it during the save, since it's a value type or tree of value types.
With ReferenceFileDocument, there also doesn't seem to be a clear way for the SwiftUI to know when to save, so it depends on you telling it. There's no direct "doc is dirty, save it now" method, so the way you inform SwiftUI that you've done something that requires saving is through the undo manager.
You also need to provide a snapshot method to return a copy of the document that's safe for it to save.
final class QuizDocument: ReferenceFileDocument, ObservableObject {
#Published var quiz: QuizTemplate
init(quiz: QuizTemplate) {
self.quiz = quiz
}
static var readableContentTypes: [UTType] { [.exampleText] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let quiz = try? JSONDecoder().decode(QuizTemplate.self, from: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.quiz = quiz
}
// Produce a snapshot suitable for saving. Copy any nested references so they don't
// change while the save is in progress.
func snapshot(contentType: UTType) throws -> QuizTemplate {
return self.quiz
}
// Save the snapshot
func fileWrapper(snapshot: QuizTemplate, configuration: WriteConfiguration) throws -> FileWrapper {
let data = try JSONEncoder().encode(quiz)
return .init(regularFileWithContents: data)
}
}
ReferenceFileDocument is a document type that will auto-save in the background. It is notified of changes via the UndoManager, so in order to use it you must also make your document undo-able.
The only mention I see of it in the docs is here.
Here is a working example.

Pass big data like images to widget? [duplicate]

#main
struct ClockWidgetExt: Widget {
private let kind: String = "ClockWidgetExt"
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
HomeTestView()
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
How can I get data from my Main App to the widget?
You can add the AppGroup capability for both your Widget and App (here is a very good explanation how to add it).
UserDefaults
Instead of
UserDefaults.standard
just use the shared UserDefaults for your AppGroup:
UserDefaults(suiteName: <your_app_group>)
Then you can read/write data like explained in this answer.
File Container
With the AppGroup entitlement you get access to the shared File Container:
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: <your_app_group>)!
and access an url like this:
let someFileURL = containerURL.appendingPathComponent("SomeFile.txt")
Then you can use your shared File Container like explained in this answer:
How to read files created by the app by iOS WidgetKit?
CoreData
You can create a shared CoreData container as well:
let storeURL = containerURL.appendingPathComponent("DataModel.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)
let container = NSPersistentContainer(name: "DataModel")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { ... }
Then you can use your shared CoreData Container like explained in this answer:
Fetch data from CoreData for iOS 14 widget
Here is a GitHub repository with different Widget examples including the App Group Widget.
One simple way to do this is by adding both the app and the widget to the same App Group, and then storing and retrieving data from UserDefaults storage located in that App Group's container instead of the default UserDefaults storage for the app.
You can access this shared storage for your App Group by initializing UserDefaults using
UserDefaults(suiteName: "YOUR_APP_GROUP_NAME")
instead of accessing it using
UserDefaults.standard.
This article gives a more thorough description of the details of this process.

Alamofire.request does not map with custom object

I was trying to create a sample app in swift that maps with alamofire with alamofire object mapper. The whole idea of this sample app is to map json response with custom model that I make. But when I try to loop the object list, it says that custom object is not compatible and its a dictionary.
Here is my network layer code
// MARK: retrieve country list
func retrieveCountryList(completion: #escaping (_ result: NSArray) -> Void) {
Alamofire.request("https://restcountries.eu/rest/v2/all").
responseJSON{(response: DataResponse<Any>) in
let result = response.result.value;
let resultsArray = result as! NSArray
completion(resultsArray)
}
}
and model
class Country : Mappable {
var name:String?
var capital:String?
required init(map : Map) {
}
func mapping(map: Map) {
name <- map["name"]
capital <- map["capital"]
}
}
It seems that the response is doesn't directly maps with model. what might be the issue ?
alamofire version 4.3
You first need to install "AlamofireObjectMapper" .
For which you should add into your project by including "pod 'AlamofireObjectMapper'" followed by command "pod install" .
Now in your API call class "import AlamofireObjectMapper" and try typing ".responseObject" you'll get it.

fetching from Coredata in ios app widget extension in ios 10

Im trying to fetch entities from my coredata database of my main app and display them in my apps widget Extention. However the fetch always returns an empty results from my database. Can ayone help me out. Swift 3, ios 10.
I solved it. After creating the App Group, I created a subclass of NSPersistentContainer and override the class method defaultDirectory() to return the shared app group directory. Also override the init(name: String, managedObjectModel model: NSManagedObjectModel). Then in the coredata stack you replace the boilerplate persistentcontainer code with a new instance of the PersistentContainer class created.
import UIKit
import CoreData
class PersistentContainer: NSPersistentContainer{
override class func defaultDirectoryURL() -> URL{
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bundleId.SomeApp")!
}
override init(name: String, managedObjectModel model: NSManagedObjectModel) {
super.init(name: name, managedObjectModel: model)
}
}
Then in the CoreDataStack Code *wherever that maybe, either in the Appdelegate or or its own file. Mine was in its own file named CoredataStack
static var persistentContainer:PersistentContainer = {
let container = PersistentContainer(name: "SomeApp", managedObjectModel: CoreDataStack.managedObjectModel)
container.loadPersistentStores(completionHandler: { (storeDescription:NSPersistentStoreDescription, error:Error?) in
if let error = error as NSError?{
fatalError("UnResolved error \(error), \(error.userInfo)")
}
})
return container
}()
Hope this helps out
Today Extension runs in a different process from its containing app, thus do not share sandbox, UserDefaults, or database tabled. That is why you get an empty set when trying to fetch data saved in container, from the widget.
If you want the containing app and the widget to share data, you should add to your app the capability "App Group" via the iOS developer portal and give it a shared group identifier string.
Next, when you instantiate the FileManager object for the database (or UserDefaults), you must use initWithSuiteName method to, passing in the identifier of the shared group.

Creating mock cloud kit objects for unit testing

Trying to mock CKDatabase but it has no accessible initializer. Is there another way to create a fake CKDatabase object?
This code comes back with the "error: cannot override 'init' which has been marked unavailable"
class MockDatabase : CKDatabase
{
override func saveRecord(record: CKRecord!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecordID!, NSError!) -> Void)!) { }
override func fetchRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteSubscriptionWithID(subscriptionID: String!, completionHandler: ((String!, NSError!) -> Void)!) { }
override func saveSubscription(subscription: CKSubscription!, completionHandler: ((CKSubscription!, NSError!) -> Void)!) {}
}
The cloud kit framework seems like a perfect candidate for unit testing as there are a lot of possible errors that can come back from the server and making sure your app can handle them all consistently , with every iteration would be great. Has anyone found a way around this?
Since CKDatabase init is made unavailable, you will never be able to create an instance of it. So there is no point in creating your MockDatabase because you will never be able to create an instance of that. The only way to do some sort of Mocking is by creating your own DAO class wrapper around the CKDatabase functionality and only call CKDatabase functions using that DAO. Then you could mock that DAO. You could even add extra functionality to that DAO, but then you will not be able to test that.
All the CloudKit classes are #objc (they all derive from NSObject.) That means they use dynamic dispatching. In Objective-C you could mock CKDatabase by implementing a new Obj-C class MockCKDatabase that subclasses NSObject and implements all the public methods of CKDatabase (or at least all the ones you use.) Then you literally just instantiate one and cast the pointer from MockCKDatabase* to CKDatabase*.
I've never done this sort of thing in Swift, but I imagine that if you just did the same steps it should work.
as #Edwin pointed out, CKDatabase init is made unavailable, so you can't make instances of it. Neither can you subclass or use an extension to override CKContainer init (compiler will complain that 'init()' is unavailable). You can't even use a CKContainer with a mock of CKDatabase, since the logic that associates them is hidden within the CloudKit implementation. All that said, I built a framework called MockCloudKitFramework that is a drop-in replacement for a limited (but reasonable) subset of CloudKit capabilities. The MockCloudKitFramework (MCF) implements mock operations for CKContainer and CKDatabase, plus the main subclasses of CKDatabaseOperation.
MCF creates its own protocols (that contain the same signatures as their CloudKit counterparts) and extends both CloudKit and MCF mocks from this set of protocols. It was necessary to create these protocols since CKContainer and CKDatabase both inherit directly from NSObject as their common Protocol. So that IOC would force generic functions to be open to NSObject types - leaving functions wide open for injection of everything that inherits from NSObject.
So using MCF comes down to:
Extending CloudKit and MCF classes from these protocols.
Refactoring your code to use generics - typed on the MCF protocols.
Using IOC to inject the mocks of CKContainer and CKDatabase (MockCKContainer and MockCKDatabase, respectively).
See the MCF docs - examples are provided, along with unit and integration tests.
Using MCF
Lets say that you have a generic class that takes a type based on the CKContainerProtocol protocol (one of the MCF protocols). It contains a method that accepts either a CKFetchRecordsOperation or a MockCKFetchRecordsOperation (the MCF mock of CKFetchRecordsOperation) based on their common MCF protocol, CKFetchRecordsOperationProtocol:
import CloudKit
/// Example Class to handle iCloud related transactions.
class CloudController<T: CKContainerProtocol> {
let cloudContainer: T
let database: T.DatabaseType
init(container: T, databaseScope: CKDatabase.Scope) {
self.cloudContainer = container
self.database = container.database(with: databaseScope)
}
/// Check if a record exists in iCloud.
/// - Parameters:
/// - cKFetchRecordsOperation: the fetch operation
/// - recordId: the record id to locate
/// - completion: closure to execute on caller
/// - Returns: success(true) when record is located, success(false) when record is not found, failure if an error occurred.
func checkCloudRecordExists<O: CKFetchRecordsOperationProtocol> (
cKFetchRecordsOperation: O,
recordId: CKRecord.ID,
_ completion: #escaping (Result<Bool, Error>) -> Void) {
var dbOperation = cKFetchRecordsOperation
dbOperation.recordIDs = [recordId]
var record: CKRecord?
dbOperation.desiredKeys = ["recordID"]
// perRecordResultBlock doesn't get called if the record doesn't exist
dbOperation.perRecordResultBlock = { _, result in
// success iff no partial failure
switch result {
case .success(let r):
record = r
case .failure:
record = nil
}
}
// fetchRecordsResultBlock always gets called when finished processing.
dbOperation.fetchRecordsResultBlock = { result in
// success if no transaction error
switch result {
case .success:
if let _ = record { // record exists and no errors
completion(.success(true))
} else { // record does not exist
completion(.success(false))
}
case .failure(let error): // either transaction or partial failure occurred
completion(.failure(error))
}
}
database.add(dbOperation)
}
}
We can build a test without MCF that validates that the record does (or doesn't) exist, but first we have to:
Populate (or depopulate) the CloudKit test environment with the data to set the test up.
Make sure that we are using the same CKDatabase scope (public, private, or shared) as where the data is.
Hope that there are no network hiccups or data transfer errors that will inject unanticipated errors or interruptions.
But still, we can't test what the error conditions might be since we have no control over them.
MCF allows us to test all of this, using a local mock instance of CKDatabase (MockCKDatabase) that we can manipulate directly or through CloudKit modification operations like (Mock)CKModifyRecordsOperation. MockCKContainer is used to access the MockCKDatabase instance of the appropriate scope using the same accessors as CKContainer does. Here is an example of a test case setup:
import XCTest
import CloudKit
#testable import OurProject // required for access to CloudController
import MockCloudKitFramework // import MCF only into test environment
class MockCloudKitTestProjectIntegrationTest: XCTestCase {
var cloudContainer: MockCKContainer!
var cloudDatabase: MockCKDatabase!
// NOTE that CloudController class is a Generic and that both CKContainer and MockCKContainer extend CKContainerProtocol
var cloudController: CloudController<MockCKContainer>!
// called before each test
override func setUpWithError() throws {
try? super.setUpWithError()
// reset state for each test. Each test must set up exactly the state they require.
MockCKContainer.resetContainer()
// get the default container from MCF
cloudContainer = MockCKContainer.default()
// get the MockCKDatabase of CKDatabase.Scope of private
cloudDatabase = cloudContainer.privateCloudDatabase
// we pass in a MockCKContainer to the generic function expecting a class that extends MCF CKContainerProtocol
cloudController = CloudController(container: cloudContainer, databaseScope: .private)
}
}
Now we can test the checkCloudRecordExists() method by adding the record that we expect to fetch directly to MockCKDatabase and calling the function, passing in our mock CKFetchRecordsOperation:
func test_checkCloudRecordExists_success() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord() // function that makes a CKRecord
// add the record to MockCKDatabase
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// pass in our mock CKFetchRecordsOperation
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { exists in
XCTAssertTrue(exists)
expect.fulfill()
}
waitForExpectations(timeout: 1)
}
If we wanted to test an error condition from the call, we just need to add an error using the setError property of MockCKDatabaseOperation (or any of its subclasses). Now the test will fail, even though the record exists. This simulates an internal error that was the result of a CloudKit transaction:
// call checkCloudRecordExists() when the record is present but error is set
func test_checkCloudRecordExists_error() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
let nsErr = createNSError(with: CKError.Code.internalError)
operation.setError = nsErr
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
expect.fulfill()
case .failure(let error):
XCTAssertEqual(error.createCKError().code.rawValue, nsErr.code)
expect.fulfill()
}
}
waitForExpectations(timeout: 1)
}
We can even test our function logic for partial failures to make sure that we handle the scenario of when a record might be found but some CKError occurred so we cannot be sure. All we need to do is set the setRecordErrors property on the operation to the set of record ids that should fail (MCF picks a random CKError to fail with):
// test for partial failures
func test_checkCloudRecordExists_partial_failure() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
operation.setRecordErrors = [record.recordID]
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
case .failure(let error):
let ckError = error.createCKError()
XCTAssertEqual(ckError.code.rawValue,
CKError.partialFailure.rawValue,
"The transaction error should always be set to CKError.partialFailure when record errors occur")
if let partialErr: NSDictionary = error.createCKError().getPartialErrors() {
let ckErr = partialErr.allValues.first as? CKError
XCTAssertEqual("CKErrorDomain", ckErr?.toNSError().domain)
expect.fulfill()
}
}
}
waitForExpectations(timeout: 1)
}