Research files synthesized: - STACK.md: Flutter + Supabase + Riverpod recommended stack - FEATURES.md: 7 table stakes, 6 differentiators, 7 anti-features identified - ARCHITECTURE.md: Offline-first sync with optimistic locking, RLS multi-tenancy - PITFALLS.md: 5 critical pitfalls (v1), 8 moderate (v1.5), 3 minor (v2+) - SUMMARY.md: Executive synthesis with 3-phase roadmap implications Key findings: - Stack: Flutter + Supabase free tier + mobile_scanner + Open Food Facts - Critical pitfalls: Barcode mismatches, timezone bugs, sync conflicts, setup complexity, notification fatigue - Phase structure: MVP (core) → expansion (usage tracking) → differentiation (prediction + sales) - All research grounded in ecosystem analysis (12+ competitors), official documentation, and production incidents Confidence: HIGH Ready for roadmap creation: YES Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
522 lines
20 KiB
Markdown
522 lines
20 KiB
Markdown
# Technology Stack
|
|
|
|
**Project:** Sage (Household Food Inventory Tracker)
|
|
**Researched:** 2026-01-27
|
|
**Focus:** Completely free, open source, self-hostable with Supabase free tier + optional Docker
|
|
|
|
## Executive Summary
|
|
|
|
Sage's tech stack prioritizes cost-free operation, multi-platform reach, and self-hosting capability. Core recommendations: **Supabase** (free tier PostgreSQL + Realtime) as primary backend with optional self-hosted Docker deployment, **Flutter + Riverpod 3.0** for shared mobile/web code, **mobile_scanner** for barcode capture, **Open Food Facts API** for product data (with local caching), and **Hive** for offline inventory caching. This combination keeps both development and operational costs at $0 while providing production-quality infrastructure.
|
|
|
|
---
|
|
|
|
## Recommended Stack
|
|
|
|
### Frontend: Flutter (Android + Web)
|
|
|
|
| Technology | Version | Purpose | Why |
|
|
|------------|---------|---------|-----|
|
|
| **Flutter** | 3.24+ | Cross-platform UI framework | Single codebase for Android, iOS, and web. Shared code reduces maintenance burden. Production-ready, widely adopted. |
|
|
| **Riverpod 3.0+** | 3.2.0+ (pub.dev) | State management | Compile-time safety, automatic retry on network errors, lowest boilerplate. New Mutations API simplifies write operations (critical for inventory updates). Better than Provider/BLoC for this domain. |
|
|
| **mobile_scanner 7.1.4+** | 7.1.4 | Barcode scanning | Lightweight, supports Android/iOS/Web. Uses ML Kit on Android, Vision Kit on iOS, ZXing on web. No dependency on paid SDKs. Supports EAN-13, UPC-A, Code 128 (via underlying ML Kit/VisionKit/ZXing). |
|
|
| **Hive 4.0+** | Latest | Local offline cache | Fast encrypted NoSQL storage (~500ms reads vs 8000ms SharedPreferences). Essential for offline inventory viewing when synced from server. AES-256 encryption built-in. |
|
|
| **go_router 12+** | Latest | Navigation | Type-safe routing for multi-screen app. Works on all platforms. |
|
|
|
|
#### Frontend Architecture
|
|
|
|
```
|
|
lib/
|
|
├── main.dart
|
|
├── providers/ # Riverpod state management
|
|
│ ├── auth_provider.dart
|
|
│ ├── inventory_provider.dart # FutureProvider wraps Supabase calls
|
|
│ ├── barcode_provider.dart # Caches barcode lookups
|
|
│ └── household_provider.dart # Multi-user household context
|
|
├── screens/ # UI layer
|
|
│ ├── inventory/
|
|
│ ├── barcode_scan/
|
|
│ ├── expiry_alerts/
|
|
│ └── shopping/
|
|
├── services/ # Business logic
|
|
│ ├── supabase_client.dart # Supabase initialization
|
|
│ ├── barcode_service.dart # Barcode scanning + caching
|
|
│ ├── open_food_facts_service.dart
|
|
│ └── notification_service.dart
|
|
└── models/ # Data classes
|
|
├── food_item.dart
|
|
├── household.dart
|
|
└── user.dart
|
|
```
|
|
|
|
**State Management Pattern:**
|
|
- Riverpod `FutureProvider` wraps Supabase calls (read-only)
|
|
- Riverpod `Notifier` + `NotifierProvider` for mutations (inventory updates, deletions)
|
|
- Hive for offline cache of recently-viewed inventory
|
|
- Automatic sync when connection restored via Riverpod's automatic retry
|
|
|
|
### Backend: Supabase (Hosted Free + Optional Self-Hosted)
|
|
|
|
| Technology | Version | Purpose | Why |
|
|
|------------|---------|---------|-----|
|
|
| **Supabase** | Latest | Backend-as-a-Service | PostgreSQL (not Firestore) = full relational power for complex queries. Free tier supports 50K MAU and unlimited API calls (critical). Realtime subscriptions included free tier (10K concurrent connections). No per-request billing like Firebase. Self-hosted Docker option available. |
|
|
| **PostgreSQL 15+** | Managed by Supabase | Relational database | SQL allows complex joins (households, users, inventory, expiry tracking). ACID transactions for multi-user safety. Row-level security (RLS) built into Supabase. Extensible with triggers/functions. |
|
|
| **Supabase Realtime** | Built-in | Multi-device sync | PostgreSQL logical replication → WebSocket broadcasts. Included free. 200 concurrent peak connections on free tier (sufficient for small households). Clients subscribe to table changes. |
|
|
| **Supabase Auth** | Built-in | User authentication | Email/password + OAuth ready. Row-level security policies tied to auth context. 50K MAU free. |
|
|
| **Supabase Storage** | Built-in | File uploads | 1 GB free. Use for food photos (optional UI feature later). |
|
|
|
|
**Free Tier Constraints (2026):**
|
|
- 500 MB database storage (sufficient for 10K+ food items with metadata)
|
|
- 2 GB database egress per month (watch for high-volume syncs)
|
|
- 200 concurrent realtime connections peak (one connection per client = 200 simultaneous users)
|
|
- **CRITICAL GOTCHA:** Free projects pause after 7 days inactivity. **Mitigation:** Add keep-alive job (GitHub Actions pings API weekly) or self-host. Users cannot avoid this on hosted free tier.
|
|
- Limited to 2 active projects (paused projects don't count)
|
|
|
|
#### Self-Hosted Option (Docker Compose)
|
|
|
|
For users wanting to avoid inactivity pausing:
|
|
|
|
```bash
|
|
# Clone Supabase repo
|
|
git clone https://github.com/supabase/supabase.git
|
|
cd supabase/docker
|
|
|
|
# Configure .env with secure keys (NEVER use defaults)
|
|
cp .env.example .env
|
|
# Edit .env: Change ANON_KEY, SERVICE_ROLE_KEY, JWT_SECRET
|
|
|
|
# Start services
|
|
docker-compose up -d
|
|
|
|
# Minimum requirements: 8GB RAM, 25GB SSD
|
|
# Remove unused services from docker-compose.yml if needed:
|
|
# - Logflare (analytics) - unnecessary
|
|
# - imgproxy (image resizing) - unnecessary early
|
|
# Keep: postgres, kong (API gateway), auth, realtime, storage
|
|
```
|
|
|
|
**Self-hosted costs:** $0/month software + infrastructure (5-30 EUR/month for basic VPS)
|
|
|
|
### Database Schema (PostgreSQL)
|
|
|
|
```sql
|
|
-- Multi-tenant: Shared database, shared schema, tenant_id discriminator
|
|
-- Simplest approach for self-hosted, scales to thousands of households
|
|
|
|
CREATE TABLE households (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE household_members (
|
|
household_id UUID REFERENCES households(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
role TEXT DEFAULT 'member', -- 'owner' or 'member'
|
|
joined_at TIMESTAMP DEFAULT NOW(),
|
|
PRIMARY KEY (household_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE food_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
household_id UUID NOT NULL REFERENCES households(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
category TEXT, -- 'produce', 'dairy', 'meat', 'pantry', etc.
|
|
barcode TEXT, -- EAN-13, UPC-A cached from Open Food Facts
|
|
quantity INT DEFAULT 1,
|
|
unit TEXT, -- 'pieces', 'kg', 'liters', 'ml'
|
|
expiry_date DATE,
|
|
location TEXT, -- 'fridge', 'freezer', 'pantry'
|
|
purchase_date DATE,
|
|
product_data JSONB, -- From Open Food Facts (name, brand, nutrition, ingredients)
|
|
added_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_food_household_expiry
|
|
ON food_items(household_id, expiry_date);
|
|
|
|
-- Barcode -> Product cache (avoids repeated API calls)
|
|
CREATE TABLE barcode_cache (
|
|
barcode TEXT PRIMARY KEY,
|
|
product_data JSONB, -- Full OFF product info
|
|
cached_at TIMESTAMP DEFAULT NOW(),
|
|
source TEXT -- 'open_food_facts', 'barcode_lookup'
|
|
);
|
|
|
|
-- Expiry alerts log (for notification dedup)
|
|
CREATE TABLE expiry_alerts_sent (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
household_id UUID NOT NULL,
|
|
food_item_id UUID NOT NULL,
|
|
alert_type TEXT, -- 'expiring_soon', 'expired'
|
|
sent_at TIMESTAMP DEFAULT NOW(),
|
|
UNIQUE(food_item_id, alert_type, DATE(sent_at))
|
|
);
|
|
|
|
-- Usage history (for shopping list AI predictor)
|
|
CREATE TABLE consumption_history (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
household_id UUID NOT NULL,
|
|
food_name TEXT NOT NULL,
|
|
category TEXT,
|
|
quantity_consumed INT,
|
|
consumed_date DATE NOT NULL,
|
|
household_id UUID REFERENCES households(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
-- Row-Level Security: Each household only sees their data
|
|
ALTER TABLE food_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "Users can only access their household's items"
|
|
ON food_items
|
|
FOR SELECT
|
|
USING (
|
|
household_id IN (
|
|
SELECT household_id FROM household_members
|
|
WHERE user_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Users can modify their household's items"
|
|
ON food_items
|
|
FOR UPDATE
|
|
USING (
|
|
household_id IN (
|
|
SELECT household_id FROM household_members
|
|
WHERE user_id = auth.uid()
|
|
)
|
|
);
|
|
```
|
|
|
|
### Data Sync Architecture
|
|
|
|
**Realtime sync (Supabase Realtime):**
|
|
|
|
```dart
|
|
// Riverpod provider subscribes to food_items table changes
|
|
final inventoryProvider = StreamProvider<List<FoodItem>>((ref) {
|
|
final userId = ref.watch(authProvider).value?.id;
|
|
if (userId == null) return const Stream.empty();
|
|
|
|
return supabase
|
|
.from('food_items')
|
|
.stream(primaryKey: ['id'])
|
|
.eq('household_id', currentHouseholdId) // RLS enforces this
|
|
.map((List<dynamic> data) =>
|
|
(data as List).map((item) => FoodItem.fromJson(item)).toList()
|
|
);
|
|
});
|
|
|
|
// UI rebuilds automatically when server sends changes
|
|
// Other family members' updates appear in real-time
|
|
```
|
|
|
|
### Barcode Scanning & Product Lookup
|
|
|
|
| Technology | Version | Purpose | Why |
|
|
|------------|---------|---------|-----|
|
|
| **mobile_scanner** | 7.1.4+ | Barcode capture | Open source, ML Kit backend, no API costs |
|
|
| **Open Food Facts API** | Public | Product database | Free, no auth required. 100 req/min for product lookup. Community-driven. Best nutritional data coverage. |
|
|
| **Barcode Lookup / EAN-Search (fallback)** | Free tier | Secondary product source | If OFF doesn't have product. Free lookup available. |
|
|
|
|
#### Barcode Caching Strategy
|
|
|
|
**Problem:** Open Food Facts rate limits = 100 req/min for product queries. With household sync, this can be hit quickly if multiple users scan same barcode.
|
|
|
|
**Solution: Client-side + Server-side cache**
|
|
|
|
```dart
|
|
// Frontend: Hive local cache (checked first)
|
|
final barcodeService = BarcodeService();
|
|
final cachedProduct = hiveBox.get(barcode);
|
|
if (cachedProduct != null && !isStale(cachedProduct)) {
|
|
return cachedProduct; // 0ms, no API call
|
|
}
|
|
|
|
// Server cache: PostgreSQL barcode_cache table (checked second)
|
|
final serverCached = await supabase
|
|
.from('barcode_cache')
|
|
.select()
|
|
.eq('barcode', barcode)
|
|
.single();
|
|
if (serverCached.exists && isRecent(serverCached)) {
|
|
return serverCached; // Store in Hive for next time
|
|
}
|
|
|
|
// Hit Open Food Facts API (last resort)
|
|
final product = await openFoodFactsApi.getProduct(barcode);
|
|
// Store in both Hive + PostgreSQL
|
|
await hiveBox.put(barcode, product);
|
|
await supabase.from('barcode_cache').insert({
|
|
'barcode': barcode,
|
|
'product_data': product.toJson(),
|
|
'source': 'open_food_facts'
|
|
});
|
|
return product;
|
|
```
|
|
|
|
**Cache invalidation:** 30-day TTL per barcode (products rarely change that fast). Manual refresh available in UI.
|
|
|
|
### Notifications & Integrations
|
|
|
|
| Technology | Version | Purpose | Why |
|
|
|------------|---------|---------|-----|
|
|
| **Supabase Edge Functions** | Built-in | Scheduled jobs | Serverless functions (TypeScript) check expiry dates nightly, trigger alerts. Free tier: 500K invocations/month. |
|
|
| **ntfy.sh** | Self-hosted or public | Push notifications | Open source, free. Send to ntfy topics or bridge to Discord/Slack. No infrastructure needed for basic version. |
|
|
| **Discord Webhooks** | Native | Optional alert channel | Teams can subscribe household alerts to Discord channel. 0 cost. |
|
|
|
|
**Expiry Alert Flow:**
|
|
|
|
```
|
|
1. Nightly Edge Function (11 PM)
|
|
→ SELECT food_items WHERE expiry_date BETWEEN TODAY AND TODAY+3 DAYS
|
|
→ Grouped by household_id
|
|
|
|
2. For each household:
|
|
→ Check dedup table (already alerted today for this item?)
|
|
→ If new: send via ntfy.sh OR Discord webhook if configured
|
|
→ Log to expiry_alerts_sent table
|
|
|
|
3. Client receives alert:
|
|
→ Notification badge on app
|
|
→ If ntfy: push to phone
|
|
→ If Discord: posted to channel
|
|
```
|
|
|
|
### Simple AI/ML for Shopping Prediction
|
|
|
|
| Technology | Purpose | Implementation |
|
|
|------------|---------|-----------------|
|
|
| **Simple statistical approach** | Predict next purchase date | No heavy ML needed. Use `consumption_history` table. When user marks item consumed, log to table. Riverpod provider computes: frequency = items consumed per month. Next purchase = last_purchase + (30 / frequency). Display on shopping list as "Usually buy X every Y days". |
|
|
| **Hive (if expanding)** | Store prediction model coefficients | Not needed initially. Keep in Firebase/Supabase if later adding regression. |
|
|
|
|
---
|
|
|
|
## Installation & Setup
|
|
|
|
### Frontend Setup
|
|
|
|
```bash
|
|
# Create Flutter project
|
|
flutter create sage --platforms android,web
|
|
|
|
cd sage
|
|
|
|
# Add dependencies
|
|
flutter pub add \
|
|
flutter_riverpod:^2.6.0 \
|
|
riverpod_generator:^2.6.0 \
|
|
build_runner:^2.4.0 \
|
|
supabase_flutter:^2.0.0 \
|
|
mobile_scanner:^7.1.4 \
|
|
hive:^2.2.3 \
|
|
hive_flutter:^1.1.0 \
|
|
go_router:^14.0.0
|
|
|
|
# Dev dependencies
|
|
flutter pub add -d \
|
|
riverpod_generator:^2.6.0 \
|
|
build_runner:^2.4.0
|
|
|
|
# Generate Riverpod code
|
|
dart run build_runner watch -d
|
|
|
|
# Run
|
|
flutter run -d android
|
|
flutter run -d chrome # Web
|
|
```
|
|
|
|
### Backend Setup (Supabase Cloud Free Tier)
|
|
|
|
1. Sign up: https://supabase.com
|
|
2. Create project (auto-pauses after 7 days inactivity)
|
|
3. Go to SQL Editor → paste schema (see Database Schema above)
|
|
4. Enable Row Level Security on all tables
|
|
5. In Flutter app:
|
|
|
|
```dart
|
|
// Initialize Supabase
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await Supabase.initialize(
|
|
url: 'https://your-project.supabase.co',
|
|
anonKey: 'your-anon-key', // Safe to commit
|
|
);
|
|
runApp(const MyApp());
|
|
}
|
|
```
|
|
|
|
### Backend Setup (Self-Hosted Docker)
|
|
|
|
```bash
|
|
# Clone Supabase
|
|
git clone https://github.com/supabase/supabase.git
|
|
cd supabase/docker
|
|
|
|
# Configure
|
|
cp .env.example .env
|
|
# Edit .env: Set secure JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY
|
|
# Example: Generate keys with: openssl rand -base64 32
|
|
|
|
# Start
|
|
docker-compose up -d
|
|
|
|
# Access:
|
|
# Supabase Studio: http://localhost:3000
|
|
# API: http://localhost:8000
|
|
# Realtime: ws://localhost:8000
|
|
```
|
|
|
|
---
|
|
|
|
## Alternatives Considered
|
|
|
|
| Category | Recommended | Alternative | Why Not Recommended |
|
|
|----------|-------------|-------------|-------------------|
|
|
| **Backend** | Supabase PostgreSQL | Firebase Firestore | Per-document billing unpredictable with realtime listeners. Firestore doesn't support complex joins needed for household + inventory queries. No self-hosting option. |
|
|
| **Backend** | Supabase PostgreSQL | Appwrite | Appwrite self-hosted is free, but requires more infrastructure overhead (Docker Swarm/K8s). Supabase's free tier is more mature. Multi-language function support in Appwrite less relevant for this project. |
|
|
| **State Mgmt** | Riverpod 3.0 | BLoC | BLoC more boilerplate, less suitable for shared web/mobile. Riverpod's mutations (new in 3.0) simplify writes. |
|
|
| **State Mgmt** | Riverpod 3.0 | Provider | Provider lacks compile-time safety and automatic retry. Riverpod newer, actively developed. |
|
|
| **Realtime Sync** | Supabase Realtime | Firebase Realtime DB | Firebase charges per read/listener. Less suitable for multi-tenant. Supabase cheaper at scale. |
|
|
| **Local Cache** | Hive | SQLite | SQLite slower for simple K-V. Hive encrypted by default. |
|
|
| **Barcode Scanning** | mobile_scanner | flutter_zxing | mobile_scanner uses native APIs (ML Kit/Vision Kit) = more accurate. ZXing pure Dart = slower on mobile. |
|
|
| **Barcode Data** | Open Food Facts | Barcode Lookup API | OFF more comprehensive nutritional data. BarcodeAPI requires paid tiers for volume. |
|
|
|
|
---
|
|
|
|
## Free Tier Gotchas & Mitigation
|
|
|
|
### Supabase Hosted Free Tier
|
|
|
|
**Gotcha 1: Inactivity Pause**
|
|
- Projects pause after 7 days without API calls
|
|
- Even a static homepage doesn't count (must hit database)
|
|
- **Mitigation:**
|
|
- Add GitHub Actions to ping API weekly: `curl https://project.supabase.co/rest/v1/tables`
|
|
- OR use self-hosted Docker (eliminates pausing)
|
|
|
|
**Gotcha 2: Storage Limit (500 MB)**
|
|
- Sufficient for 10K food items with product metadata (JSON)
|
|
- NOT sufficient if storing food photos
|
|
- **Mitigation:** Defer photos to post-MVP or use S3 integration (paid)
|
|
|
|
**Gotcha 3: Realtime Concurrent Connections (200 peak)**
|
|
- Small households fine, but if scaling to 200+ active households simultaneously = limit reached
|
|
- Each browser tab = one connection
|
|
- **Mitigation:** Implement connection pooling or move to Pro ($25/month) for 500 connections
|
|
|
|
**Gotcha 4: Database Egress (5 GB/month)**
|
|
- Each byte downloaded from API counts
|
|
- Heavy sync + large payloads = rapid consumption
|
|
- **Mitigation:** Implement pagination, compress JSON, use partial selects (`select: 'id,name,expiry_date'`)
|
|
|
|
### Firebase (Not Recommended, Included for Comparison)
|
|
|
|
**Gotcha 1: Per-Read Billing**
|
|
- Listening to query = charged per document in result set
|
|
- Small household = 50 items, listener active = 50 reads per update
|
|
- At scale: $50+/month easily
|
|
|
|
**Gotcha 2: One Free Database Per Project**
|
|
- Named databases not included in free tier
|
|
- Complex queries across databases cost double
|
|
|
|
---
|
|
|
|
## Recommended Local Development Workflow
|
|
|
|
### Development
|
|
|
|
```bash
|
|
# Terminal 1: Supabase Docker (if developing offline)
|
|
cd supabase/docker && docker-compose up
|
|
|
|
# Terminal 2: Flutter
|
|
flutter run -d chrome # Or android emulator
|
|
|
|
# Terminal 3: Run code gen
|
|
dart run build_runner watch -d
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
Create `lib/config/secrets.dart` (add to `.gitignore`):
|
|
|
|
```dart
|
|
const String SUPABASE_URL = 'https://your-project.supabase.co';
|
|
const String SUPABASE_ANON_KEY = 'your-key';
|
|
const String OPEN_FOOD_FACTS_USER_AGENT = 'Sage/1.0';
|
|
```
|
|
|
|
Or use `.env` + `flutter_dotenv` package for easier secrets management.
|
|
|
|
---
|
|
|
|
## Version Pinning Strategy
|
|
|
|
```yaml
|
|
# pubspec.yaml
|
|
dependencies:
|
|
flutter_riverpod: ^3.2.0 # Use 3.x for mutations
|
|
supabase_flutter: ^2.0.0 # Stable 2.x branch
|
|
mobile_scanner: ^7.1.4 # Latest 7.x
|
|
hive: ^2.2.3
|
|
go_router: ^14.0.0
|
|
|
|
# Lock critical versions
|
|
riverpod_generator: 2.6.0 # Exact for code generation consistency
|
|
build_runner: 2.4.0
|
|
|
|
dev_dependencies:
|
|
flutter_lints: latest
|
|
```
|
|
|
|
Lock `riverpod_generator` to ensure consistent code generation across team.
|
|
|
|
---
|
|
|
|
## Sources
|
|
|
|
### State Management
|
|
- [Flutter State Management Options (Official Docs)](https://docs.flutter.dev/data-and-backend/state-mgmt/options)
|
|
- [State Management in Flutter: 7 Approaches to Know (2026)](https://www.f22labs.com/blogs/state-management-in-flutter-7-approaches-to-know-2025/)
|
|
- [Ultimate Guide to Flutter State Management 2026: Riverpod](https://medium.com/@satishparmarparmar486/the-ultimate-guide-to-flutter-state-management-in-2026-from-setstate-to-bloc-riverpod-561192c31e1c)
|
|
|
|
### Supabase & Backend
|
|
- [Supabase Pricing 2026](https://supabase.com/pricing)
|
|
- [Supabase Review 2026: Free Tier Limits & Gotchas](https://hackceleration.com/supabase-review/)
|
|
- [Prevent Supabase Free Tier Pausing (2026 Guide)](https://shadhujan.medium.com/how-to-keep-supabase-free-tier-projects-active-d60fd4a17263)
|
|
- [Self-Hosting Supabase with Docker](https://supabase.com/docs/guides/self-hosting/docker)
|
|
- [Designing Postgres Database for Multi-tenancy](https://www.crunchydata.com/blog/designing-your-postgres-database-for-multi-tenancy)
|
|
|
|
### Firebase Comparison
|
|
- [Firebase vs Supabase for Flutter (2026)](https://medium.com/@mdazadhossain95/firebase-vs-supabase-for-flutter-apps-which-one-should-you-choose-0cf9dd897123)
|
|
- [Firestore Quotas and Limits](https://firebase.google.com/docs/firestore/quotas)
|
|
- [Firebase Free Tier Gotchas](https://supertokens.com/blog/firebase-pricing)
|
|
|
|
### Barcode & Product Data
|
|
- [Open Food Facts API Documentation](https://openfoodfacts.github.io/openfoodfacts-server/api/)
|
|
- [Open Food Facts API Rate Limits](https://openfoodfacts.github.io/documentation/docs/Product-Opener/api/)
|
|
- [Mobile Scanner 7.1.4 (pub.dev)](https://pub.dev/packages/mobile_scanner)
|
|
- [Popular Flutter Barcode Scanners](https://scanbot.io/blog/popular-open-source-flutter-barcode-scanners/)
|
|
- [Barcode Lookup APIs (2026)](https://www.barcodelookup.com/api)
|
|
|
|
### Local Storage
|
|
- [Flutter Local Storage Comparison: Hive vs SharedPreferences (2026)](https://medium.com/@taufik.amary/local-storage-comparison-in-flutter-sharedpreferences-hive-isar-and-objectbox-eb9d9ef9a712)
|
|
|
|
### Notifications
|
|
- [ntfy.sh: Open Source Push Notifications](https://ntfy.sh/)
|
|
- [ntfy Integrations & Projects](https://docs.ntfy.sh/integrations/)
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Phase 1 (MVP):** Supabase hosted free tier. Add inactivity ping job immediately.
|
|
2. **Phase 2+:** Consider self-hosted Docker if inactivity becomes pain point.
|
|
3. **Future:** Firebase alternative analysis only if Supabase free tier limits become blocker (unlikely given household scale).
|
|
4. **Scaling:** Pro tier ($25/month) if reaching 200+ concurrent households before monetization.
|