Google Summer of Code 17 minute read

Connecting MIT App Inventor to the Classroom

In the prototype I built, a teacher picks an App Inventor project as the starting point for an assignment in Moodle or Canvas. Each student opens their own copy without signing up, and when they submit, App Inventor saves a frozen copy and reports the submission to the gradebook. The project began with Google Classroom and moved to LTI 1.3 at the start of July. Other learning management systems with the same LTI 1.3 features should work too.


This summer I built LTI 1.3 support for MIT App Inventor. LTI 1.3 is the standard that Moodle, Canvas, Schoology, and many other learning management systems, or LMSs, use to connect outside tools. With it, App Inventor can be added to a course like any other outside tool. I tested it end to end against a real Moodle, and against a local development build of Canvas for every step except the read only review, both running on my laptop. The code is in open pull requests with tests, and none of it has merged into the main repository. At the start of July, my mentor José Dominguez and I decided to move the project from Google Classroom to LTI 1.3.

The App Inventor designer with a message at the top that reads Submitted to your LMS. Your teacher will grade it there.
A student’s own copy of the starting project, right after Submit to LMS. The student did not have to create an App Inventor account.

MIT App Inventor lets people build mobile apps by snapping blocks together. MIT RAISE counts 25 million learners across 200 countries and regions. Many of them use it at school, but App Inventor itself gives a teacher no way to create a class, give an assignment, or record a grade. It also supports no standard that would let a school’s learning management system hand out App Inventor projects, collect them, and grade them. A teacher often hands out the starting project as a file or a link that each student imports, and nothing ties those copies to the assignment. Students then send finished work back as exported project files, attached to emails or uploaded by hand. App Inventor never reports anything to the school’s gradebook.

The goal was to fix this without asking teachers to change how they work. Many schools already manage all three in a learning management system, so the plan was to plug App Inventor into that system.

Starting with Google Classroom

I started with Google Classroom because it is free and widely used, and the main App Inventor server already signs people in with a Google account. In the first weeks I added a Connect Google Classroom item to the Connect menu. It links the signed in user’s Google account through the OAuth authorization code flow with PKCE. The refresh token is stored encrypted with the server’s existing keys. For Drive, the connection asks only for access to the files that App Inventor itself creates or opens. Then I added a Submit to Google Classroom menu item, which exports the project on the server and saves it to the student’s Drive. The student still attaches that file to the assignment by hand. All of it is off by default behind server properties. The work is in pull request #4007, which adds 36 server tests. The connection still asks for a read only Classroom scope that the code never uses, and that scope should be removed before any of this ships.

The code worked, but the Classroom route had several limits. Through the regular Classroom API, a tool can turn in work only for assignments that the same tool created, so an assignment that a teacher creates in Classroom directly is out of reach. The other way in is an integration that runs inside Classroom itself. To reach schools beyond its own domain it has to pass Google’s Marketplace review, and teachers can use it only with a Teaching and Learning or Education Plus license. Every school that runs an App Inventor server would also need its own OAuth client. Google treats the Classroom scopes as sensitive, so each school’s client would have to pass Google’s app verification. It could skip verification if all of its users belong to the school’s own Google Workspace, or if their administrators mark it as trusted. Even then, it would work only with Google Classroom. Moodle, Canvas, and Schoology would each still need a separate integration.

The move to LTI 1.3

LTI 1.3 is maintained by 1EdTech and supported by Moodle, Canvas, Schoology, Blackboard, and Brightspace. In LTI terms the LMS is called the platform, and App Inventor is the tool. Both sign their messages with private keys and share only the public keys that verify them, so unlike the older LTI 1.1 there is no shared secret. Each launch also carries a nonce that the tool accepts only once. With one implementation, any platform that follows the standard should be able to launch students into App Inventor and receive their submissions.

Google Classroom cannot launch LTI tools, so the Classroom work from the first weeks stays in its draft pull request, where it could be a starting point if Classroom support is taken up again.

A Deep Linking request, which is how a teacher picks content for an assignment, does not say which assignment link the content is for. I had planned to store the chosen template under that link, and without it I could not see how to keep a whole class on the same template. What I built instead freezes a copy of the template the moment the teacher picks it, and the first student who opens the assignment ties that copy to the assignment’s link. Every later student gets the same template, and edits the teacher makes to the original project afterwards do not reach the assignment.

