Skip to content
Make sure to read the updated submission instructions!

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.

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:

  1. Adding Supabase authentication (email/password)
  2. Creating login/register screens with protected route flow
  3. Modifying your SQLite schema to add user_id columns
  4. Updating all CRUD operations to filter by the current user’s ID

Protect your Assignment 3 work before adding authentication:

  1. Tag your Assignment 3 final state:

    Terminal window
    git tag assignment-3-final
    git push origin assignment-3-final

    This creates a permanent bookmark you can return to if needed: git checkout assignment-3-final

  2. Create a new branch for Assignment 4:

    Terminal window
    git checkout -b assignment-4

    All your A4 work will happen on this branch. You can always go back to main if something breaks.

  3. Push the new branch:

    Terminal window
    git push -u origin assignment-4

Before coding, update your README.md on your new assignment-4 branch.

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_id column to todos table since each user can create many todos.
  • Only todos from the currently authenticated user will populate the UI.

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.

Update your UI state diagram to include anything related to users now.

Assignment 4 Sketches

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_id column so that each user only sees their own todos.
    • All database operations (SELECT, INSERT, UPDATE, DELETE) are restricted by current user’s ID.

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!

Follow the instructions in chapter 4.2.

Now update your SQLite tables to support multiple users.

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).

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_id parameter 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

Update your Context/Reducer to use the current user ID.

// Example TodoContext update
function 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>
);
}

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);
};

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:

Examples: Favorites list, saved locations, user preferences, bookmarks, personal collections

  1. Add user_id to relevant tables following the same pattern from Part 3.
  2. Update CRUD operations to filter by current user ID.
  3. Protect the feature’s routes if they should only be accessible when logged in.
// Example: Saving a favorite location
const saveFavorite = async (location: string) => {
if (!user) return;
await db.runAsync(
'INSERT INTO favorites (user_id, location) VALUES (?, ?)',
[user.id, location]
);
};

Examples: Weather lookup, news feed, restaurant search, movie database

  1. Consider making it a public tab (no Tabs.Protected wrapper) if it makes sense for your app.
  2. Maintain your A3 caching strategy and ensure TTL and cache invalidation still work.
  3. 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>

Follow these steps on both iOS and Android to thoroughly test your app before submission.

  1. Test registration with empty fields

    • Go to register screen
    • Leave email and password blank, tap register
    • Error message appears (e.g., “Email required”)
  2. Test registration with invalid email

    • Enter notanemail and password test123
    • Tap register
    • Error message appears (e.g., “Invalid email format”)
  3. 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)
  4. Test log out

    • Find and tap log out button
    • Returns to login screen
    • Cannot access app screens anymore
  5. Test login with wrong password

    • Enter alice@test.com with password wrongpassword
    • Tap login
    • Error message appears (e.g., “Invalid login credentials”)
  6. Test login with user that doesn’t exist

    • Enter nobody@test.com with password anything
    • Tap login
    • Error message appears (e.g., “Invalid login credentials”)
  7. 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”)
  8. Test successful login

    • Enter alice@test.com / password123
    • Tap login
    • Loading indicator appears
    • Redirects to app screens successfully
PointsCriteria
5Login 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.
4Auth UI mostly working but minor issues (e.g., some error messages unclear, or validation incomplete for edge cases, or loading states sometimes missing).
3Auth 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).
2Partial auth UI (e.g., login works but register has major issues, or validation missing entirely, or error messages not displayed).
1Minimal auth UI (e.g., login/register screens exist but barely functional, frequent crashes, or no error handling).
0No auth UI or completely non-functional.
  1. 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
  2. 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
PointsCriteria
5Route 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.
4Route protection mostly working but minor issues (e.g., brief flicker of wrong screen on load, or session persistence occasionally fails).
3Route 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).
2Partial route protection (e.g., some screens protected but others accessible when they shouldn’t be, or can manually navigate to wrong screens).
1Minimal route protection (e.g., guards exist but don’t work correctly, or conditional rendering instead of proper route protection).
0No route protection or completely non-functional.
  1. 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
  2. Log out and register Bob

    • Tap log out
    • Register new account: bob@test.com / password123
    • Registration succeeds and redirects to app
  3. 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)
  4. Create data for Bob

    • Create 2 instances of the main entity for Bob
    • CRUD operations work
    • Bob sees only his 2 items
  5. 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
  6. Test persistence

    • Force quit the app
    • Reopen app
    • Still logged in as Alice
    • Alice’s 2 items still visible
PointsCriteria
5Perfect 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.
4User scoping mostly working but minor issues (e.g., one operation occasionally shows wrong user’s data, or data isolation works but has edge cases).
3User 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).
2Partial user scoping (e.g., only Read operations scoped, or Create/Update/Delete not filtering by user_id, or major data leakage issues).
1Minimal user scoping (e.g., attempted to add user_id to queries but implementation broken, or users frequently see wrong data).
0No user scoping or completely non-functional (all users see all data).
  1. 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
PointsCriteria
3Big feature fully functional as described in README. No errors during usage. If user-specific, data properly scoped. If public, accessible to all.
2Big feature mostly working but has minor issues (e.g., small bugs, unclear behavior, or partial functionality).
1Big feature barely functional (e.g., frequent errors, missing core functionality, or significantly different from README description).
0Big feature missing, completely non-functional, or not described in README.

.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.

CriteriaStandard
Process ReflectionClear explanation of approach, design decisions, and problem-solving steps
Technical DetailSpecifics about code structure, logic, or bugs encountered and fixed
AI Usage DisclosureClearly explains how AI was used, what was kept/changed, with reasoning
Insight & Critical ThinkingThoughtful reflection on what was learned, understood, or found challenging
Clarity & FormatConcise, readable, well-structured with bullet points or short paragraphs

At the top of your .devlog.md, you must declare the option that best describes how you used AI during the assignment:

CategoryDescription
No UseYou did not use any AI tools at any point.
TutorYou used AI to explain code, concepts, or errors. No code was generated by AI.
AssistantYou asked AI for code suggestions or snippets and integrated them with understanding.
ReviewerYou wrote the code yourself, then used AI to review, critique, or suggest improvements.

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.

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.

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.

  1. Test thoroughly. Download each submission, run it on both iOS and Android emulators in the lab.
  2. Grade using the rubric. Go through each criterion systematically, choosing the level that best matches what you observe.
  3. Provide detailed feedback. Write specific, actionable comments explaining what works well and what needs improvement.
  4. 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.

The average of all peer assessments you received for your work.

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.

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)
1Click the Source Control icon (third down on the left sidebar)git status - View changed files
2Click + to stage all changes, or + next to individual filesgit add . or git add <filename> - Stage changes
3Type a commit message in the text box, then click the ✔ to commitgit commit -m "Your message" - Commit staged changes
4Click ... and choose Push to upload your commit to GitHubgit push - Push commits to GitHub

Commit frequently. It’s good practice, and it also creates a traceable history of your progress.

Before submitting your assignment, ensure that your app includes all the necessary elements for your peers to properly evaluate your submission.

  1. Go to Moodle and click the link for this assignment in the calendar.

  2. Click the blue Add Submission button at the top of the workshop page.

    1. Title: A4 Submission.

    2. 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:

        1. On the Todos tab, tap the + button to add a todo
        2. On the Weather tab, search for a city and tap “Save Location”
        3. Pull to refresh to update weather data
        4. 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

    3. Zip your assignment folder (without the node_modules folder!) 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 .git folder 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) and app.json (name and slug).
      • Optionally, you may include your .devlog.md file if you want to share your design diary with your reviewers
  3. Click the Save changes button at the bottom.

  4. 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.