LabFlow
Frontend Design Lead · Four-Person Android Team
A native Android app that guides students through lab checkpoints, tracks their progress live, and lets TAs generate a whole lab from its manual with AI, backed by Spring Boot and MySQL. I led the frontend design and implementation, from the visual system to integration-ready screens.
1st in section · 2nd overall of 40+ teamsGallery
Overview
LabFlow turns a lab handout into a guided sequence of checkpoints on Android. Students work through each checkpoint and answer its questions, and their progress is saved as they go. TAs watch progress across a section and answer questions, and admins manage users, courses and approvals.
We built it as a four-person team for Iowa State's COM S 309 (Spring 2026): two frontend and two backend developers. The app is a native Android client in Java that talks to a Spring Boot REST and WebSocket backend backed by MySQL.
AI Lab Constructor: a full lab from the lab manual
TAs don't have to build a lab one checkpoint at a time. On the Create Lab screen, they switch on Use AI Lab Constructor, upload the existing lab manual as a PDF and tap Create with AI. The backend reads the manual and creates the lab's whole checkpoint sequence: titles, instructions, time estimates, and graded questions with answer keys. The TA then lands on the finished checkpoint list to review it and publish.
The TA flow, end to end
Create the lab
The TA enters the lab name and deadline. In the admin flow, a lab section is created as well. With the AI constructor on, the "number of steps" field is hidden, because the manual decides how many checkpoints there are.
Upload the lab manual
The TA picks the PDF on the device. The app reads its page count and uploads it as multipart form data to
POST /labs/pdf/upload/{labId}.Generate the checkpoints
The app calls
POST /labs/openai/{labId}/checkpoint-creation. The backend extracts the manual's text with PDFBox (sorted by position on the page), cleans up the whitespace, and sends it to OpenAI's Responses API with a strict parser prompt.Validate and save
The backend pulls the JSON array of checkpoints out of the reply, stripping code fences and matching brackets, and validates each entry. Every checkpoint needs a title. Answerable checkpoints need a question type and description, and choice-style questions need options and correct answers. Checkpoints are then numbered 1…n, the lab's step count is set, and everything is saved.
Review and publish
A "Lab Created" dialog takes the TA to the lab's checkpoint list ("Tap a checkpoint to edit or delete"). There they can adjust any generated checkpoint, add more, and publish the lab to students.
What the manual becomes
| Generated field | What it holds | Where it's used |
|---|---|---|
title | The checkpoint's name (required) | Checkpoint lists for students and TAs |
description | The instructions for that step, taken from the manual | The student's checkpoint view |
estimatedMinutes | Expected time for the step | TA step timers and "Step taking too long" alerts |
answerable + question | A question, only when the manual actually asks one: multiple choice, multi-select, fill-in-the-blank, short answer or image upload | Student answers saved to their progress |
options + correctAnswerIndex | Answer choices and the answer key, required for the choice-based types | Checking student answers |
Grounded in the manual
The prompt tells the model to use only the manual's text, to ignore any instructions inside that text, not to add missing information, and to return a single JSON array. It may only attach a question when a real one exists in the manual, and it must write units in plain ASCII (for example μ → u, Ω → Ohm).
Safe failure
Generation is refused if the lab already has checkpoints or if no manual has been uploaded. If the upload or generation fails, the lab is still created and the TA is sent to its checkpoint list with an explanation, so they can build it by hand. Labs can also start from a saved template instead.
The service calls OpenAI's Responses API with the model set in code (gpt-5.4-mini). The API key comes from the backend config or the OPENAI_API_KEY environment variable. Without a key, the endpoint returns a clear "not configured" error.
My role: frontend design lead
I led frontend design and implementation, covering the visual system, the reusable screens and navigation, and integration-ready interfaces for the backend.
- Created the app's visual system. It uses an Iowa State cardinal accent (
#C8102E), lab and course cards with progress bars, and a persistent Home / Labs / Help / Profile bottom navigation. - Wrote the design-document specs for the student Lab List and Lab Instructions screens.
- Lab List: a summary panel (active labs, average progress, labs due soon) above tappable lab cards with completion bars.
- Lab Instructions: a checkpoint-by-checkpoint view with progress, Previous/Next, Submit, Help Request and Save / Mark Complete.
- Built reusable screens and navigation, and wired them to the backend through the shared Retrofit API layer.
- Worked across both frontend and backend, and helped resolve major integration issues before the final demo.
What the app does
These features are in the team's final codebase. The work was shared between the frontend and backend developers, so this section describes the product as a whole and does not list only my parts.
Guided checkpoints
Each lab is split into checkpoints, and each checkpoint can carry questions. The backend defines five question types: multiple choice, multi-select, fill-in-the-blank, short answer and image upload. Students answer them in the app.
Live progress tracking
Per-student progress is saved through REST, and every checkpoint completion or submission is broadcast over a STOMP WebSocket topic. TA dashboards, the student-progress screen and the course roster update as it happens (details below). Submissions can be downloaded as PDFs.
Lab authoring
TAs can generate an entire lab from its manual with the AI Lab Constructor (see above). They can also create and edit labs and checkpoints by hand, start from lab templates, and clone labs across courses.
Equipment in 3D
Checkpoints can link 3D models of lab equipment. The app opens them in an in-app viewer built on a WebView with <model-viewer>.
Help & communication
Students can post doubt threads (questions with images) and use course chat that runs over WebSockets. The app also includes office-hours booking with student and TA views and a student calendar.
Accounts & roles
The app has separate Student, TA and Admin dashboards. Sign-in uses a one-time-code MFA step, and TA signups and course changes go through approval requests. Notifications arrive by Firebase push and over WebSocket.
Real-time layer: WebSockets
WebSockets carry everything in LabFlow that has to show up without a refresh: checkpoint progress, course chat and notifications. The Spring Boot backend runs a STOMP message broker. It registers a /ws endpoint with a SockJS fallback, uses a simple broker on /topic and /queue, and sends client messages to /app.
The Android app connects to /ws/websocket with a small STOMP client the team wrote on top of plain WebSockets (OkHttp, and Java-WebSocket for notifications). It builds the CONNECT, SUBSCRIBE and SEND frames by hand and buffers incoming text until each frame's terminating null byte arrives.
Channels the app uses
| Channel | Direction | What updates live | Where it shows up |
|---|---|---|---|
/topic/labs/progress | Server → app | One event per progress change. Each carries the lab and student IDs, a completion percentage, a submitted flag, the event type, and the student's current checkpoint step with its start time and paused time. | TA dashboard, TA student-progress screen, course roster |
/topic/chats/courses/{courseId}/app/chats/courses/{courseId}/send | Both ways | Course chat. History loads once over REST, then new messages are sent and received as STOMP frames. Messages are de-duplicated by ID so nothing appears twice. | Course chat screen |
/topic/notifications/{userId} | Server → app | Per-user notifications: lab deadline reminders, deadline changes, lab releases, new doubt threads for TAs, approval requests for admins, and approval outcomes. | Student home alert card; system notifications on TA and admin dashboards |
How a progress update travels
The backend publishes CHECKPOINT_STARTED, CHECKPOINT_PAUSED, CHECKPOINT_COMPLETED, LAB_SUBMITTED and LAB_UNSUBMITTED. The app's checkpoint-answer and submit calls trigger the completed and submitted events.
What TAs see live
On the TA dashboard, each pinned lab's average completion is recalculated as student events arrive. On the student-progress screen, the affected student's row refreshes and per-step timers count elapsed time minus pauses. When TA alerts are on, a student who runs past a step's expected time triggers an alert ("Step taking too long"). The course roster flips students between Not started, In progress and Submitted.
What students see live
Notifications go to each user's own topic, so the home screen's alert card updates as they arrive. A backend scheduler checks every minute and sends 24-hour and 1-hour deadline reminders. Each notification is also saved to the database and sent as a Firebase push for when the app isn't open.
Socket as signal, REST as truth
On the roster and TA progress screens, a progress event tells the app which student changed. The app then fetches that student's full record over REST before redrawing, so the screen always matches the database.
Connection lifecycle
Sockets are tied to screens. They connect when a screen opens and close when it pauses or stops. The progress socket is one shared connection that several screens register with, and it closes when the last screen leaves. After an unexpected drop, the progress and chat sockets reconnect after 2 seconds. A chat message sent while the socket is still connecting shows "Chat is still connecting" instead of being lost.
Why it matters
- During a live lab, a TA can see who is falling behind on which step while it is happening, without refreshing.
- Chat and notifications arrive as pushed messages, so the app doesn't have to poll the server.
- Broadcasting one progress topic lets several TA screens show the same change from a single event.
The backend also publishes /topic/labs/submissions, direct-chat topics (/topic/chats/{conversationId}) and doubt-thread topics (/topic/doubts/threads/{threadId}), which the Android app doesn't subscribe to. The repo also includes a browser-based SockJS + STOMP.js page for testing the backend's sockets.
Architecture
ApiServiceAndroid client
The client is written in Java with Material Components and RecyclerView adapters for lab and checkpoint lists, and uses Glide for images. A singleton RetrofitClient is created at app start by LabFlowApp; it sets the base URL, timeouts, interceptors and Gson. The app targets min SDK 24 and target SDK 34.
Spring Boot backend
The backend is built on Spring Boot 3.2 and Java 17, using Spring Data JPA, Security, Mail and WebSocket (STOMP). It exposes controllers for auth, users, courses, labs, checkpoints, progress, chat, office hours and notifications, and documents them with springdoc OpenAPI.
The GitLab CI pipeline runs Maven build, test and deploy stages for the backend, plus Gradle build and test stages for the Android app.