Somnia: AI-Powered Dream Journal

Joel Brown | Sep 20, 2024 min read

Somnia: When Dreams Meet AI

Ever wondered what your dreams actually mean? As a 27-year-old engineering student fascinated by the intersection of AI and human psychology, I built Somnia - a modern Flutter dream journal that uses AI to unlock the hidden patterns in your subconscious mind.

This isn’t just another note-taking app. It’s a complete system that combines dream logging, AI-powered analysis, and freemium monetization into a polished mobile experience.

The Problem I Wanted to Solve

During my time at Digicel, I learned the value of data analysis and pattern recognition in network systems. I got curious: what if we could apply similar analytical approaches to something as personal and mysterious as dreams?

Traditional dream journals are just text dumps. You write, you forget, you never really understand the patterns. I wanted something smarter - an app that could:

  • Track dreams with rich metadata (mood, lucidity, themes)
  • Analyze patterns using AI
  • Provide actionable insights for lucid dreaming
  • Actually help users understand their subconscious minds
Welcome Screen

Welcome Screen

Insights Dashboard

Insights Dashboard

The Complete System Architecture

Frontend: Modern Flutter with Riverpod

Somnia is built with Flutter, using the latest Material Design 3 principles and sophisticated state management:

// Clean, type-safe state management with Riverpod
final dreamProvider = StateNotifierProvider<DreamNotifier, AsyncValue<List<Dream>>>(
  (ref) => DreamNotifier(ref.read(supabaseServiceProvider))
);

// Real-time UI updates with proper loading states
class DreamListScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final dreamsAsync = ref.watch(dreamProvider);

    return dreamsAsync.when(
      loading: () => const LottieLoadingWidget(),
      error: (error, stack) => ErrorWidget(error.toString()),
      data: (dreams) => DreamListView(dreams: dreams),
    );
  }
}

Key Features:

  • Material Design 3 with custom dream-themed color palette
  • Riverpod state management for reactive UI updates
  • Lottie animations for smooth loading states
  • Responsive design that works across different screen sizes

Backend: Supabase with Edge Functions

Instead of building a custom backend, I leveraged Supabase for maximum efficiency:

Database Schema:

