Assignment 4
Learning Objectives
- Integrate Supabase authentication into your existing app
- Implement email/password registration and login flows
- Protect app routes based on authentication state
- Migrate SQLite schema to include user ownership
- Scope all CRUD operations to the current authenticated user
- Test multi-user functionality with separate user accounts
- 💯 Worth: 10%
- 📅 Due: See due date on Moodle
- 🚫 Penalty: Late submissions lose 10% per day up to 3 days. Nothing is accepted after 3 days.
🔍 Context
Section titled “🔍 Context”In Assignment 3, you built an app with SQLite persistence, API integration, and professional state management. However, all data is stored locally on one device with no concept of users.
For Assignment 4, you’ll add multi-user authentication using Supabase. Users will be able to create accounts, log in, and each user will see only their own data. You’ll achieve this by:
- Adding Supabase authentication (email/password)
- Creating login/register screens with protected route flow
- Modifying your SQLite schema to add
user_idcolumns - Updating all CRUD operations to filter by the current user’s ID
🔖 Part 0: Tag & Branch
Section titled “🔖 Part 0: Tag & Branch”Protect your Assignment 3 work before adding authentication:
-
Tag your Assignment 3 final state:
Terminal window git tag assignment-3-finalgit push origin assignment-3-finalThis creates a permanent bookmark you can return to if needed:
git checkout assignment-3-final -
Create a new branch for Assignment 4:
Terminal window git checkout -b assignment-4All your A4 work will happen on this branch. You can always go back to main if something breaks.
-
Push the new branch:
Terminal window git push -u origin assignment-4
📐 Part 1: Planning
Section titled “📐 Part 1: Planning”Before coding, update your README.md on your new assignment-4 branch.
1. Update Database Tables
Section titled “1. Update Database Tables”Explain which tables you modified and the relationships between them now that multiple users can use your app. You must also describe any many-to-many relationships you might have.
Example:
CREATE TABLE IF NOT EXISTS todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, title TEXT NOT NULL, description TEXT, priority INTEGER DEFAULT 0, completed INTEGER DEFAULT 0, created_at TEXT DEFAULT CURRENT_TIMESTAMP);- Added
user_idcolumn totodostable since each user can create many todos. - Only todos from the currently authenticated user will populate the UI.
2. Outline Other “Big Feature”
Section titled “2. Outline Other “Big Feature””The point of this is to make sure your app is more interesting than a simple CRUD app. A good way to think about a “big feature” is something that would warrant its own tab in the outer navigation. Most of you already have something that would fit this description. Explain whether your big feature’s data is user-specific or public.
Example:
My other “big feature” is a weather tab. This feature was added in A3 and uses the OpenWeather API so that users can see the current weather for their location. The weather tab will be public since it’s not specific to any user.
3. UI Sketches
Section titled “3. UI Sketches”Update your UI state diagram to include anything related to users now.