An App Inventor page inside Moodle titled Choose a template for this assignment, with two projects to choose from and a button that reads Use this as the assignment template.
Picking the starting project in Moodle. This page comes from App Inventor, and Moodle shows it inside its Select content dialog. The local Canvas showed the same page in its own dialog.

What got built

Launch
An LTI 1.3 tool inside the App Inventor server, which is Java on Google App Engine. It handles OIDC login initiation and checks the platform’s signed launch token against the platform’s published keys. The checks cover the signature, the issuer, the audience and authorized party, the protocol version, the message type, the deployment id when one is configured, expiry, and a nonce that is consumed once in a datastore transaction. The tool also publishes its own keys at a JWKS endpoint so the platform can verify the tool’s messages.
Accounts
Students never sign up or set a password. The launch creates an App Inventor account automatically, in a namespace that no normal login can produce. The account name is a hash of the platform’s issuer and the user’s id on that platform, each prefixed with its length, so two different identities can never give the hash the same input.
Projects
Each student gets one project per assignment, apart from a rare duplicate when they open a new assignment twice at the same moment. The first launch copies the template into the student’s account and names the copy after the assignment. Later launches find the same project again, even after the teacher renames the assignment in the LMS or the server restarts, because the link between student, assignment, and project is stored in the datastore. The short lived state of a launch that is still in progress is kept in memory, which is why this first version runs on a single server instance.
Templates
The teacher’s own launch also creates an App Inventor account, separate from any account they use outside the LMS, and the Deep Linking picker lists the projects in it. The tool stores the chosen project as the frozen template described above, in a reserved account that belongs to the tool rather than to a student or teacher.
Submitting
Submit to LMS sits in the Projects menu and appears only in assignment projects. Choosing it makes App Inventor wait until every change from the same browser window has been saved on the server, then freeze a copy of the project in a reserved account. The platform hears about the submission only after that copy is stored and its record is read back, so the gradebook is never told about a submission before its frozen copy exists.
Gradebook
The tool posts to the platform’s Assignment and Grade Services score endpoint with activity progress Submitted, grading progress PendingManual, and no score value. In the standard, that means the work arrived and waits for a manual grade. Canvas lists the work as needing grading, while Moodle stores a blank grade. In both, the teacher enters the grade in the LMS. If a student submits again after being graded, Canvas clears that grade, because the message carries no score, while Moodle keeps a grade the teacher entered in its grader report.
Review
The tool answers the Submission Review message with a read only view for the teacher. It first checks that the student being reviewed owns both the recorded submission and the project they opened from the assignment link, and that the reserved account named in the record holds the frozen copy. Only then does it open that copy read only. It never opens the student’s live project, because the account that holds it also holds the student’s work for every other course in that LMS.
Registration
Dynamic Registration sits behind a flag and is meant to let an administrator register App Inventor with Moodle by pasting the tool’s registration address. A platform registered this way is stored disabled, and since there is no screen to enable it yet, whoever runs the App Inventor server turns it on in the datastore. During the summer this was tested only against a mock platform. A real Moodle refuses it when the App Inventor server’s address includes a port number, because the tool registers its domain without the port while Moodle compares that domain with the launch address, port included. The reference page I wrote for the integration still says this path was verified against Moodle, and that sentence needs correcting.
Client tests
The client code that tracks which saves are finished and which are still on their way to the server lives in a small class with no browser dependencies, where plain unit tests can check the rules that protect a submission.
Documentation
The reference page gives the values for registering App Inventor in Moodle and Canvas, the server flags, a local Moodle walkthrough, and the known limitations to weigh before any production use.

How it was tested

I ran the whole loop against a local Moodle in Docker. A teacher creates the assignment and picks a template. A student opens it, works, and submits. The teacher opens the frozen copy and enters a grade, which the student then sees. I ran the same code against Canvas, also running locally, for the launch, the template pick, the submission, and the grade. App Inventor has no separate code path for either platform. The local Canvas was a development build, and the read only review is the one step it cannot run, because Canvas does not send the Submission Review message.

Moodle’s grader report with a column named App Inventor FinalTemplate. The first student has a grade of 88.00 and the second student has none yet.
The teacher’s grade in Moodle’s grader report. The App Inventor assignment is an ordinary gradebook column.
Messages in one assignment, from template pick to review A sequence diagram. The learning management system sends a Deep Linking request to the App Inventor server, which returns a signed link to the frozen template. At launch time the platform sends an OIDC login and then the signed launch, and the server opens the editor on the student copy. The student submits, and the server stores a frozen copy and checks its record before telling the platform the work was submitted. For review the platform sends a Submission Review message and the server opens the frozen copy read only. LMS App Inventor Editor Assign Deep Linking request a signed link to the frozen template Work OIDC login, then the signed launch session and the student copy Submit to LMS the frozen copy is stored and checked Submitted, PendingManual, no score Review Submission Review the frozen copy, read only Moodle runs every step. The local Canvas runs all of them except the review.
The main messages between the LMS, the App Inventor server, and the App Inventor editor in the browser, in the order they happen. Green arrows start at the App Inventor server. On a narrow screen the diagram scrolls sideways.