-- Core dreams table (content stored encrypted)
CREATE TABLE dreams (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  title TEXT NOT NULL,           -- AES-256 encrypted on device
  content TEXT NOT NULL,          -- AES-256 encrypted on device
  dream_date DATE NOT NULL,
  mood_rating INTEGER,            -- Encrypted metadata
  is_lucid BOOLEAN DEFAULT FALSE,
  tags TEXT[],
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- User profiles with encryption keys
CREATE TABLE user_profiles (
  id UUID REFERENCES auth.users(id) PRIMARY KEY,
  encryption_key TEXT NOT NULL,   -- Unique 256-bit AES key per user
  subscription_tier TEXT DEFAULT 'free',
  credits_remaining INTEGER DEFAULT 10,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- AI analysis results (encrypted after generation)
CREATE TABLE dream_analyses (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  dream_id UUID REFERENCES dreams(id) ON DELETE CASCADE,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  analysis_text TEXT NOT NULL,    -- Encrypted after AI processing
  insights JSONB NOT NULL,        -- Encrypted after AI processing
  model_used TEXT DEFAULT 'gpt-3.5-turbo',
  confidence_score DECIMAL(3,2) DEFAULT 0.85,
  credits_consumed INTEGER DEFAULT 1,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

AI Analysis: OpenAI Edge Functions

The real magic happens in the AI analysis system. I deployed a Supabase Edge Function that integrates with OpenAI:

Edge Function Deployment:

// supabase/functions/analyze-dream/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const { dreamText, userId } = await req.json()

  // Check user credits and subscription
  const user = await supabase
    .from('user_profiles')
    .select('subscription_tier, credits_remaining')
    .eq('id', userId)
    .single()

  if (!user.data?.credits_remaining && user.data?.subscription_tier === 'free') {
    return new Response(JSON.stringify({
      error: 'Insufficient credits'
    }), { status: 402 })
  }

  // Analyze with OpenAI
  const analysis = await openai.chat.completions.create({
    model: "gpt-3.5-turbo",
    messages: [{
      role: "system",
      content: "You are a professional dream analyst. Provide insights about themes, emotions, symbols, and lucidity probability."
    }, {
      role: "user",
      content: dreamText
    }],
    max_tokens: 200 // Cost optimization
  })

  // Store results and update credits
  // ... implementation
})

Smart Features That Actually Work

1. Intelligent Dream Logging

The app goes beyond basic text entry:

class Dream {
  final String id;
  final String title;
  final String content;
  final DateTime dreamDate;
  final int? moodRating; // 3 (sad), 6 (neutral), 9 (happy)
  final bool isLucid;
  final List<String> tags;
  final bool hasAiAnalysis;
  final String? aiAnalysisText;

  // Auto-generates titles if user doesn't provide one
  String get displayTitle {
    if (title.trim().isNotEmpty) return title;
    return DreamTitleGenerator.generateTitle(content);
  }
}

2. AI-Powered Analysis System

Each dream can be analyzed for:

  • Themes: Recurring patterns (flying, water, people)
  • Emotions: Underlying emotional content
  • Symbols: Psychological meanings of dream elements
  • Lucidity Probability: Likelihood the dream was lucid
  • Mood Correlation: How the dream relates to daily mood
  • Recommendations: Personalized tips for lucid dreaming

3. Freemium Business Model

I implemented a complete freemium system:

class UserSubscription {
  final String subscriptionTier; // 'free', 'premium', 'ultra'
  final int creditsRemaining;
  final DateTime? premiumExpiresAt;

  bool get canAnalyzeDream => isPremium || creditsRemaining > 0;

  int get monthlyAnalysisLimit {
    switch (subscriptionTier) {
      case 'free': return 10;
      case 'premium': return 50;
      case 'ultra': return -1; // Unlimited
    }
  }
}

Business Logic:

  • Free Tier: 10 AI analyses per month
  • Premium: 50 analyses + advanced insights
  • Ultra: Unlimited + priority processing
  • Cost per analysis: ~$0.001 (highly profitable)

Technical Deep Dive

State Management with Riverpod

Coming from my network engineering background, I appreciate robust state management. Riverpod provides exactly that:

class DreamNotifier extends StateNotifier<AsyncValue<List<Dream>>> {
  final SupabaseService _supabaseService;

  DreamNotifier(this._supabaseService) : super(const AsyncValue.loading()) {
    loadDreams();
  }

  Future<void> loadDreams() async {
    try {
      state = const AsyncValue.loading();
      final dreams = await _supabaseService.getDreams();
      state = AsyncValue.data(dreams);
    } catch (error, stackTrace) {
      state = AsyncValue.error(error, stackTrace);
    }
  }

  Future<void> addDream(Dream dream) async {
    // Optimistic updates for better UX
    state.whenData((dreams) {
      state = AsyncValue.data([dream, ...dreams]);
    });

    try {
      await _supabaseService.insertDream(dream);
      loadDreams(); // Refresh from server
    } catch (error) {
      // Rollback on error
      loadDreams();
      rethrow;
    }
  }
}

Modern UI with Custom Theming

The app features a carefully crafted design system:

class AppTheme {
  static const dreamPurple = Color(0xFF6B46C1);
  static const dreamCyan = Color(0xFF06B6D4);
  static const dreamNight = Color(0xFF0F172A);

  static ThemeData get lightTheme => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: dreamPurple,
      brightness: Brightness.light,
    ),
    cardTheme: CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    ),
  );
}

Performance Optimizations

Learning from my telecom experience with large-scale systems:

  1. Lazy Loading: Dreams load on-demand with pagination
  2. Image Optimization: Lottie animations instead of heavy GIFs
  3. State Persistence: User preferences saved locally
  4. Offline Support: Core functionality works without internet
  5. Memory Management: Proper disposal of resources and listeners

The AI Analysis in Action

Here’s what makes the AI system special:

Prompt Engineering

I spent considerable time optimizing the AI prompts for cost and accuracy:

You are a professional dream analyst. Analyze this dream in exactly 2-3 sentences.

Then provide:
- Themes: [list of 2-4 main themes]
- Emotions: [2-3 primary emotions detected]
- Symbols: [2-3 key symbols with brief meanings]
- Lucidity Probability: [0.0-1.0 score]
- Mood Correlation: [positive/neutral/negative]
- Recommendations: [1-2 actionable lucid dreaming tips]

Be concise but insightful. Focus on psychological accuracy.

Dream: [USER_DREAM_TEXT]

