App to SchoolWork interface not working?

[EDIT: I originally thought this issue was about the Obj-C interface]


The first app I'm testing with ClassKit is Objective-C.


So far I have all of the activities showing up as expected within the SchoolWork app. I can create a task, add an activity and post it to the student as expected.


I can then log in as the student (using settings to switch to student mode), and restart the SchoolWork app. I see the task and can initiate it just fine. My app is launched, and the requested task is started.


From my logs I can see that the activity is started, and that as the student traverses the activity, the progress is increased, and at the end, when the activity is ended, a score, and several other Quantity items are added. They all look fine when traced from my app.


My problem is that even though the changes to the activity are being logged fine, and the act of saving the state produces no errors, the SchoolWork app does not show that the task/activity was started, and none of the progress or quantity items are shown.


When I use the sample GreatPlays app in the same way it works as expected - sometimes.


Before I try to dig further, does anyone have this working in Objective-C? Can you get the results shown within the SchoolWork app?


UPDATE: I've tried any number of combinations; removing all CLSActivityItem objects, reordering them, etc. Then I noted that even activities created for GreatPlays don't always work either, so perhaps it's a more generalised issue with the interface to SchoolWork. I haven't yet found a reproducible pattern.

Accepted Reply

Answering my own question.


I pulled my interface code out into a test app and stepping through it all over again, I discovered that when I began or ended an activity, I was submitting the identifier as received by the AppDelegate method:


- (BOOL) application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler;


Noting in the GreatPlays sample code:


    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

        if userActivity.isClassKitDeepLink,
            let identifierPath = userActivity.contextIdentifierPath {
     
            // The first element of the identifier path is the main app context, which we don't need, so drop it.
            return navigate(to: Array(identifierPath.dropFirst()))
        }

        return false
    }


That comment is really important. If you don't drop the first element, and use that path when you call:


CLSDataStore.shared.mainAppContext.descendant(matchingIdentifierPath: identifierPath)


Then the context won't be found.


So having now changed my code to drop that first element, I'm seeing results in the SchoolWork app which is great.


I'm still seeing errors when ever I try to make calls to


CLSDataStore.shared.save


such as:


2018-05-01 22:53:37.847296+1000 TestClassKit[58240:4714131] startActivityWith: Unable to save: Error Domain=com.apple.ClassKit Code=4 "Saving object not allowed." UserInfo={NSLocalizedDescription=Saving object not allowed., CLSErrorObjectKey=<CLSActivity: 0x1016d9b90> (objectID: 884C8BC1-FF09-4A1A-B386-27B5B3EC9BC8) (dateCreated: 1/5/18, 10:53 pm) (dateLastModified: 1/5/18, 10:53 pm) (progress: 0.00) (duration: 0.00) (primary-item-id: (null))}

I haven't worked out what these are about yet. The errors aren't entirely helpful or informative.

Replies

So, now I'm wondering about the call to:


            CLSDataStore.shared.save { error in
                guard error == nil else { print("***Save error: \(error!.localizedDescription)"); return }
            }


Does this call need to be made on the main thread?


The doc states:


Important

Make no assumptions about the thread on which the completion block is called.


So I'm left wondering if there are requirements about where and when we should call this. At the moment, I'm calling this in my endActivity: method but there's no indication that this is working.


I'll test this by wrapping it:


                        dispatch_async(dispatch_get_main_queue(),
                                      ^{
                                          [[CLSDataStore shared] saveWithCompletion:^(NSError * _Nullable error) {
                                              if (error != nil) {
                                                  NSLog(@"endActivity: Unable to save: %@", error);
                                              }
                                          }];
                                      });


when I next have access to an iPad with SchoolWork installed (side note: it would have been really helpful if we'd been able to use the simulator for this).

Answering my own question.


I pulled my interface code out into a test app and stepping through it all over again, I discovered that when I began or ended an activity, I was submitting the identifier as received by the AppDelegate method:


- (BOOL) application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler;


Noting in the GreatPlays sample code:


    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

        if userActivity.isClassKitDeepLink,
            let identifierPath = userActivity.contextIdentifierPath {
     
            // The first element of the identifier path is the main app context, which we don't need, so drop it.
            return navigate(to: Array(identifierPath.dropFirst()))
        }

        return false
    }


That comment is really important. If you don't drop the first element, and use that path when you call:


CLSDataStore.shared.mainAppContext.descendant(matchingIdentifierPath: identifierPath)


Then the context won't be found.


So having now changed my code to drop that first element, I'm seeing results in the SchoolWork app which is great.


I'm still seeing errors when ever I try to make calls to


CLSDataStore.shared.save


such as:


2018-05-01 22:53:37.847296+1000 TestClassKit[58240:4714131] startActivityWith: Unable to save: Error Domain=com.apple.ClassKit Code=4 "Saving object not allowed." UserInfo={NSLocalizedDescription=Saving object not allowed., CLSErrorObjectKey=<CLSActivity: 0x1016d9b90> (objectID: 884C8BC1-FF09-4A1A-B386-27B5B3EC9BC8) (dateCreated: 1/5/18, 10:53 pm) (dateLastModified: 1/5/18, 10:53 pm) (progress: 0.00) (duration: 0.00) (primary-item-id: (null))}

I haven't worked out what these are about yet. The errors aren't entirely helpful or informative.