Transcript
[ Music ]
>> 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.