Cost Optimization

  • Token Limit: 200 tokens max per analysis (~$0.001)
  • Model Selection: GPT-3.5-turbo for cost efficiency
  • Batch Processing: Multiple dreams analyzed together for premium users
  • Smart Caching: Similar dreams get cached responses

Technical Challenges Solved

1. Real-Time Data Sync

// Supabase real-time subscriptions
final subscription = supabase
  .from('dreams')
  .stream(primaryKey: ['id'])
  .eq('user_id', userId)
  .listen((data) {
    // Update UI in real-time when data changes
    ref.read(dreamProvider.notifier).updateFromStream(data);
  });

2. Offline-First Architecture

class OfflineDreamStorage {
  Future<void> saveOfflineDream(Dream dream) async {
    final prefs = await SharedPreferences.getInstance();
    final offlineDreams = getOfflineDreams();
    offlineDreams.add(dream);
    await prefs.setString('offline_dreams', jsonEncode(offlineDreams));
  }

  Future<void> syncOfflineDreams() async {
    final offlineDreams = await getOfflineDreams();
    for (final dream in offlineDreams) {
      await supabaseService.insertDream(dream);
    }
    await clearOfflineDreams();
  }
}

3. Complex JSON Handling

class DreamAnalysis {
  final DreamInsights insights;

  factory DreamAnalysis.fromJson(Map<String, dynamic> json) {
    return DreamAnalysis(
      insights: DreamInsights.fromJson(json['insights']),
      // ... other fields
    );
  }

  // No code generation needed - clean manual serialization
}

The Publishing / OAuth Authentication Hell

Man.. getting the app approved on the Play Store is an entirely different beast from building it. I spent a solid week debugging Google Sign-In authentication issues that only appeared in production builds.

The Three-Certificate Problem

Here’s the thing about Android app signing that will absolutely wreck your deployment if you don’t understand it:

Your app gets signed THREE different times:

  1. Debug builds (flutter run) → Signed with debug certificate
  2. Local release builds (flutter build apk --release) → Signed with your upload certificate
  3. Play Store downloads → Google re-signs with their App Signing certificate

The critical realization: Even internal testing uses Google’s certificate, not yours.

# What I thought was happening:
flutter build appbundle → Upload to Play Store → Users download my signed app

# What actually happens:
flutter build appbundle → Upload to Play Store → Google strips my signature
→ Google re-signs with THEIR certificate → Users download Google-signed app

Error Code 10: The Developer’s Nightmare

PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10)

This error code haunted me for days (freal). ApiException: 10 means DEVELOPER_ERROR - Google’s polite way of saying “your OAuth configuration is wrong.”

What was wrong:

  1. I was using the Android OAuth client ID in my code instead of the Web client ID
  2. I had registered my upload certificate SHA-1, but not the Play Store signing certificate SHA-1

The Client ID Confusion

This is the part that killed me dead bro. Google Sign-In requires different client IDs for different purposes:

// WRONG - Using Android client ID (type 1)
const webClientId = '1027251168998-d74qj4ms7c3cepujnnv1g3a7ukgt9ast...';

// CORRECT - Using Web client ID (type 3)
const webClientId = '1027251168998-b3ith979iulja574ra6tc3vsvlq8fn6b...';

The serverClientId parameter needs your Web application client for backend authentication (Supabase). The Android client is matched automatically by your app’s package name and certificate SHA-1.

Hunting Down SHA-1 Certificates

I had to track down THREE different SHA-1 fingerprints:

# Debug certificate (for development)
keytool -list -v -alias androiddebugkey \
  -keystore ~/.android/debug.keystore \
  -storepass android | grep "SHA1:"
# → 67:9A:B8:AE:35:1E:B1:A7:AA:B3:84:EF:84:CA:5B:2C:D8:5A:E4:0D

# Upload certificate (for local release testing)
keytool -list -v -keystore android/app/somnia-release-key.jks \
  -alias somnia -storepass "somnia2025!" | grep "SHA1:"
# → AC:B8:FF:8D:1C:C6:42:A4:1B:B8:00:FD:85:A0:7A:48:F5:43:1F:A6

# Play Store signing certificate (the one that matters for production)
# Only available in Play Console → App integrity
# → D6:AB:49:45:FC:30:3A:15:A1:C3:E1:15:0C:1E:FA:11:51:B7:EC:8D

All three had to be registered in Firebase Console and have corresponding OAuth clients in Google Cloud Console.