Excalidraw link to this diagram. If you’re already working in Excalidraw, make sure to open in a private/incognito window so you don’t overwrite your work.
-
Authentication Flow
- If no session exists on initial start, show Login screen.
- If session exists, then show Todos tab.
- Users can log out from the Profile tab which will return to the Login screen.
-
Protected vs Public Screens
- Weather tab is public (no login required) since anyone can check weather.
- Profile tab is public, but it shows different screens depending on the current authentication state.
- Todos tab is protected (login required) since it contains user-specific data with CRUD operations.
-
Data Scoping
- Todos table stores
user_idcolumn so that each user only sees their own todos. - All database operations (SELECT, INSERT, UPDATE, DELETE) are restricted by current user’s ID.
- Todos table stores
4. Message Me
Section titled “4. Message Me”You don’t have to wait for me to start implementing, but just be aware I may ask you to change things in the README which will affect your implementation. Detailed implementation specs coming soon!
📦 Part 2: Supabase Setup
Section titled “📦 Part 2: Supabase Setup”Follow the instructions in chapter 4.2.
🗄️ Part 3: SQLite Schema Migration
Section titled “🗄️ Part 3: SQLite Schema Migration”Now update your SQLite tables to support multiple users.
Add user_id Column
Section titled “Add user_id Column”For each table that stores user-specific data, add a user_id column. This will be a text column containing the Supabase user ID (UUID format).
Update Database Functions
Section titled “Update Database Functions”Modify all your CRUD functions to include user_id, for example:
export async function getTodos(db: SQLiteDatabase, userId: string) { return await db.getAllAsync('SELECT * FROM todos'); return await db.getAllAsync('SELECT * FROM todos WHERE user_id = ?', [ userId, ]);}Apply this pattern to ALL your CRUD functions:
- Create: Add
user_idparameter in INSERT statement - Read: Add
WHERE user_id = ?to all SELECT queries - Update: Add
WHERE user_id = ?to verify ownership - Delete: Add
WHERE user_id = ?to verify ownership
Connect Auth to CRUD
Section titled “Connect Auth to CRUD”Update your Context/Reducer to use the current user ID.
// Example TodoContext updatefunction TodoProvider({ children }) { const { user } = useAuth(); const [state, dispatch] = useReducer(todoReducer, initialState);
const addTodo = async (title: string) => { if (!user) return;
await createTodo(db, user.id, title);
// dispatch... };
const fetchTodos = async () => { if (!user) return;
const todos = await getTodos(db, user.id);
// dispatch... };
// etc.
return ( <TodoContext.Provider value={{ state, addTodo, fetchTodos, ... }}> {children} </TodoContext.Provider> );}TypeScript Safety
Section titled “TypeScript Safety”Since your protected screens use Tabs.Protected, users can’t access them while logged out. However, TypeScript doesn’t know this, so you’ll need to handle the user being potentially null:
const addTodo = async (title: string) => { // This should never happen due to route protection, // but TypeScript requires the check since user is User | null if (!user) return;
await createTodo(db, user.id, title);};Alternatively, use non-null assertion if you’re certain the route is protected:
const addTodo = async (title: string) => { // Non-null assertion (!) since this screen is route-protected await createTodo(db, user!.id, title);};🚀 Part 4: Big Feature
Section titled “🚀 Part 4: Big Feature”Now that authentication is working, ensure your “big feature” (outlined in Part 1) is fully integrated with the authentication system.
Depending on what your big feature is, follow the appropriate approach:
If Your Feature Uses User-Specific Data
Section titled “If Your Feature Uses User-Specific Data”Examples: Favorites list, saved locations, user preferences, bookmarks, personal collections
- Add
user_idto relevant tables following the same pattern from Part 3. - Update CRUD operations to filter by current user ID.
- Protect the feature’s routes if they should only be accessible when logged in.
// Example: Saving a favorite locationconst saveFavorite = async (location: string) => { if (!user) return;
await db.runAsync( 'INSERT INTO favorites (user_id, location) VALUES (?, ?)', [user.id, location] );};If Your Feature Uses Public/Shared Data
Section titled “If Your Feature Uses Public/Shared Data”Examples: Weather lookup, news feed, restaurant search, movie database
- Consider making it a public tab (no
Tabs.Protectedwrapper) if it makes sense for your app. - Maintain your A3 caching strategy and ensure TTL and cache invalidation still work.
- Consider user-specific preferences, even if the data is public, users might have preferences (like saved search queries or display settings) that should be user-scoped.
// Example: Weather tab remains public, but saved locations are user-scoped<Tabs> {/* Public - anyone can check weather */} <Tabs.Screen name="weather" options={{ title: 'Weather' }} />
{/* Protected - saved locations require login */} <Tabs.Protected guard={isLoggedIn}> <Tabs.Screen name="saved-locations" options={{ title: 'Saved' }} /> </Tabs.Protected></Tabs>✅ Part 5: Testing & Verification
Section titled “✅ Part 5: Testing & Verification”Follow these steps on both iOS and Android to thoroughly test your app before submission.
Authentication Flow & UI
Section titled “Authentication Flow & UI”-
Test registration with empty fields
- Go to register screen
- Leave email and password blank, tap register
- Error message appears (e.g., “Email required”)
-
Test registration with invalid email
- Enter
notanemailand passwordtest123 - Tap register
- Error message appears (e.g., “Invalid email format”)
- Enter
-
Register Alice’s account
- Enter email:
alice@test.com - Enter password:
password123 - Confirm password:
password123 - Tap register
- Loading indicator appears during registration
- On success, app redirects to main app screens
- Cannot navigate back to login/register screens (routes protected)
- Enter email:
-
Test log out
- Find and tap log out button
- Returns to login screen
- Cannot access app screens anymore
-
Test login with wrong password
- Enter
alice@test.comwith passwordwrongpassword - Tap login
- Error message appears (e.g., “Invalid login credentials”)
- Enter
-
Test login with user that doesn’t exist
- Enter
nobody@test.comwith passwordanything - Tap login
- Error message appears (e.g., “Invalid login credentials”)
- Enter
-
Test registering with existing email
- Go to register screen
- Enter
alice@test.com/password123/password123 - Tap register
- Error message appears (e.g., “User already registered”)
-
Test successful login
- Enter
alice@test.com/password123 - Tap login
- Loading indicator appears
- Redirects to app screens successfully
- Enter
| Points | Criteria |
|---|---|
| 5 | Login and register screens fully functional. Proper input validation (empty fields, invalid email, existing user). Clear error messages for all error cases. Loading states visible. Smooth UX with no crashes. |
| 4 | Auth UI mostly working but minor issues (e.g., some error messages unclear, or validation incomplete for edge cases, or loading states sometimes missing). |
| 3 | Auth UI functional but significant issues (e.g., poor error handling, no validation on some fields, loading states missing, or UX issues like no feedback on submission). |
| 2 | Partial auth UI (e.g., login works but register has major issues, or validation missing entirely, or error messages not displayed). |
| 1 | Minimal auth UI (e.g., login/register screens exist but barely functional, frequent crashes, or no error handling). |
| 0 | No auth UI or completely non-functional. |
Route Protection & Session Persistence
Section titled “Route Protection & Session Persistence”-
Test session persistence
- Force quit the app by swiping up from app switcher
- Reopen the app
- Still logged in as Alice (goes directly to app screens)
- Alice’s session persisted across app restart
-
Verify cross-platform authentication
- Open Android emulator (if you tested on iOS) or vice versa
- Login with
alice@test.com/password123 - Login succeeds, indicating the account is synced via Supabase
| Points | Criteria |
|---|---|
| 5 | Route protection fully working. Logged-out users only see login/register, and any public screens. Logged-in users only see all screens. Smooth transitions with proper loading states. Session persists across app restarts. |
| 4 | Route protection mostly working but minor issues (e.g., brief flicker of wrong screen on load, or session persistence occasionally fails). |
| 3 | Route protection functional but significant issues (e.g., can sometimes access protected screens while logged out, or log out doesn’t fully clear session, or no loading state during auth check). |
| 2 | Partial route protection (e.g., some screens protected but others accessible when they shouldn’t be, or can manually navigate to wrong screens). |
| 1 | Minimal route protection (e.g., guards exist but don’t work correctly, or conditional rendering instead of proper route protection). |
| 0 | No route protection or completely non-functional. |
User-Scoped CRUD & Data Isolation
Section titled “User-Scoped CRUD & Data Isolation”-
Create data for Alice
- While logged in as Alice, go to the main entity screen
- Create 3 instances of the main entity
- Update one of them
- Delete one of them
- All CRUD operations work without errors
- Alice now has 2 items remaining
-
Log out and register Bob
- Tap log out
- Register new account:
bob@test.com/password123 - Registration succeeds and redirects to app
-
Verify Bob sees empty data
- Go to main entity screen
- Bob sees NO data from Alice (empty list)
- Bob’s screen is empty (fresh start)
-
Create data for Bob
- Create 2 instances of the main entity for Bob
- CRUD operations work
- Bob sees only his 2 items
-
Switch back to Alice
- Log out as Bob
- Login as
alice@test.com/password123 - Alice sees her original 2 items
- Alice does NOT see Bob’s 2 items
-
Test persistence
- Force quit the app
- Reopen app
- Still logged in as Alice
- Alice’s 2 items still visible
| Points | Criteria |
|---|---|
| 5 | Perfect data isolation. All CRUD operations properly scoped by user_id. Alice sees only her data, Bob sees only his. No data leakage between users. Data persists correctly across login/log out cycles. |
| 4 | User scoping mostly working but minor issues (e.g., one operation occasionally shows wrong user’s data, or data isolation works but has edge cases). |
| 3 | User scoping functional but significant issues (e.g., some CRUD operations not scoped by user_id, or data isolation inconsistent, or users sometimes see each other’s data). |
| 2 | Partial user scoping (e.g., only Read operations scoped, or Create/Update/Delete not filtering by user_id, or major data leakage issues). |
| 1 | Minimal user scoping (e.g., attempted to add user_id to queries but implementation broken, or users frequently see wrong data). |
| 0 | No user scoping or completely non-functional (all users see all data). |
Big Feature
Section titled “Big Feature”- Test the big feature
- Read the README’s “Big Feature” description
- Follow any testing instructions provided
- Feature works as described without errors
- If user-specific feature, log out, log in as Bob, verify Bob doesn’t see Alice’s feature data
- If public data feature, ensure it’s accessible and functional regardless of user
| Points | Criteria |
|---|---|
| 3 | Big feature fully functional as described in README. No errors during usage. If user-specific, data properly scoped. If public, accessible to all. |
| 2 | Big feature mostly working but has minor issues (e.g., small bugs, unclear behavior, or partial functionality). |
| 1 | Big feature barely functional (e.g., frequent errors, missing core functionality, or significantly different from README description). |
| 0 | Big feature missing, completely non-functional, or not described in README. |
📝 Dev Log (5%)
Section titled “📝 Dev Log (5%)”.devlog.md is your design diary. It’s where you document how you approached the assignment, what decisions you made, what challenges you encountered, and how you worked through them, including how you used any AI tools.
This is not a summary of your final product (that’s what your code and commit messages show). Instead, it’s a reflection of your process and thinking.
What to write:
- What approach you chose and why
- Any bugs or roadblocks you encountered and how you solved them
- How you tested and verified your implementation
- If you used AI tools (e.g. ChatGPT, Claude, Copilot), describe:
- What you asked
- What it returned
- What you kept or changed
- Include links to relevant chat logs when possible
What makes a good devlog:
- Specific technical insights (e.g. “I struggled with connecting the AI paddle’s movement to the ball’s position. I solved this by…”)
- Honest reflection on what you understood and what confused you
- Commentary on any AI output you received, what was useful, what wasn’t
What makes a weak devlog:
- Restating the assignment prompt
- Only describing what the final code does, without process
- Hiding or omitting AI tool usage
- Generic statements with no technical substance
Be concise. Bullet points are fine.
| Criteria | Standard |
|---|---|
| Process Reflection | Clear explanation of approach, design decisions, and problem-solving steps |
| Technical Detail | Specifics about code structure, logic, or bugs encountered and fixed |
| AI Usage Disclosure | Clearly explains how AI was used, what was kept/changed, with reasoning |
| Insight & Critical Thinking | Thoughtful reflection on what was learned, understood, or found challenging |
| Clarity & Format | Concise, readable, well-structured with bullet points or short paragraphs |
AI Involvement Category
Section titled “AI Involvement Category”At the top of your .devlog.md, you must declare the option that best describes how you used AI during the assignment:
| Category | Description |
|---|---|
| No Use | You did not use any AI tools at any point. |
| Tutor | You used AI to explain code, concepts, or errors. No code was generated by AI. |
| Assistant | You asked AI for code suggestions or snippets and integrated them with understanding. |
| Reviewer | You wrote the code yourself, then used AI to review, critique, or suggest improvements. |
💬 Self & Peer Assessment (5%)
Section titled “💬 Self & Peer Assessment (5%)”You’ll use the Moodle Workshop feature to assess your own work and give feedback on 2 of your peers’ submissions. Assessment is a core developer skill. Reading others’ code, giving constructive feedback, and critically evaluating your own work are things you’ll do constantly in real software teams.
Self-Assessment
Section titled “Self-Assessment”Before assessing your peers, you must grade your own submission using the same rubric. This helps you:
- Reflect on your work objectively
- Identify areas you could improve
- Practice evaluating code against clear criteria
- Develop self-awareness about your coding skills
Be honest in your self-assessment. The grade you give yourself is compared to the grade given by your peers and will negatively affect your final grade if there is a large discrepancy, and the thoughtfulness of your reflection will be considered.
Peer Assessment
Section titled “Peer Assessment”After completing your self-assessment, you’ll assess 2 of your peers’ submissions. Your assessment grade depends on how thorough, specific, and helpful your feedback is.
- Test thoroughly. Download each submission, run it on both iOS and Android emulators in the lab.
- Grade using the rubric. Go through each criterion systematically, choosing the level that best matches what you observe.
- Provide detailed feedback. Write specific, actionable comments explaining what works well and what needs improvement.
- Be fair and constructive. Treat your peers like colleagues whose success matters to your team.
Deeper Dive: How Moodle Calculates Your Grade
You receive two grades in a Workshop, the submission grade and the assessment grade.
Submission Grade (90%)
Section titled “Submission Grade (90%)”The average of all peer assessments you received for your work.
Assessment Grade (10%)
Section titled “Assessment Grade (10%)”How well you assessed others, based on how close your assessments are to the “consensus”. Moodle compares all assessments of the same submission and finds the one closest to the average (the “best” assessment). Your assessment grade depends on how similar your assessment is to this consensus:
- If you grade similarly to the majority of assessors → Higher assessment grade
- If you grade very differently from everyone else → Lower assessment grade
- The teacher’s assessment can have more weight to help establish the consensus
Example: Three peers assess the same app. Two give similar scores across all criteria, one gives very different scores. Moodle identifies the two similar assessments as closer to consensus and gives them higher assessment grades. The outlier gets a lower assessment grade.
💻 Code Walkthrough
Section titled “💻 Code Walkthrough”For each assignment, I might randomly select a few students for a short (10-15 minute) one-on-one code walkthrough. You’ll be asked to explain your implementation, reflect on your design decisions, and answer a few questions. This helps ensure understanding, promotes academic integrity, and prepares you to communicate your work which is an essential skill for every developer. You can be selected for any assignment, so always be ready to walk me through your code.
We will be using GitHub to submit in this course. You can use either the Git CLI or you can also use VSC’s built-in Git GUI client.
| Visual Studio Code (GUI) | Command Line (CLI) | |
|---|---|---|
| 1 | Click the Source Control icon (third down on the left sidebar) | git status - View changed files |
| 2 | Click + to stage all changes, or + next to individual files | git add . or git add <filename> - Stage changes |
| 3 | Type a commit message in the text box, then click the ✔ to commit | git commit -m "Your message" - Commit staged changes |
| 4 | Click ... and choose Push to upload your commit to GitHub | git push - Push commits to GitHub |
Commit frequently. It’s good practice, and it also creates a traceable history of your progress.
📥 Submission
Section titled “📥 Submission”Before submitting your assignment, ensure that your app includes all the necessary elements for your peers to properly evaluate your submission.
-
Go to Moodle and click the link for this assignment in the calendar.
-
Click the blue
Add Submissionbutton at the top of the workshop page.-
Title: A4 Submission.
-
Submission content: Describe your app briefly and provide critical information for your reviewers. Include:
-
Describe what your main entity is (the primary data type users create/manage). For example:
The main entity is Todos. Users can create, update, and delete todo items. Each todo has a title, description, due date, and completion status.
-
Describe your big feature and whether it uses user-specific or public data. For example:
The big feature is a Weather Tab that shows current weather for saved locations. Weather data is public (from OpenWeather API), but saved locations are user-specific. Each user can save their own favorite cities.
-
Provide any specific instructions for testing your app. For example:
- On the Todos tab, tap the + button to add a todo
- On the Weather tab, search for a city and tap “Save Location”
- Pull to refresh to update weather data
- Note: Weather API has 60 calls/hour limit, so cache is set to 10 minutes
-
Known issues, bugs, or incomplete features you’re aware of
-
-
Zip your assignment folder (without the
node_modulesfolder!) and attach it as a file. If you care about anonymity:- DO NOT include your name in any of the files or folders
- DO NOT include the
.gitfolder before zipping, otherwise it will contain your commit history which has your name and email in it - DO change the appropriate fields inside
package.json(name) andapp.json(nameandslug). - Optionally, you may include your
.devlog.mdfile if you want to share your design diary with your reviewers
-
-
Click the
Save changesbutton at the bottom. -
You’ll be able to start assessing your peers the soon after the assignment is due, look out for an announcement on Teams for when this becomes available.