RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-04-02Beginner

Building a Survey App with Rork — A Beginner's Guide to Forms, Data Collection, and Charts

Learn how to build a survey collection app from scratch using Rork. This beginner-friendly guide covers form design, multiple answer types, Supabase data storage, and aggregation charts — all in a single day.

surveyformRork539Supabase33React Native227beginner21data visualization2

Survey apps are incredibly practical — whether you're collecting customer feedback, running internal polls, or gathering event responses. Building a dedicated mobile app instead of relying on spreadsheets makes the experience far more pleasant for respondents, and gives you full control over the data.

Here you build a fully functional survey app with Rork — multiple question types (single choice, multiple choice, and free text), responses saved to Supabase, and aggregated results drawn as a bar chart. It is written for beginners: you don't need deep React Native knowledge, just a basic familiarity with Rork's interface.

What You'll Build

By the end of this tutorial, your app will include:

  • A survey form supporting single-choice, multiple-choice, and free-text questions
  • Response storage in Supabase via its REST API
  • An aggregation dashboard showing response counts and percentages as bar charts
  • Basic duplicate-response prevention using a device identifier

Prerequisites and Setup

Before diving in, make sure you have the following ready:

  • A Rork account (the free plan is sufficient for this project)
  • A Supabase account (free tier works fine — you'll need the project URL and anon key)
  • A rough idea of your survey questions, options, and expected answer types

If you haven't created a Supabase project yet, go ahead and do that first. The setup only takes a couple of minutes.

App Architecture Overview

Screen Structure

The app consists of three screens:

  • Home screen — Displays the survey title, description, and a "Start Survey" button
  • Form screen — Shows questions one at a time in a step-by-step flow
  • Dashboard screen — Visualizes aggregated responses with bar charts

Database Schema

You'll need two tables in Supabase:

surveys
  id          uuid (primary key)
  title       text
  description text
  created_at  timestamp

responses
  id            uuid (primary key)
  survey_id     uuid (foreign key → surveys)
  question_id   text
  answer        text
  respondent_id text
  created_at    timestamp

The answer field stores a plain string for single-choice and free text, and a comma-separated string for multiple-choice responses. This keeps the schema simple while covering all common cases.

Step-by-Step Implementation

Step 1: Set Up Supabase Tables

Open the SQL editor in your Supabase dashboard and run the following:

-- Create the surveys table
CREATE TABLE surveys (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  title text NOT NULL,
  description text,
  created_at timestamp DEFAULT now()
);
 
-- Create the responses table
CREATE TABLE responses (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  survey_id uuid REFERENCES surveys(id) ON DELETE CASCADE,
  question_id text NOT NULL,
  answer text NOT NULL,
  respondent_id text,
  created_at timestamp DEFAULT now()
);
 
-- Enable Row-Level Security and allow public access for this demo
ALTER TABLE surveys ENABLE ROW LEVEL SECURITY;
ALTER TABLE responses ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY "Public surveys are viewable" ON surveys FOR SELECT USING (true);
CREATE POLICY "Anyone can submit responses" ON responses FOR INSERT WITH CHECK (true);
CREATE POLICY "Responses are viewable" ON responses FOR SELECT USING (true);

Once the tables are created, copy your project URL and anon key from Settings → API.

Step 2: Create the Project in Rork

Open Rork, create a new project, and use this prompt to get started:

Build a survey collection app with the following features:

1. A home screen showing the survey title, description, and a "Start Survey" button
2. A step-by-step form screen that shows one question at a time
3. Three question types: single choice (radio buttons), multiple choice (checkboxes),
   and free text (text input)
4. A "Submit" button at the end that saves responses to Supabase
5. A dashboard screen showing response counts and percentage bars per option

Use a clean, blue-themed color scheme.

Rork will generate the screen structure automatically. Review the UI and refine as needed before wiring up the data layer.

Step 3: Define Your Survey Questions

Add a questions.ts file to store your question data:

// questions.ts — Survey question definitions
export const SURVEY_QUESTIONS = [
  {
    id: "q1",
    type: "single", // single-choice
    text: "How would you describe your app development experience?",
    options: ["No experience", "Self-teaching", "Published personal apps", "Professional developer"],
  },
  {
    id: "q2",
    type: "multiple", // multiple-choice
    text: "Which no-code/AI app builders have you tried? (Select all that apply)",
    options: ["Rork", "FlutterFlow", "Adalo", "Bubble", "Bolt", "None"],
  },
  {
    id: "q3",
    type: "text", // free-text
    text: "What's your biggest challenge when building apps?",
  },
];

Step 4: Save Responses to Supabase

Set up the Supabase client and a submit function:

// supabase.ts — Supabase client and response submission
import { createClient } from "@supabase/supabase-js";
 
const supabase = createClient(
  process.env.EXPO_PUBLIC_SUPABASE_URL!,
  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!
);
 
export async function submitSurveyResponses(
  surveyId: string,
  answers: Record<string, string | string[]>,
  respondentId: string
) {
  const rows = Object.entries(answers).map(([questionId, answer]) => ({
    survey_id: surveyId,
    question_id: questionId,
    // Join arrays (multiple choice) into a comma-separated string
    answer: Array.isArray(answer) ? answer.join(",") : answer,
    respondent_id: respondentId,
  }));
 
  const { error } = await supabase.from("responses").insert(rows);
 
  if (error) {
    console.error("Failed to save responses:", error.message);
    throw error;
  }
 
  return { success: true };
}

Use a device-generated UUID stored in AsyncStorage as the respondentId to prevent duplicate submissions from the same device.

Ask Rork to wire up the submit button with this prompt:

When the user taps Submit, call submitSurveyResponses() with the collected answers
and respondentId. Show a loading spinner during submission. On success, navigate to
a "Thank You" screen. On error, show an alert with the error message.

Step 5: Build the Results Dashboard

Fetch and aggregate response data with a custom hook:

// useResponseStats.ts
import { useEffect, useState } from "react";
import { supabase } from "./supabase";
 
interface AnswerStat {
  option: string;
  count: number;
  percentage: number;
}
 
export function useResponseStats(surveyId: string, questionId: string) {
  const [stats, setStats] = useState<AnswerStat[]>([]);
  const [total, setTotal] = useState(0);
 
  useEffect(() => {
    async function fetchStats() {
      const { data } = await supabase
        .from("responses")
        .select("answer")
        .eq("survey_id", surveyId)
        .eq("question_id", questionId);
 
      if (!data) return;
 
      const countMap: Record<string, number> = {};
      data.forEach(({ answer }) => {
        answer.split(",").forEach((opt: string) => {
          const key = opt.trim();
          countMap[key] = (countMap[key] || 0) + 1;
        });
      });
 
      const totalCount = data.length;
      setTotal(totalCount);
      setStats(
        Object.entries(countMap).map(([option, count]) => ({
          option,
          count,
          percentage: Math.round((count / totalCount) * 100),
        }))
      );
    }
    fetchStats();
  }, [surveyId, questionId]);
 
  return { stats, total };
}

For the charting layer, Building Interactive Charts in Your Rork App — Victory Native Guide walks through exactly how to render bar and pie charts from this kind of aggregated data.

Common Errors and Fixes

"Row-level security policy violation" — This means the RLS policies weren't applied correctly. Re-run the CREATE POLICY SQL statements from Step 1 and confirm they're listed under Authentication → Policies in the Supabase dashboard.

"Cannot read property of undefined" — This usually means the component rendered before data was ready. Make sure your hooks provide sensible default values ([] for arrays, 0 for counts) and show a loading indicator while data is being fetched.

Duplicate responses being saved — Double-check that your respondentId is being generated once and persisted in AsyncStorage. If it's re-generated on every app launch, the same user will appear as a new respondent each time.

Ideas for Extending the App

Once the core app is working, here are some directions worth exploring:

  • Dynamic question management: Move question data to a Supabase table so you can edit surveys without redeploying the app
  • Submission deadline: Add an expires_at field to limit when responses can be submitted
  • CSV export: Let admins download response data as a spreadsheet — this is highly valued in business settings
  • SaaS expansion: Turn it into a multi-survey platform with user accounts, and charge a monthly subscription fee. For building real-time dashboards on top of Supabase, Building a Real-Time Analytics Dashboard with Rork Max and Supabase Realtime is a great next read.

Wrapping Up

Building a survey app with Rork is a great way to get hands-on experience with forms, data persistence, and data visualization all in one project. Here's a quick recap of what we covered:

  • Setting up Supabase tables with RLS policies for public surveys
  • Generating the form UI in Rork and connecting it to your question definitions
  • Saving responses to Supabase and preventing duplicate submissions
  • Fetching aggregated data and rendering it as a dashboard

The survey app you built today is a solid foundation — take it further by adding dynamic question management, deadlines, and eventually a full SaaS model with authentication and billing.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-06-23
DAU Went Up but Retention Didn't — Rebuilding Gamification That Actually Sticks in Rork Apps
Points, badges, and leaderboards lift DAU, but retention is a different story. Field notes on a server-authoritative point ledger, streaks that forgive, and leaderboards that don't crush newcomers — with working code for Rork apps.
Dev Tools2026-05-03
react-native-chart-kit BarChart with Multiple Datasets: Side-by-Side Implementation Patterns for Rork Apps
react-native-chart-kit's BarChart doesn't support multiple datasets out of the box. Learn 3 practical workarounds for side-by-side grouped bar charts in Rork apps — from quick color-coded fixes to migrating to Victory Native.
Dev Tools2026-04-19
Offline-First Architecture in Rork Apps: WatermelonDB + Supabase Sync
A complete guide to implementing offline-first architecture in Rork apps using WatermelonDB and Supabase Realtime. Covers local caching, optimistic updates, conflict resolution, and cross-device sync.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →