DEV Community

kanta13jp1
kanta13jp1

Posted on

I Built a Zero-Cost Price Tracker in Flutter Web + Supabase — No Extra Edge Functions Needed

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()
);
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Supabase triggers keep aggregation logic in the database where it belongs
  2. The hub + action routing pattern scales EF capacity without adding new deployments
  3. Deep Research with NotebookLM before coding reduces design mistakes
  4. 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/

FlutterWeb #Supabase #buildinpublic #OpenSource

Top comments (0)