Agnonymous is a Flutter-based web and mobile application designed as a secure and anonymous platform for the agricultural sector. Users can anonymously post reports about agricultural issues, companies, and practices, which are then validated by the community through a real-time voting and commenting system.
- New Categories: Added "General" 📝 and "Other" 🔗 categories to complement existing agricultural categories
- Data Migration: Successfully imported 97 legacy posts from original Agnonymous platform
- Enhanced Post Creation: Improved validation with more flexible requirements
- Performance Optimization: Added pagination to handle large datasets efficiently
- Web App Fixes: Resolved loading issues and improved compatibility
- App Loading Failures: Fixed web app crashes when loading large datasets (97+ posts)
- Environment Variable Issues: Implemented JavaScript interop to properly read Supabase credentials
- HTML Structure Problems: Simplified web/index.html for better browser compatibility
- Post Validation: Reduced minimum requirements to be more user-friendly
- 97 imported posts from legacy platform with proper categorization
- 11 categories available: Farming, Livestock, Ranching, Crops, Markets, Weather, Chemicals, Equipment, Politics, General, Other
- 30-post pagination implemented for optimal performance
- Frontend: Flutter (Web & Mobile)
- Backend: Supabase (Database, Auth, Real-time)
- State Management: Flutter Riverpod
- Hosting: Firebase Hosting
- Database: PostgreSQL (via Supabase)
- Real-Time Post Feed: Live feed displays posts with pagination (30 posts max)
- Post Creation: Complete form with category selection and validation
- Real-Time Comments: Instant comment system for all posts
- Real-Time Voting: Truth meter with "True," "Partial," or "False" voting
- Category Filtering: Filter posts by agricultural categories
- Search Functionality: Search posts by title and content
- Responsive Design: Works across web and mobile platforms
- AdSense Integration: Site verification and monetization ready
- Title: Minimum 1 character, Maximum 100 characters
- Content: Minimum 10 characters, Maximum 2000 characters
- Category: Required selection from predefined list
- Farming 🚜 - General farming practices and issues
- Livestock 🐄 - Animal husbandry and cattle-related posts
- Ranching 🤠 - Ranch management and operations
- Crops 🌾 - Crop production, seeds, and harvest
- Markets 📈 - Agricultural markets and pricing
- Weather 🌦️ - Weather impacts and forecasting
- Chemicals 🧪 - Pesticides, fertilizers, and agricultural chemicals
- Equipment 🔧 - Machinery and agricultural technology
- Politics 🏛️ - Agricultural policy and regulations
- General 📝 - General agricultural discussions
- Other 🔗 - Miscellaneous topics
- Live Web App: https://agnonymousbeta.web.app
- Firebase Console: https://console.firebase.google.com/project/agnonymousbeta
- Custom Domain: Prepared for agnonymous.news (DNS setup pending)
Problem: After importing 97 posts, the web app would hang or crash when trying to load all posts simultaneously.
Root Causes:
- No pagination - app tried to load all 97 posts at once
- Supabase credentials not properly accessible via JavaScript
- Complex HTML structure with Flutter bootstrap causing loading delays
Solutions Implemented:
- Added Pagination: Limited initial load to 30 posts with
.limit(30)inmain.dart:150 - JavaScript Interop: Added
dart:jsimport to readwindow.ENVvariables from HTML - Simplified HTML: Streamlined
web/index.htmlto use directmain.dart.jsloading - Enhanced Error Handling: Added comprehensive logging for debugging
Issue: App couldn't read Supabase credentials from environment variables
Resolution: Implemented fallback system in main.dart:
- Try
window.ENV(Firebase/web deployment) - Fallback to
dart-define(production builds) - Fallback to
.envfile (development)
- Source: Original Agnonymous platform database
- Total Posts: 97 historical posts imported
- Date Range: Posts from June 2025 - July 2025
- Categorization: All posts categorized using new 11-category system
- Anonymous Users: Migrated with
user_migrated_[0-96]IDs
Legacy posts were analyzed and categorized based on content:
- Agricultural supply chain issues → Markets
- Chemical/pesticide concerns → Chemicals
- Equipment and technology → Equipment
- Policy and regulatory → Politics
- General farming practices → Farming
- Livestock operations → Livestock
- And more...
- Mobile App: Not yet deployed to app stores (Flutter web only)
- Load More: No "load more" button for pagination (only shows latest 30)
- User Profiles: All users are anonymous, no persistent profiles
- Image Upload: Not implemented in current version
- Push Notifications: Not configured
- Infinite scroll or "Load More" functionality
- Mobile app deployment (iOS/Android)
- Image/file attachment support
- Advanced search and filtering
- User reputation system
- Content moderation tools
- Flutter SDK (latest stable)
- Firebase CLI
- Git
# Clone repository
git clone https://github.com/Bushels/agnonymous_beta.git
cd agnonymous_beta
# Install dependencies
flutter pub get
# Run web development server
flutter run -d chrome
# Build for production
flutter build web
# Deploy to Firebase
firebase deploy --only hostingThe app uses a fallback system for configuration:
- Production (Firebase): Credentials in
web/index.htmlaswindow.ENV - Development: Create
.envfile with:SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=your-anon-key
lib/main.dart- Main app with providers and UIlib/create_post_screen.dart- Post creation formweb/index.html- Web app HTML with credentialsfirebase.json- Firebase hosting configurationpubspec.yaml- Flutter dependencies
- Load More Posts: Implement pagination beyond first 30 posts
- Counter Accuracy: Fix live counters for posts, votes, and comments
- Mobile Optimization: Improve mobile responsive design
- Performance: Optimize real-time updates for better performance
- Mobile App Deployment: Build and deploy to iOS/Android app stores
- Enhanced Search: Add advanced filtering and search capabilities
- User Experience: Improve post creation and interaction flows
- Content Management: Add moderation and reporting features
- Community Features: User reputation and community governance
- Data Analytics: Trending topics and agricultural insights
- Integration: Connect with agricultural data sources and APIs
- Monetization: Expand AdSense and explore agricultural partnerships
A new function, get_global_stats, needs to be created in the Supabase SQL Editor. This function will efficiently query the database to get the total counts of posts, votes, and comments.
-- This function should be added to the Supabase SQL Editor
CREATE OR REPLACE FUNCTION get_global_stats()
RETURNS TABLE (
total_posts BIGINT,
total_votes BIGINT,
total_comments BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
(SELECT COUNT(*) FROM posts) AS total_posts,
(SELECT COUNT(*) FROM truth_votes) AS total_votes,
(SELECT COUNT(*) FROM comments) AS total_comments;
END;
$$ LANGUAGE plpgsql;
Step 2: Create a New Riverpod Provider in FlutterA new StreamProvider named globalStatsProvider should be created in main.dart. This provider will:Call the get_global_stats function to get the initial counts.Establish a real-time listener that re-fetches the stats whenever a new post, vote, or comment is created.Step 3: Update the UI WidgetsGlobalStatsHeader Widget: This widget must be converted to a ConsumerWidget to watch the new globalStatsProvider and display the live data
Supabase SQL
-- #############################################################################
-- ## COMPLETE & HARDENED SUPABASE SETUP FOR AGNONYMOUS APP
-- #############################################################################
-- Drop existing resources to ensure a clean slate
DROP TABLE IF EXISTS comments CASCADE;
DROP TABLE IF EXISTS truth_votes CASCADE;
DROP TABLE IF EXISTS posts CASCADE;
DROP FUNCTION IF EXISTS get_post_vote_stats(UUID);
DROP FUNCTION IF EXISTS cast_user_vote(UUID, TEXT, TEXT);
DROP FUNCTION IF EXISTS check_vote_rate();
-- #############################################################################
-- ## 1. CREATE TABLES
-- #############################################################################
CREATE TABLE posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
anonymous_user_id TEXT NOT NULL,
title TEXT,
content TEXT NOT NULL,
category TEXT NOT NULL,
subcategory TEXT,
location TEXT,
topics TEXT[] DEFAULT '{}',
evidence_urls TEXT[] DEFAULT '{}',
truth_score INTEGER DEFAULT 0,
vote_count INTEGER DEFAULT 0,
comment_count INTEGER DEFAULT 0,
flag_count INTEGER DEFAULT 0,
is_hidden BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE truth_votes (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
anonymous_user_id TEXT NOT NULL,
vote_type TEXT CHECK (vote_type IN ('thumbs_up', 'partial', 'thumbs_down', 'funny')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(post_id, anonymous_user_id)
);
CREATE TABLE comments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
anonymous_user_id TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- #############################################################################
-- ## 2. ADD PERFORMANCE INDEXES
-- #############################################################################
CREATE INDEX IF NOT EXISTS idx_truth_votes_post_id ON truth_votes(post_id);
CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id);
-- #############################################################################
-- ## 3. CREATE FUNCTIONS
-- #############################################################################
CREATE OR REPLACE FUNCTION get_post_vote_stats(post_id_in UUID)
RETURNS TABLE (
thumbs_up_votes BIGINT,
partial_votes BIGINT,
thumbs_down_votes BIGINT,
funny_votes BIGINT,
total_votes BIGINT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*) FILTER (WHERE vote_type = 'thumbs_up') AS thumbs_up_votes,
COUNT(*) FILTER (WHERE vote_type = 'partial') AS partial_votes,
COUNT(*) FILTER (WHERE vote_type = 'thumbs_down') AS thumbs_down_votes,
COUNT(*) FILTER (WHERE vote_type = 'funny') AS funny_votes,
COUNT(*) AS total_votes
FROM truth_votes
WHERE post_id = post_id_in;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION cast_user_vote(post_id_in UUID, user_id_in TEXT, vote_type_in TEXT)
RETURNS VOID AS $$
BEGIN
IF vote_type_in IS NULL THEN
DELETE FROM truth_votes
WHERE post_id = post_id_in AND anonymous_user_id = user_id_in;
ELSE
INSERT INTO truth_votes (post_id, anonymous_user_id, vote_type)
VALUES (post_id_in, user_id_in, vote_type_in)
ON CONFLICT (post_id, anonymous_user_id)
DO UPDATE SET vote_type = vote_type_in;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION check_vote_rate() RETURNS TRIGGER AS $$
BEGIN
IF (
SELECT COUNT(*)
FROM truth_votes
WHERE anonymous_user_id = NEW.anonymous_user_id
AND created_at > NOW() - INTERVAL '1 minute'
) >= 5 THEN
RAISE EXCEPTION 'Vote rate limit exceeded. Please try again later.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- #############################################################################
-- ## 4. SETUP TRIGGERS AND SECURITY
-- #############################################################################
-- VOTE RATE LIMITING TRIGGER
DROP TRIGGER IF EXISTS trg_vote_rate_limit ON truth_votes;
CREATE TRIGGER trg_vote_rate_limit
BEFORE INSERT ON truth_votes
FOR EACH ROW EXECUTE FUNCTION check_vote_rate();
-- ENABLE ROW LEVEL SECURITY
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE truth_votes ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Allow public read access to posts" ON posts;
DROP POLICY IF EXISTS "Allow anonymous users to create posts" ON posts;
DROP POLICY IF EXISTS "Allow public read access to votes" ON truth_votes;
DROP POLICY IF EXISTS "Allow anonymous users to cast votes" ON truth_votes;
DROP POLICY IF EXISTS "Users can only update or delete their own vote" ON truth_votes;
DROP POLICY IF EXISTS "Users can only delete their own vote" ON truth_votes;
DROP POLICY IF EXISTS "Allow public read access to comments" ON comments;
DROP POLICY IF EXISTS "Allow anonymous users to create comments" ON comments;
-- SECURE RLS POLICIES (CORRECTED)
CREATE POLICY "Allow public read access to posts" ON posts
FOR SELECT USING (true);
CREATE POLICY "Allow authenticated users to create posts"
ON posts FOR INSERT TO authenticated WITH CHECK
(auth.uid()::text = anonymous_user_id);
CREATE POLICY "Allow public read access to votes" ON
truth_votes FOR SELECT USING (true);
CREATE POLICY "Allow authenticated users to cast votes" ON
truth_votes FOR INSERT TO authenticated WITH CHECK
(auth.uid()::text = anonymous_user_id);
CREATE POLICY "Users can update their own vote" ON
truth_votes FOR UPDATE TO authenticated USING
(auth.uid()::text = anonymous_user_id);
CREATE POLICY "Users can delete their own vote" ON
truth_votes FOR DELETE TO authenticated USING
(auth.uid()::text = anonymous_user_id);
CREATE POLICY "Allow public read access to comments" ON
comments FOR SELECT USING (true);
CREATE POLICY "Allow authenticated users to create
comments" ON comments FOR INSERT TO authenticated WITH
CHECK (auth.uid()::text = anonymous_user_id);
-- #############################################################################
-- ## 5. GRANT PERMISSIONS
-- #############################################################################
GRANT EXECUTE ON FUNCTION get_post_vote_stats(UUID) TO anon;
GRANT EXECUTE ON FUNCTION cast_user_vote(UUID, TEXT, TEXT) TO anon;
GRANT ALL ON posts TO anon;
GRANT ALL ON truth_votes TO anon;
GRANT ALL ON comments TO anon;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO anon;
-- #############################################################################
-- ## SETUP COMPLETE!
-- #############################################################################
Second Supabase Function
ALTER PUBLICATION supabase_realtime ADD TABLE posts;
Third Supabase Function
ALTER PUBLICATION supabase_realtime ADD TABLE truth_votes;