Because the build can report success while a test suite fails, the test counts on this page come from the JUnit XML reports rather than the exit code. For each refusal the submit endpoint can send back, and for each ownership check that decides which frozen copy a review may open, I deleted the check in a scratch copy of the code and confirmed that a test failed, and I did the same for the rules in the class that tracks saves. No test sends a whole launch through the server yet, so several checks made only there have no test of their own, such as the issuer, the protocol version, the message type, the deployment id, and the rule that only an instructor can open a review. A few defensive checks made while the frozen copy is stored have no test yet either.

Fixes after a demo rehearsal

After the final Google Summer of Code submission, I rehearsed a demo in the Moodle gradebook. This time I clicked around the way a teacher might instead of following my own script, and that evening I found two bugs in the review. Fixing them brought me back to a gap that the reference page already listed. The server did not enforce read only mode, and a closer look showed that this was the most serious of the three problems. It came from my review feature reusing an older upstream flag in a new way. The fixes are written and tested but not public yet.

The review link on the column heading

In the grader report, Moodle links to the review from two places. The menu on a single gradebook cell has Grade analysis, and the menu on the column heading has View External tool results. The column link has no student in context. Moodle sends the Submission Review message without naming one, and the tool showed its generic launch error page even though the teacher had done nothing wrong. In Moodle’s source, the part of the message that names the student is left out whenever the user id is zero. Moodle sites already in use will keep sending the message this way, so the fix had to go in the tool. With the fix, the tool explains the problem in plain words and points the teacher to the cell for a single student. Opening anything else would have meant guessing which student’s work to show.

Drag and drop in the read only review

The review opened read only, but the designer still let a teacher drag a new component onto the phone preview. The component never reached the server, and a reload removed it. Until then, though, the screen showed an edit that had not happened. Dragging from the palette and moving a component on the phone preview both go through one shared class, so the refusal went there. A double click or the Enter key adds a component without any drag, which needed a refusal of its own. Both refusals show the same message. Other edits still change only what the browser shows and are not refused yet, such as reordering components in the Components tree, dragging a marker or shape on a map, renaming or deleting a component, typing in a property field, and moving or editing blocks in the Blocks editor. None of them reaches the server, and a reload removes them.

The App Inventor designer in read only mode, with a message at the top that reads This project is open for reading only, so it cannot be changed.
The teacher’s read only view of a submission after the fix. Dragging a component from the palette now shows this message and changes nothing.

Read only on the server

Apart from Submit to LMS, the server did not enforce read only mode. The client honored the read only flag, and the rest of the server relied on that. A comment in the upstream code gives the reason. It says that only privileged users can turn the flag on, and that they are trusted not to turn it off on their client. My review feature gives the flag to an ordinary teacher in a review session. With the fix, the server refuses the project changes such a session asks for, which means creating, copying, or deleting a project, and saving or deleting its files, settings, or media. It also refuses file uploads and any request to delete the account the session runs as, which can be a reserved account that holds one student’s submitted copies of one assignment. Two writes stay open on purpose. Reporting a corrupt file records a diagnosis and changes nothing in the project. Building the app never touches the source, although a build can store a new signing key on the account. A few other account writes, such as accepting the terms of service, are not refused yet either. Each refusal has a test that fails when its guard is removed.

Reviewing José’s pull request

On August 29, after my final Google Summer of Code submission, José opened a draft pull request, #4080, which submits an assignment from App Inventor to an outside system such as a classroom portal. Like my #4063, it saves the project first and waits until the server has answered every one of those saves before it submits. It does this inside the existing save code. He named me as a coauthor.

After I reviewed it, José added a read only check on the server to the new submit call. The review also turned up two older problems in the save code underneath, which counts replies to know when every save has finished. It expected one reply for a whole batch of file saves, a leftover from when the files were sent together. Since then each file has been saved by a separate call that sends its own reply, so anything waiting on the save could start while another save was still in flight. And a settings save that failed, or had nothing to write, sent no reply at all, so the count never reached zero and switching between screens stayed locked. My fix for those two is a small pull request with a single commit, which José merged into his branch on August 31.