Production Debugging Without Console Logs

Here’s another fun challenge: production builds don’t show console logs. When beta testers reported that Google Sign-In “just closes the window with no error,” I had zero visibility into what was happening.

Solution: Persistent Error Logging

I built a custom error tracking system that saves errors to SharedPreferences and displays them in a debug dialog:

class ErrorLogger {
  static Future<void> logError(String error, {String? context}) async {
    final prefs = await SharedPreferences.getInstance();
    final errorLog = '''
=== ERROR LOG ===
Time: ${DateTime.now().toIso8601String()}
Context: ${context ?? 'Unknown'}
Error: $error
=================
''';
    await prefs.setString('last_error_log', errorLog);
  }
}

// In the login screen - add a debug button
Widget _buildDebugButton() {
  return TextButton.icon(
    onPressed: () async {
      final lastError = await ErrorLogger.getLastError();
      // Show error dialog that users can screenshot
      showErrorDialog(lastError);
    },
    icon: Icon(Icons.bug_report),
    label: Text('View Last Error (Debug)'),
  );
}

This let beta testers screenshot the actual error and send it to me. That’s how I discovered the SHA-1 mismatch.

Step-by-Step Logging in Production

Since I couldn’t see console output, I logged every single step of the authentication flow:

static Future<bool> signInWithGoogle() async {
  try {
    await ErrorLogger.logError('STEP 1: Starting Google Sign-In');

    const webClientId = '1027251168998-b3ith979iulja574ra6tc3vsvlq8fn6b...';
    await ErrorLogger.logError('STEP 2: Clearing cache');

    await GoogleSignIn().signOut();
    await ErrorLogger.logError('STEP 3: Creating GoogleSignIn instance');

    final googleSignIn = GoogleSignIn(serverClientId: webClientId);
    await ErrorLogger.logError('STEP 4: Calling signIn()');

    final googleUser = await googleSignIn.signIn();
    if (googleUser == null) {
      await ErrorLogger.logError('STEP 5: User cancelled');
      return false;
    }

    await ErrorLogger.logError('STEP 6: Got user: ${googleUser.email}');
    // ... rest of authentication flow
  } catch (e) {
    await ErrorLogger.logError('EXCEPTION: $e');
    rethrow;
  }
}

This approach revealed that the flow was failing at “STEP 4” with ApiException: 10 - pointing directly to the OAuth configuration.

The Fix That Actually Worked

After a week of debugging, the solution was a three-step process:

  1. Get the Play Store signing SHA-1 from Play Console → App integrity
  2. Create an Android OAuth client in Google Cloud Console with that SHA-1
  3. Use the Web client ID in code (not the Android client)
// Final working configuration
static Future<bool> signInWithGoogle() async {
  // Web client ID for Supabase backend authentication
  const webClientId = '1027251168998-b3ith979iulja574ra6tc3vsvlq8fn6b.apps.googleusercontent.com';

  final GoogleSignIn googleSignIn = GoogleSignIn(
    serverClientId: webClientId, // Web client for backend
  );
  // Android client auto-matched by package + SHA-1
}

What This Taught Me About Mobile Development

The Production Reality Gap: Things that work perfectly in development can spectacularly fail in production due to signing, permissions, and platform-specific quirks you’d never encounter locally.

Documentation Isn’t Always Right: Google’s documentation doesn’t clearly explain the three-certificate system or the difference between Web and Android client IDs for hybrid authentication flows.

Build Your Own Debugging Tools: When platform limitations prevent normal debugging, you need creative solutions. The persistent error logging system saved this launch.

Testing Matrix Is Essential:

✓ Debug mode (flutter run) → Works
✓ USB install (flutter install --release) → Works
✗ Play Store download → Fails

Lesson: Test the ACTUAL distribution method early.

This experience made me appreciate why companies have dedicated DevOps and release engineering teams. Getting from “working on my machine” to “working for all users via the Play Store” is a discipline unto itself.

What I Learned Building This

1. Mobile Development Maturity

Flutter has come a long way. The developer experience with hot reload, the widget system, and package ecosystem made complex features surprisingly manageable. But the gap between development and production deployment is still very real.

2. AI Integration Reality

The hype around AI is real, but the implementation details matter. Cost optimization, prompt engineering, and error handling are crucial for production apps.

3. Privacy Isn’t Optional

