I Built a Zero-Cost Price Tracker in Flutter Web + Supabase — No Extra Edge Functions Needed
Introduction
We've all been there: you bookmark a product hoping to buy it when the price drops, and then either forget about it or check too late. My app 自分株式会社 (Jibun Kabushiki Kaisha — "My Corporation") is a Flutter Web + Supabase life management platform. It already had a shopping reminder list (shopping_items) and a post-purchase log (purchase_sessions), but there was a gap: pre-purchase price tracking.
Using insights from a NotebookLM Deep Research session ("Building a Zero-Cost Amazon Price Tracker"), I built a full price tracker feature this week — here's how.
What It Does
- Paste a product URL → it gets added to your wishlist
- Manually log the current price (Phase 2 will auto-scrape)
- Tracks all-time high and low prices automatically via a DB trigger
- Set a target price → get notified when it's hit (via notification-center EF)
- Price history chart (fl_chart, Phase 2)
Implementation
DB Schema (Supabase PostgreSQL)
Two tables: tracked_products (the wishlist) and price_logs (time series).
CREATE TABLE tracked_products (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
product_url text NOT NULL,
product_name text NOT NULL DEFAULT '',
store_domain text NOT NULL DEFAULT '', -- e.g. amazon.co.jp
current_price int,
target_price int, -- notify when price <= this
highest_price int,
lowest_price int,
notify_on_drop boolean NOT NULL DEFAULT true,
is_active boolean NOT NULL DEFAULT true,
is_purchased boolean NOT NULL DEFAULT false,
UNIQUE (user_id, product_url)
);
CREATE TABLE price_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product_id uuid NOT NULL REFERENCES tracked_products(id) ON DELETE CASCADE,
price int NOT NULL,
source text NOT NULL DEFAULT 'manual', -- 'manual' | 'scraper' | 'api'
logged_at timestamptz NOT NULL DEFAULT now()
);
Row Level Security is fully configured so users only see their own products.
Auto-Update High/Low via Trigger
CREATE OR REPLACE FUNCTION update_tracked_product_prices()
RETURNS TRIGGER AS $$
BEGIN
UPDATE tracked_products SET
current_price = NEW.price,
highest_price = GREATEST(COALESCE(highest_price, NEW.price), NEW.price),
lowest_price = LEAST(COALESCE(lowest_price, NEW.price), NEW.price),
updated_at = now()
WHERE id = NEW.product_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_price_logs_after_insert
AFTER INSERT ON price_logs
FOR EACH ROW EXECUTE FUNCTION update_tracked_product_prices();
Just insert a row into price_logs and everything updates automatically. The frontend never needs to compute min/max.
No New Edge Functions — Hub Pattern
This project enforces a hard cap of 50 Edge Functions to keep deployments manageable. Instead of creating a new price-tracker EF, I added price.* action handlers to the existing lifestyle-hub:
case 'price.add': return await handlePriceAdd(req, supabase);
case 'price.list': return await handlePriceList(req, supabase);
case 'price.log': return await handlePriceLog(req, supabase);
This "hub + action routing" pattern lets us add features without increasing the EF count or adding cold start overhead.
Flutter UI
The main page shows a card list of tracked products. Adding a product uses a showModalBottomSheet — quick and mobile-friendly.
Future<void> _load() async {
final resp = await _supabase.functions.invoke(
'lifestyle-hub',
body: {'action': 'price.list', 'only_active': true},
);
final items =
(resp.data?['products'] as List?)?.cast<Map<String, dynamic>>() ?? [];
if (mounted) setState(() => _products = items);
}
Challenges
1. Avoiding Table Overlap
Three tables handle finances in the app:
| Table | Purpose |
|---|---|
shopping_items |
Daily item reminders (no price/URL) |
purchase_sessions |
Post-purchase logs (no pre-buy wishlist) |
tracked_products |
Pre-buy wishlist with price history ← new |
Documenting the distinction clearly prevents future confusion.
2. The 50 EF Hard Cap
It's tempting to create a standalone EF for every new feature. The hub pattern forced me to think harder about grouping, which led to cleaner architecture.
3. Philosophy Alignment Check
Before implementing, we run a 9-principle checklist ("9 Gensoku") to ensure features align with the app's philosophy — "the user is the CEO of their own life." Price tracker scored 9/9:
- CEO: User sets the target price, not the AI
- Time as capital: Automate the "check price" task entirely
- KPI = yesterday's self: Compare against your own all-time low, not others
Conclusion
Key takeaways:
- Supabase triggers keep aggregation logic in the database where it belongs
- The hub + action routing pattern scales EF capacity without adding new deployments
- Deep Research with NotebookLM before coding reduces design mistakes
- A philosophy checklist filters out features that would make you just another competitor clone
Next steps: Phase 2 (auto-scraping via Deno Edge Function), price drop push notifications, and an fl_chart price history widget.
Building in public: https://my-web-app-b67f4.web.app/
Top comments (0)