The ClassKit framework helps you surface your app's valuable educational content for inclusion in a teacher's classroom curriculum. Get an overview of the ClassKit integration workflow, debugging instructor and student roles with the Schoolwork app, and new features designed to make publishing to ClassKit easier than ever.
Hello, welcome to what's new in ClassKit. My name is John Calhoun. I'm an iOS Engineer on the ClassKit and Schoolwork Team.
In case you are new to ClassKit, I'll begin with an introduction to give you some context for what follows. And what follows are the new features for ClassKit as well as a brief discussion of best coding practices that you might want to consider.
Let's begin with what ClassKit is.
ClassKit is a framework that Apple introduced in iOS 11.3.
It's a core part of Apple's education ecosystem. So if you consider your app to also target education, then you should become familiar with the ClassKit framework and what it can do for your app.
The whole purpose of ClassKit is to allow your app to share a student's progress with a teacher. I'll discuss what this means in more detail in a bit.
And because student data is so important, ClassKit exists to ensure that the data remains secure and is only accessible to specific users like the teachers who have been granted privileges to access that data. But let's move on to an example.
I'm going to use an imaginary application as an example today. This will be a simple app that introduces the user to writing code. It contains a number of sections that the student works through.
There are exercises and quizzes along the way that check to see how well the student is understanding the material. So it already has the notion of activities and it will be nice to share the student's progress with a teacher. In short, it's a good candidate for ClassKit.
Here's the student using the app.
And somehow the app would like to share the student's progress with the teacher and do so in a way that protects the student's privacy.
This is the role that ClassKit facilitates. ClassKit insures that the student data stored on the device, sent to the Cloud, and stored in the Cloud is always secure. And ClassKit insures that access to this data is available only to users with the right privileges. For example, the student's teacher.
So how does a teacher or the student for that matter, see their progress in a ClassKit enabled app? Apple provides an application called Schoolwork for iOS.
It's a free app for the iPad. It's been available since ClassKit debuted. You can download it today.
It's been used by schools not only in the United States, but in other countries as well. Teachers use Schoolwork to create assignments called handouts and students receive these handouts from within Schoolwork.
When your application in response to a handout activity records progress, it is within Schoolwork that the teacher and student both can track this progress.
Let's briefly look at Schoolwork so you can understand the workflow.
The first time you launch Schoolwork as a developer, you may see a screen like this. Schoolwork requires a managed Apple ID. These are the type of Apple IDs that schools use in order to assign them to students and teachers. Since most of us don't have a managed Apple ID, we've provided a useful switch in iOS under Settings in the Developer's Section.
You'll need the Developer Install of iOS. This adds additional developer options in the Settings app.
One of the settings is labeled ClassKit API.
Here you can emulate the role of either a user with teacher privileges, or a user with student privileges. Initially it is off.
But select Teacher and when you launch Schoolwork, you'll have the ability to create handouts and assign them to a student. Go back into Settings and switch the ClassKit API role to Student and when you return to Schoolwork, you're now your student persona and you can complete that handout that you just assigned from your teacher persona.
If you're still with me, you can then switch back to the teacher role and see your student persona's progress.
Okay, if you've never used Schoolwork, here's an example of what a handout looks like. The handout recipients displayed at the top of the card are chosen by the teacher. The title and instructions for the handout just below the recipients are created by the teacher as well.
But the activity, the icon representation of what you can see here, could be an activity in your ClassKit enabled app. So how do we get from your code, your app, to a handout? By adopting ClassKit. And there's an entire presentation from WWDC 2018 that covers ClassKit and it's full feature set. But for brevity, I'll give you a primer on one specific and important class in ClassKit, the CLSContext.
If you're not familiar with the whole of ClassKit, understanding at least the CLSContext will give you insight into how exactly your app can appear in Schoolwork and be added to a handout. It is through the use of CLSContexts that you're app can surface the activities that it supports. If your app teaches coding, each lesson might be an individual activity and so each lesson would have a corresponding CLSContext.
And when a student is using your app, those same CLSContexts provide the scaffolding upon which you hang the progress data.
In the example app, I mentioned we can keep track of time spent within a given lesson, or if there is a quiz in the app, we can record the quiz score for that student as progress data. And as I alluded to, your application can create as many CLSContexts as there are activities within your app. As many as makes sense. ClassKit has your organize them in a tree structure with child-parent relationships.
So let's take a look at what is mean by your app's Context Tree. It begins with the one CLSContext that your app doesn't create. This is the main app context. The main app context is created for you by ClassKit when you request it and it is this context that all CLSContext that you will create will descend from, will be children of.
It's the root node of the tree.
So before you do anything at all with ClassKit in your app, you need to request your main app context from ClassKit. You do this with a simple call to the CLSDatastore singleton as shown here. But, before we go on to creating contexts and adding them to our main app context, we need to step back and ask, what contexts or activities does our app want to present? I'll stick with the example of an app that is an introduction on how to code. For our example, I'll imagine that there are three major sections in the app. An Intro section, a section on Variables and Datatypes, and then one on Conditionals. We could consider each of these an activity but ask yourself, if a teacher might want to assign one of these sections as part of a handout. These are large sections and might cover a little too much for a single handout. It might be too large in scope.
But digging deeper into our app, within each section our app has individual lessons.
The lessons are smaller, more bite-sized if you will. It makes more sense to expose these instead at the activities.
Since the sections were rather broad, I'm going to assume no teacher would assign an entire section. So I've removed them from consideration. We're left with these seven lessons.
Oh, additionally in our app, some of the lessons are followed by a quiz to see how well the student understood the lesson. These would make nice activities with which our app can provide meaningful progress data to the teacher like what score the student gets on a quiz.
So where does that leave us? We decide to take advantage of the tree structure that ClassKit allows. The quizzes then can be children activities of the lessons that they cover. Okay, each item here makes sense as an individual, progress trackable activity. They're not too broad. But now let's see how an app can go about representing these activities as CLSContexts.
So I've depicted the activities now in a tree like arrangement. At the top of the tree is not surprisingly, the main app content that ClassKit provides our app. And descending from the main app context are the first level of children. These are the seven lessons that I described. Or a lesson had a quiz, of course we have a child seal as context that represents that quiz.
The labels I'm showing here for each app created CLSContext is the CLSContexts identifier property. The identifier is any string you wish to assign to a CLSContext. It's never seen by the teacher or student so you can use whatever nomenclature makes sense for you. For this example, I came up with something fairly compact but descriptive.
CLSContexts have a title property as well. And this is what the teacher and student will see as I will soon demonstrate. I'm showing only a portion of the context tree here with their titles because of how much visual space they would take up on the slide.
But let's return to the identifiers. From the software side, we deal with the identifier property exclusively.
Once a CLSContext with a give identifier is added to your context tree, it will have an implied identifier path.
ClassKit API often make reference to this path so it is worthwhile briefly explaining it. An identifier path is an array of strings, an array of identifiers of CLSContexts from the root of your context tree to the CLSContext in question. I begin at any child of the main app context and grab it's identifier. Append the identifiers of the children as you descend the tree until you arrive at some intended CLSContext. You have the identifier path now for that CLSContext.
Just to give you a single example, consider the highlighted CLSContext. The identifier path that looks like 4 underscore structs and 4 underscore quiz uniquely refers to this one CLSContext and is this quiz context's identifier path.
Now having examined our app and considered the activities we want to support and their hierarchy, we've arrived at this tree representation of CLSContexts. If you want to see code examples on how to create CLSContexts, there are resources associated with this presentation. To stay within the scope of this presentation, I want to go back briefly to Schoolwork so we can tie this discussion of CLSContexts back into the teacher-student workflow.
You'll recall that in Settings you could select a teacher persona. If you select this and then you launch Schoolwork, there is a plus button in the top right corner of the Dashboard that allows the teacher to create a new handout.
Tapping on that, presents this view. It allows you to create a new handout.
As I mentioned earlier, the teacher determines who to assign the handout to, gives the handout a title, and instructions. But more interesting to us, is the blue plus button, labeled Add Activity.
Tapping on that brings up a list of activities.
There are a number of different options for adding various document types, but we want to pay attention to the top item, Apps.
Tapping that brings up a list of App activities, and there, second on the list, is our application. That our app appears at all indicates that our app has created a CLSContext tree.
The disclosure chevron to the right tells the teacher to drill in for the activities for this app.
And having tapped to drill in, guess what is presented to the teacher? The CLSContexts that are the first descendants of our app's main app context. Here of course we are displaying the titles for each CLSContext, not the identifiers. That's why their human readable. Notice that the last five have a disclosure chevron.
It should surprise you that these are the CLSContexts that had a quiz as well.
For example, if the teacher taps into the third item down, "What is a variable?", there's the quiz. But let's say that the teacher is creating a handout that introduces Swift. The class is not yet for variables so instead, the teacher backs up by tapping the button to return.
The teacher then taps the Select button in the upper right and selects the "What is Swift?" activity. This is how you add an activity to a handout.
So now, we're back on the handout editing view. And at this point, the teacher can tap Post in the upper right corner and the handout will then be sent to the assignees. And as we saw before, a handout card with the activity displayed will appear in Schoolwork on everyone's device.
There is more to learn in ClassKit regarding what happens when the student taps on the activity and your app launches and how you report progress data, but that is outside of the scope of this session. I would refer you instead to the many resources associated with this session in particular, the ClassKit session from WWDC 2018.
But with that somewhat focused background on ClassKit, let's go now to the new features.
The features I'm going to describe were introduced in ClassKit for iOS version 12.2, so these are already available today.
We've added a new context provider extension, added a new function you can call to mark an activity as complete and added a new progress value.
The first feature I'll discuss is the Context Provider Extension. The name might be self-explanatory. It is a new extension you can create for your app that will be called upon to create your CLSContext tree to provide contexts.
But before I explain how it works, I want to show you how you would initially create this extension. Here is the most recent Xcode. If you go to the File menu and select New and then Target, for the iOS application Extensions Templates, there is a new template for ClassKit Context Provider.
When you add this target to your app, it creates a single new file for you. Let me explain how the Context Provider Extension works by showing you boilerplate code that you might implement. I'll show you after how it will likely be used by Schoolwork.
So, I said that a context provider extension adds a single file. It's in fact just a single class. CLSContextProvider is the super class where you must override just one function, updateDescendants of context completion.
ClassKit will call your extensions updateDescendants call. When called, your extension is being asked to add a minimum, update or provide the children's CLSContexts, of the CLSContext being passed in.
You don't return these children in the function, mind you. Your extension is being asked to add or update just a specific part of your app's context tree and to save those changes.
And for reasons I'll explain your code in this function should be as performant as possible.
I'll go back to our earlier example to give you an idea of how this all might work. Imagine your extension being called updateDescendants of context completion being called. And the CLSContext passed in is your app's main app context. How does your code know it's the main app context? The simplest check is to see if the CLSContext passed in has a parent. Only the main app context is parentless. So, your contract is to update your context tree to provide at least the first tier, that is, the immediate children CLSContext of your main app context. If you recall from our example, this would correspond to the CLSContext what is an IDE, what is Swift, what is a variable, et cetera. Those seven top level lessons, or since we've been referring to them by their identifiers, these.
If these children are all there, exist already in your context tree, do not need to be modified, then your code need do nothing at all. You can simply call the completion block and pass no error to indicate all is well.
But the extension exists to allow you to create this portion of your tree in the event that the teacher had never yet visited this portion of your app. Maybe they have never launched your app at all.
I want to add though that your extension is free to populate more of your app's context tree. In fact, could populate the entire tree as long as it can be done quickly. You'll see why this is important in a minute. For this example, I'll stick to the bare minimum and imagine that we only create the immediate descendants of the CLSContext passed in. And so, we create those seven descendants, and our context tree should look like this.
Let me give one more example and I think it will be clearer how this works. Consider now your extension is called again. But this time, the CLSContext with the identifier 3 underscore datatypes is the CLSContext passed to the function. The expectation is that your code will add or update at least the immediate descendants of 3 underscore datatypes.
And you may recall, this was simply a quiz. And so, we have added it here.
What is the purpose of the context provider extension? I essentially laid it out for you, it's to give your app an opportunity to create your CLSContext tree to, in effect, advertise your app's activities, and the teacher will not have needed to have even launched your app.
Of course, the teacher will have had to have downloaded your app. But the very act of downloading will cause your ClassKit extension to be registered with iOS, such as the ClassKit, and more specifically, Schoolwork will be aware of those activities available.
Some of your apps may support a bevy of activities and have a tree that may be broad or deep. To create this entire tree when your app launches might cause a performance issue. So, this extension is designed to allow you to dole out CLSContexts in smaller batches.
Some ClassKit apps require user interaction before they can begin to create their context tree. Unfortunately, the extension is not going to be useful in those cases. The extension is called without an option for your app to display any UI.
Finally, I want to present to you how your Context Provider Extension fits in with the teacher workflow. Recall the example that I gave earlier, where a teacher was creating a handout and was navigating through the activities available.
If your app has a context provider extension, you can expect it to be called before the teacher gets to this screen in Schoolwork. Your extension should have been called with the main app context, so that your app can be presented here as having available activities. And should the teacher begin drilling down into your app's activity hierarchy, you can expect repeated calls to your extension to provide more depth to your CLSContext tree.
Since it is possible that your tree is being created in more or less real time, live as the teacher is drilling in, this is why it is important that your extension code work quickly to create and save the CLSContext being asked for.
I think you can see now how all the pieces of this work together, the extension, your app's tree of activities and Schoolwork.
The next feature new to ClassKit is an API that allows your app to mark an activity as completed. That is, to make it easier for the student to let the teacher know that the activity that was assigned is done. To understand why this is a nice new feature, let me revisit Schoolwork but briefly show you the student side. We've seen a bit about the teacher workflow for assigning activities. What's it like to be on the receiving end? This is the handout card that I showed earlier. It's how the student sees it in Schoolwork.
When they tap on it, they see the handout with the activities, just the one activity in this case. Tapping on the activity takes them to your app. But after completing the activity, the students still has to come back to Schoolwork and tap the complete button.
If your app adopts a new ClassKit API, you can make this last step unnecessary. From within your app, you can mark the activity complete by calling a new API.
The CLSDataStore has a new function called completeAllAssignedActivities matching contextPath. The path is, of course, an identifier path to the CLSContext or activity that the student just completed. If, for example, the student just completed the, what is a variable quiz within this app, we can indicate it is complete by calling completeAllAssignedActivities for the CLSContext with the path 2 underscore vars, 2 underscore quiz. The next time the student returns to Schoolwork, it will indicate the activity complete. Further, the teacher who assigned the handout will also find the activity marked as complete for that student.
If your app adapts this new call, the student's workflow will be much smoother.
Finally, ClassKit and iOS 12.2 adds a new activity item type, a correct/incorrect type.
In my ClassKit overview, I didn't describe the CLS activity class, but for purposes of introducing this new feature, I'll just mention that each CLSContext can have an activity in the form of a CLS activity object. And, a CLS activity can have any number of CLS activity items.
Now, CLSActivityItem is a parent class to a handful of other classes. And I'll call out one, in particular, the CLSBinaryItem. A CLSBinaryItem can only represent progress in one of two states, the flavors of which are shown here.
Maybe the progress you want to report is simply whether the student passed or failed. And CLSBinaryItem has an ENUM to indicate this. Additionally, you can indicate true versus false or yes versus no.
But we heard from developers that felt as though we were missing another binary flavor.
So, ClassKit has defined the correct/incorrect enumeration to describe this new type of binary activity. Consider the quiz example that I mentioned for our sample app. It consists of ten questions. We'll probably not use a binary activity type for the primary activity item, because we want to represent the score as a quantity. So, the teacher sees that the student got 70%, for example.
But as a bonus, our app can add additional activity items. For example, one for each question, and indicate correct or incorrect for those so that the teacher can see which questions the student missed, which ones they got correct. I just offer that as one example of how this type might be used.
So, finally, let's talk about ClassKit coding best practices.
I've already described that it is an error to add to your context tree, a CLSContext whose identifier path is not unique. At first blush, this sounds sort of like an error that's hard to make. But consider the following scenario, your app launches for the very first time, and you dutifully create a CLSContext tree in order to make available your app's activities. A portion of that original tree is shown here.
On your app's second launch, you should not create your CLSContext tree again.
This would be adding CLSContext that have identifier paths that conflict with the existing ones. You should always check that a CLSContext does not exist first in your tree before adding it.
There are a few ways to check to see if a CLSContext is already part of your tree. One way is to call the CLSDataStore function contexts matchingIdentifierPath. And when your completion block is called, a note that the call is asynchronous, an empty array for the CLSContext returned would indicate the context with that path does not yet exist. So, it would be correct to now create it. Or, instead there was a CLSContext function descendant's matchingIdentifierPath that you could call. It also is asynchronous. And like the previous example, if no context is passed to your completion block, then again, it is safe for your app to create and add the new CLSContext. There are going to be numerous places within your app where you're going to have to make one of these checks. And if you adopt the new Context Provider Extension, you're adding even more vectors from which you will need to test whether a CLSContext has already been added to your tree or not.
So I wanted you to be aware of what might be an elegant solution, which is to implement the CLSDataStore Delegate function, createContext forIdentifier, parentContext, parentIdentifierPath. If you make one of the classes in your app, the CLSDataStore Delegate, then when you call one of the previous functions like CLSDataStore context matchingIdentifierPath, this delegate function will only be called if the context has never been created, implementing the delegate sort of bottlenecks then all context creation to just one place in your app.
In my experience, the actual implementation of creating a CLSContext is pretty app specific. So I've left the code empty here. If you are adding ClassKit support for the first time, the 2018 WWDC session on ClassKit or the available sample code is an excellent place to start.
Here's an example of a simple helper function that might exist in your app. This function we'll call beginActivity, can be called to make a specific CLSContext the active context. We pass in only an identifier path.
The function calls the CLSContext query to find the descendant that matches the identifier path passed in. Again, since we have elsewhere in this application set up the CLSDataStore Delegate, we can be sure that if the specified CLSContext was never created, it will be in the delegate function and therefore a CLSContext should be returned from the query.
We make the context return to active, create a new activity for it and start that activity. These series of calls indicates we want to record progress in the form of time spent for a specific CLSContext.
And finally, we call save on the CLSDataStore to initiate these series of calls that we've made.
Likely, you will create plenty of other helper functions like this one, but the context creation code need to live in only one place, in your CLSDataStore Delegate function.
If you are new to ClassKit, I hope this brief introduction has given you a taste of its capabilities. And if you think students and teachers would benefit from using your app in the education realm, you should give consideration to adopting ClassKit. If you are already familiar with ClassKit, I hope you take advantage of the new features we added for 2019. All of these were the result of feedback from developers like yourselves.
Take a look at the links that accompany this presentation. You'll find links to sample code, documentation and to other presentations that take a deeper dive into ClassKit. Thank you.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.