Building encryption from the ground up taught me that security is easiest when designed in from day one. Retrofit encryption is painful - but with dreams being so personal, it was non-negotiable. Users deserve to know their subconscious thoughts aren’t stored in plain text on someone else’s server.

4. Freemium Business Models

Users are willing to pay for AI-powered insights, but the free tier needs to provide genuine value. The conversion funnel is all about demonstrating value before asking for payment.

5. State Management Evolution

Riverpod feels like the future of Flutter state management. It’s more complex than Provider but scales beautifully for real applications.

Development Stats

  • Development Time: 3 months (part-time while studying)
  • Lines of Code: ~5,000 Dart + 500 TypeScript (Edge Functions)
  • Dependencies: 15 packages (kept lean)
  • Test Coverage: 67% (ongoing improvement)
  • Platforms: Android, iOS, Web ready
  • Debugging Time: 1 week for OAuth/publishing issues alone
  • App Versions: 11 releases to fix Google Sign-In authentication
  • Beta Testers: Internal testing track with OAuth debugging tools

Future Roadmap

Phase 1: Enhanced AI Features

  • Dream Prediction: ML models predicting likely dream themes
  • Personalization: AI that learns individual dream patterns
  • Sleep Integration: Correlation with sleep tracking data

Phase 2: Social Features

  • Anonymous Sharing: Community insights without privacy loss
  • Dream Challenges: Gamified lucid dreaming goals
  • Expert Analysis: Connect with certified dream therapists

Phase 3: Advanced Analytics

  • Pattern Recognition: Long-term trend analysis
  • Mood Correlation: Integration with mental health tracking
  • Custom Insights: Personalized dashboard with actionable recommendations

The Tech Stack That Works

Frontend:

  • Flutter 3.8+ with Material Design 3
  • Riverpod for state management
  • Lottie for animations
  • SharedPreferences for local storage

Backend:

  • Supabase (PostgreSQL + Auth + Edge Functions)
  • OpenAI GPT-3.5-turbo for analysis
  • Row Level Security for data privacy
  • End-to-end encryption for dream content

DevOps:

  • GitHub Actions for CI/CD
  • Automated testing and deployment
  • Environment-based configuration

Why This Project Matters

Somnia isn’t just a portfolio piece - it’s a working product that solves real problems. It demonstrates:

  1. Full-Stack Mobile Development: End-to-end Flutter application with backend integration
  2. AI/ML Integration: Practical application of LLMs with cost optimization
  3. Business Model Implementation: Complete freemium system with subscription management
  4. Modern Architecture: Clean code practices and scalable patterns
  5. User-Centered Design: Solving actual user problems with thoughtful UX

As an engineering student preparing for my internship in 2026, this project showcases my ability to take complex technical concepts and turn them into polished, user-facing applications that people actually want to use.

The intersection of AI, mobile development, and human psychology fascinates me - and Somnia proves that with the right technical approach, you can build products that genuinely enhance people’s understanding of themselves.


App Screenshots

Here’s what Somnia looks like in action:

Dream List Screen

Dream List - Clean interface with mood indicators and AI badges

Create Dream Screen

Create Dream - Simple dream logging with mood and lucidity tracking

Privacy & Security: Your Dreams Stay Yours

In an age where personal data is often treated as a commodity, I built Somnia with privacy as a foundational principle. Dreams are deeply personal - they contain your fears, hopes, memories, and subconscious thoughts. That data deserves protection.

End-to-End Encryption Implementation

Every dream you write is encrypted on your device before being sent to the server:

class EncryptionService {
  // AES-256-CBC encryption with user-specific keys
  static String encryptString(String plainText, String keyString) {
    final key = encrypt.Key(base64.decode(keyString)); // 256-bit key
    final encrypter = encrypt.Encrypter(
      encrypt.AES(key, mode: encrypt.AESMode.cbc),
    );
    final encrypted = encrypter.encrypt(plainText, iv: _fixedIV);
    return encrypted.base64;
  }
}

What this means for you:

  • Device-Side Encryption: Your dreams are encrypted on your phone before being uploaded
  • Unique User Keys: Each user gets a unique 256-bit AES encryption key stored securely in their profile
  • Zero-Knowledge Architecture: Even the database administrator can’t read your dreams without your encryption key
  • Secure AI Analysis: Dreams are decrypted server-side only during AI analysis, then immediately re-encrypted

Privacy-First Architecture

