← Back to Blog

RightNow Technical Deep Dive: Building in Public

As an indie developer building RightNow in public, I want to share the technical decisions behind the app and what I’ve learned from them.

The Numbers: A Growing Codebase

For a sense of scale: the app is somewhere north of 40,000 lines of Dart, with roughly another 10,000 lines of tests. Big enough that architecture decisions matter; small enough that one person can still hold it in their head. Mostly.

Architecture: Why Offline-First?

Early on, I made a crucial decision: offline-first architecture. Here’s why:

graph TD
    A[User Action] --> B[Local Update]
    B --> C[Instant UI Change]
    B --> D[Queue for Sync]
    D --> E{Online?}
    E -->|Yes| F[Sync to Cloud]
    E -->|No| G[Wait & Retry]
    G --> E

Benefits I’ve Experienced

  1. Lightning fast UI - No waiting for network calls
  2. Works anywhere - Airplane, subway, poor connection
  3. No loading spinners - Everything feels instant
  4. Reliability - Network issues don’t break the app

The Trade-offs

State Management Evolution

I went through several iterations before landing on the current architecture:

Version 1: Simple Provider

Widget → Provider → Database

Problem: Too much rebuilding, poor performance

Version 2: Repository Pattern

Widget → Provider → Repository → Database

Better: Separation of concerns, easier testing

Version 3: Current Architecture

graph LR
    A[UI Widget] --> B[Riverpod Provider]
    B --> C[Repository Interface]
    C --> D[Hive Repository]
    C --> E[Mock Repository]
    D --> F[Local Database]
    E --> G[Test Data]

This pattern has paid for itself many times over:

The Testing Journey

Testing wasn’t an afterthought - it evolved with the app:

graph TD
    A[Manual Testing Only] --> B[Basic Widget Tests]
    B --> C[Repository Tests]
    C --> D[Integration Tests]
    D --> E[Full Test Suite]
    
    style A fill:#ff6b6b
    style B fill:#ffa06b
    style C fill:#ffda6b
    style D fill:#9fd86b
    style E fill:#6bd8a0

Current Test Coverage

Testing Lessons Learned

  1. Start simple - Even basic tests catch bugs
  2. Mock at boundaries - Repository interfaces are perfect
  3. Test user journeys - Not just individual functions
  4. Keep tests fast - Or you won’t run them

Feature Development Process

Here’s how I typically build a new feature:

graph TD
    A[User Feedback] --> B[Design in Figma]
    B --> C[Create Data Model]
    C --> D[Build Repository]
    D --> E[Write Tests]
    E --> F[Create UI]
    F --> G[Connect Provider]
    G --> H[Manual Testing]
    H --> I[Ship It!]

Real Example: Task Grooming Games

The gamification feature went through this exact process:

  1. Feedback: “Grooming backlog is boring”
  2. Design: Swipe-based card games
  3. Model: GroomingData with score tracking
  4. Repository: Separate from main task repo
  5. Tests: Swipe gesture handling
  6. UI: Custom game widgets
  7. Provider: Game state management
  8. Testing: Friends and family tried it and sent feedback
  9. Shipped: In 3 days

Performance Optimizations

Building for mobile taught me about performance the hard way:

Problem 1: Slow List Scrolling

Solution: Implemented virtualization

graph LR
    A[1000 Tasks] --> B[Render Only Visible]
    B --> C[~20 Widgets]
    C --> D[Smooth 60fps]

Problem 2: Expensive Rebuilds

Solution: Selective watching with Riverpod

// Instead of watching entire task list
ref.watch(taskListProvider.select((tasks) => tasks.length))

Problem 3: Memory Leaks

Solution: Proper disposal and lifecycle management

Platform Challenges

Supporting multiple platforms brought unique challenges:

graph TD
    A[Flutter App] --> B[Android]
    A --> C[iOS]
    A --> D[Web]
    A --> E[Desktop]
    
    B --> F[Play Store]
    C --> G[App Store]
    D --> H[Firebase Hosting]
    E --> I[Direct Download]

Platform-Specific Issues

Key Technical Decisions

✅ Decisions I’m Happy With

  1. Flutter - Write once, run everywhere actually works
  2. Offline-first - Users love the speed
  3. Hive database - Simple, fast, reliable
  4. Repository pattern - Made everything testable
  5. Riverpod - Powerful state management

🤔 Decisions I’d Reconsider

  1. Not using code generation earlier - Would have saved time
  2. Custom animation system - Should have used built-in
  3. Over-engineering early - YAGNI is real
  4. Delaying tests - Should have started day one

Deployment Pipeline

Getting the app to users efficiently:

graph LR
    A[Git Push] --> B[GitHub Actions]
    B --> C[Run Tests]
    C --> D[Build Apps]
    D --> E[Deploy Web]
    D --> F[Upload to Stores]
    F --> G[Users!]

But when GitHub Actions minutes ran out, I adapted:

:: Local build script
flutter test
flutter build web
firebase deploy
flutter build appbundle
:: then upload the bundle to the Play Console

Lessons for Other Indie Developers

1. Start Shipping Early

Don’t wait for perfection. My first version had 5 features, not 50.

2. Use Your Own App

I’m my own harshest tester. The best fixes came from being annoyed by my own software daily.

3. Automate Everything

Time spent on automation pays back 10x.

4. Test the Critical Path

You don’t need 100% coverage, just test what matters.

5. Document as You Go

This blog post was easier because I kept notes.

What’s Next?

Technically speaking, the areas I keep poking at, no promises:

Join the Journey

Building in public means sharing the good and the bad. Follow along:

Have questions? Want to share your experience? I’d love to hear from you.


*Previous: Who RightNow is For ← Next: Introduction to RightNow →*

*Learn more: RightNow App Development Privacy Policy Delete Account*