Where the code is

Everything linked below is public, and none of it has merged into the main repository. Two pieces, the archive export overload from the Classroom work and the Submit to LMS client code from the design branch, were split out as their own pull requests there. The full integration is kept as one design branch and as seven stacked layers meant to be read in order. The last layer has the same git tree hash as the design branch, so together the layers contain exactly the code on the design branch. The save counting fix merged into the branch behind José’s draft pull request, not into the main repository. Separately from this project, three smaller pull requests of mine have merged into the main repository, #3858, #3874, and #3893.

The public work from the project, and where it stood at the end of August 2026.

WorkWhereState
Archive export overload, a second way to call the project export from server code, first needed by the Drive export mit-cml #4002 Open, ready for review, at 0b9978106
Submit to LMS client code with the class that tracks saves mit-cml #4063 Open as a reference. José’s draft mit-cml #4080, described above, also waits for its saves before it submits
The save counting fix from my review of José’s pull request commit 2955d276a Merged on August 31 into José’s branch for mit-cml #4080, an open draft
Full LTI 1.3 design branch mit-cml #4031 Kept as a draft at 94bce0653. It is the whole design and was never meant to merge as one pull request
The same work in seven layers (storage, plumbing, launch, submit, review, Deep Linking, documentation) #2, #3, #4, #5, #6, #9, #10 Each layer builds from a clean checkout together with the layers below it. All seven are open on my fork, to be read in order. #7 was an earlier draft of the documentation layer, replaced when Deep Linking became a layer of its own
Google Classroom exploration, account connection with PKCE, Drive export, attach by hand mit-cml #4007 Draft. The starting point before the move to LTI 1.3
Reference documentation with the known limitations lti-integration.md On the design branch

At the end of the program, the App Inventor server suite had 225 tests, the shared suite 22, and the client suite 44. Of those, 132 server tests and 20 client tests came with this work, and the rest are upstream tests. With the fixes from the demo rehearsal, the server suite has 242 and the other two are unchanged. All of them pass under JDK 17, the version that the project’s continuous integration uses. Those fixes are not pushed yet, so the reference page linked above still lists server side enforcement as future work.

What is left

The reference page ends with a numbered list of the known limitations, and each item names the feature it affects. Server side enforcement of the read only review was on that list at the end of the program and is covered above. Four larger items remain. The state of launches in progress has to move out of memory so the tool can run on more than one server instance, and that state should be bound to the browser that started the login. The tool should also support several registrations and several deployments under one issuer, and during Dynamic Registration it should send the registration token the way Canvas’s documentation asks. None of these block the local development setup the reference page describes. The page names binding the launch state to the browser as the main item to add before a pilot with real students.

What I would do again

One habit I would keep is reading the comments in upstream code before reusing it. The normal project export leaves out the Yail files, which hold the intermediate code App Inventor generates for building the app. A comment there says the Firebase component can leave secrets in them. Assignment copies cross account boundaries, so copying everything would have handed a teacher’s secrets to the whole class. I made the copy service itself leave the Yail files out whenever a copy goes to another account, which protects every caller of that service. A private Firebase token saved in the screen file still travels with the copy, and the reference page lists that as a limitation.

For my submit code, putting the save bookkeeping in its own small class let me test it. The client save logic lived in code that plain unit tests cannot run, because it depends on the browser and on GWT deferred binding. The few lines that connect the new class to the editor still need a GWT client, and the test file says they were checked by reading them and by the end to end run.

Where it stands

The same code worked with a local Moodle, and with a local development build of Canvas for every step except the read only review, because Canvas does not send the Submission Review message. It is still a prototype, with its known limitations listed on the reference page. No teacher or student has used it, and every run described here was my own. The pull requests, the design branch, and the reference page stay public so that the App Inventor team or anyone else can pick up any part of it.

Acknowledgments

Thank you to my mentor, José Dominguez, who supported the move to LTI 1.3 and guided the design of the frozen copies and the read only review. He gave me careful feedback all summer and answered every question, including the small ones. I learned a lot from reading his commits, and I am grateful that he named me as a coauthor on his pull request and merged my fix into it. Thank you as well to the MIT App Inventor team and community for welcoming a newcomer and answering my questions.

All the screenshots on this page come from a local Moodle test course. The names in them belong to test accounts.