┌─────────────────┐    Encrypted    ┌──────────────────┐
│  Your Device    │    ────────►    │   Supabase DB    │
│  (Plain Text)   │                 │  (Encrypted)     │
└─────────────────┘                 └──────────────────┘
        │                                    │
        │ Encryption Key                     │
        │ (Stored in Profile)                │
        │                                    │
        ▼                                    ▼
┌─────────────────┐    Decrypted    ┌──────────────────┐
│  AI Analysis    │    ◄────────    │  Edge Function   │
│  (Temporary)    │    ────────►    │  Re-encrypts     │
└─────────────────┘    Analysis     └──────────────────┘

Multi-Layer Security:

  1. Row Level Security (RLS): Supabase enforces that users can only access their own data
  2. JWT Authentication: All API requests require valid authentication tokens
  3. Encryption at Rest: Dreams stored as encrypted ciphertext in PostgreSQL
  4. No Third-Party Analytics: No Google Analytics, Facebook Pixel, or tracking SDKs
  5. Local-First Approach: Offline mode keeps dreams on your device until you’re ready to sync

Data You Control

What We Store:

  • Encrypted dream content (title, content, date)
  • Dream metadata (mood, lucidity rating) - encrypted
  • AI analysis results - encrypted after generation
  • Subscription tier and credits remaining
  • Authentication tokens (managed by Supabase Auth)

What We DON’T Store:

  • Unencrypted dream text
  • Device identifiers or advertising IDs
  • Location data
  • Browsing history or app usage patterns
  • Social media connections (beyond initial Google Sign-In)

Your Rights:

  • Export All Data: Download your complete dream journal as JSON
  • Delete Account: Permanently remove all data with one tap
  • Revoke Access: Sign out from all devices anytime
  • Data Portability: Standard format for migrating to other apps

Open Source Commitment

The encryption implementation is open source and auditable:

// lib/services/encryption_service.dart
// View the complete implementation on GitHub
static String generateKey() {
  final key = encrypt.Key.fromSecureRandom(32); // 256-bit secure random
  return base64.encode(key.bytes);
}

You can verify that we’re using industry-standard AES-256-CBC encryption with properly random keys. No backdoors, no key escrow, no compromises.

Compliance & Best Practices

  • GDPR Compliant: Data minimization, user consent, right to erasure
  • No Selling Data: Your dreams are never sold to advertisers or data brokers
  • Minimal Retention: Deleted dreams are purged within 30 days
  • Secure Credentials: All API keys stored in environment variables, never in code
  • Regular Security Audits: Automated dependency scanning and security testing

Why This Matters

Many apps claim “privacy” but still send your data in plain text to their servers. With Somnia:

  1. Even if our database is compromised, attackers only get encrypted ciphertext
  2. Even if Supabase employees look at the data, they see gibberish without your key
  3. Even during AI analysis, dreams are decrypted temporarily and never logged

This is the same encryption standard used by:

  • Signal for end-to-end encrypted messaging
  • ProtonMail for secure email
  • 1Password for password management

Your subconscious thoughts deserve the same protection as your passwords.


Current Status & Availability

After overcoming the OAuth authentication challenges, Somnia is now live on the Google Play Store in internal testing. The app successfully handles:

  • ✅ Google Sign-In authentication across all distribution channels
  • ✅ AI-powered dream analysis with freemium monetization
  • ✅ Real-time data synchronization with Supabase
  • ✅ End-to-end encryption for all dream content
  • ✅ Privacy-first architecture with zero-knowledge storage
  • ✅ Persistent error logging for production debugging
  • ✅ Multi-certificate signing for Play Store distribution

The journey from “working on my machine” to “working for beta testers” taught me more about production mobile development than any tutorial could. This project demonstrates not just the ability to build features, but to troubleshoot and deploy them in the real world with genuine respect for user privacy.

Technical Highlights:

  • Complete Flutter application with Riverpod state management
  • Supabase backend with Edge Functions and PostgreSQL
  • OpenAI integration with cost-optimized prompts
  • End-to-end encryption using AES-256-CBC
  • OAuth 2.0 authentication with multiple signing certificates
  • Production-grade error tracking and logging system
  • Freemium business model with subscription management
  • GDPR-compliant data handling and privacy controls

Full technical documentation available, including the complete OAuth troubleshooting guide and encryption implementation details. Open to discussing the technical architecture or exploring collaboration opportunities.

-|