Skip to main content

Overview

Vitu follows a single-file architecture pattern where the entire application is contained in main.dart (6,698 lines). This is an unconventional but deliberate design choice optimized for rapid development and simplified deployment.
All application code resides in a single file: lib/main.dart. This includes UI components, business logic, data models, and service integrations.

Technology Stack

Flutter SDK
string
default:"^3.10.8"
Core framework for cross-platform development
Material 3
UI Framework
Modern Material Design 3 theming with custom seed colors
Hive
NoSQL Database
Local-first storage using hive (^2.2.3) and hive_flutter (^1.1.0)
Gemini AI
AI Service
Google Generative AI (google_generative_ai ^0.4.0) for food analysis and recipe recommendations

Key Dependencies

image_picker: ^1.1.2      # Camera/gallery access
path_provider: ^2.1.4     # File system paths
fl_chart: ^0.68.0         # Data visualization
geolocator: ^12.0.0       # Location tracking
sensors_plus: ^5.0.0      # Accelerometer for step counting

Application Entry Point

The app initializes Hive boxes before launching the UI:
lib/main.dart:21-31
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  await Hive.openBox('users');
  await Hive.openBox('user_settings');
  await Hive.openBox('daily_exercise');
  await Hive.openBox('hydration_logs');
  await Hive.openBox('daily_hydration_summary');
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  runApp(const MyApp());
}
The daily_sleep box is opened lazily in SleepScreen._initSleep() rather than at app startup.

Architecture Pattern: StatefulWidget

Every screen in Vitu uses the StatefulWidget pattern for managing local state and lifecycle:

Screen Structure

class HomeScreen extends StatefulWidget {
  final Brightness brightness;
  final Color seedColor;
  final String? fontFamily;
  
  const HomeScreen({
    super.key,
    required this.brightness,
    required this.seedColor,
    this.fontFamily,
  });
  
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // Local state variables
  File? _photo;
  bool _analyzing = false;
  
  @override
  void initState() {
    super.initState();
    // Initialize data, start listeners
  }
  
  @override
  void dispose() {
    // Clean up resources
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    // Build UI
  }
}

State Management Strategy

Local State

Each screen manages its own state using setState(). No global state management library is used.

Persistent Data

All persistent data is stored in Hive boxes and retrieved synchronously on screen load.
The VidaPlusApp widget manages bottom navigation with an IndexedStack to preserve state:
lib/main.dart:319-420
class VidaPlusApp extends StatefulWidget {
  const VidaPlusApp({super.key});
  @override
  State<VidaPlusApp> createState() => _VidaPlusAppState();
}

class _VidaPlusAppState extends State<VidaPlusApp> {
  int _index = 0;
  Brightness _brightness = Brightness.light;
  Color _seedColor = const Color(0xFF80CBC4);
  String? _fontFamily;
  bool _followLocation = false;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: background,
      body: IndexedStack(index: _index, children: pages),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index,
        onTap: (i) => setState(() => _index = i),
        items: items,
      ),
    );
  }
}
IndexedStack keeps all screens alive in memory, preserving scroll positions and local state when switching tabs.

Theming System

Vitu implements a dynamic theming system where users can customize:
brightness
Brightness
default:"light"
Light or dark mode (stored in UserSettings)
seedColor
Color
default:"0xFF80CBC4"
Material 3 seed color for dynamic color schemes (ARGB int)
fontFamily
string
Custom font family: null, 'serif', or other system fonts

Theme Application

The root MyApp widget uses a static theme, but individual screens receive theme parameters as constructor props:
lib/main.dart:39-54
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Vitu',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.lime),
        useMaterial3: true,
      ),
      home: const SplashScreen(),
    );
  }
}
Each screen builds custom text styles using the theme parameters:
final heading = TextStyle(
  fontSize: 22,
  fontWeight: FontWeight.w800,
  color: isDark ? const Color(0xFFEDEDED) : const Color(0xFF111111),
  fontFamily: widget.fontFamily,
);

Screen Lifecycle

1. Splash Screen

  • Displays animated logo with scale and fade transitions
  • Checks for existing user session in Hive
  • Navigates to either VidaPlusApp or LoginRegisterScreen after 2.2 seconds

2. Authentication Flow

1

Login/Register

User enters credentials in LoginRegisterScreen with two tabs
2

Validation

Form validates email format and password presence
3

Storage

User data saved to Hive box with key user:{email}
4

Session

Current user email stored in Hive under key currentUserEmail

3. Main Application

Five screens accessible via bottom navigation:
  • Food image capture and AI analysis
  • Nutritional breakdown with pie chart
  • AI-generated recipe recommendations

Key Architectural Decisions

Single-File Design

  • Zero import complexity: No circular dependencies or import management
  • Rapid prototyping: All code in one searchable file
  • Simplified deployment: Single compilation unit
  • Easy debugging: Ctrl+F to find any function/class
  • Limited scalability: Difficult to maintain beyond ~10k lines
  • No code reusability: Hard to extract shared components
  • Merge conflicts: High risk in team environments
  • IDE performance: Large files can slow down autocomplete
  • Testing challenges: Unit testing requires testing entire file

No State Management Library

Decision: Use built-in setState() instead of Provider, Riverpod, or BLoC. Rationale:
  • App has minimal shared state (theme settings only)
  • Each screen operates independently
  • Data is loaded synchronously from Hive on each screen mount
  • Avoids external dependency and learning curve

Local-First with Hive

Decision: Use Hive NoSQL instead of SQLite or cloud database. Rationale:
  • No internet required for core functionality
  • Zero boilerplate (no migrations, schemas)
  • Synchronous reads for instant UI updates
  • Type-safe with Dart’s dynamic typing
Security Concern: User passwords are stored in plaintext in Hive. This is acceptable for local-only apps but should use hashing (e.g., bcrypt) if adding cloud sync.

External AI Integration

Decision: Use Google Gemini API instead of on-device ML models. Rationale:
  • No model training or maintenance required
  • Access to state-of-the-art vision and language models
  • Handles complex food recognition and recipe generation
  • Trade-off: Requires internet and API key management

Code Organization Within main.dart

Despite being a single file, code follows a logical structure:
main.dart (6,698 lines)
├── Imports & Utilities (lines 1-38)
├── App Entry Point (lines 39-54)
├── Data Models (lines 56-200)
│   ├── User class
│   ├── UserSettings class
│   └── Helper functions
├── Main Navigation (lines 319-420)
├── Screens (lines 448-4500+)
│   ├── HomeScreen (nutrition)
│   ├── ExerciseScreen
│   ├── HydrationScreen
│   ├── SleepScreen
│   └── SettingsScreen
├── Authentication (lines 3905-4300+)
│   ├── SplashScreen
│   ├── LoginRegisterScreen
│   └── Forms
└── Shared Widgets (scattered)
    ├── PressableScale
    └── Custom components

Performance Considerations

Positive

  • Hive provides O(1) key-value lookups
  • IndexedStack avoids rebuilding screens
  • Charts use efficient fl_chart library
  • Images stored on disk, not in memory

Concerns

  • Large file size increases compile time
  • No code splitting or lazy loading
  • All screens loaded at app start
  • AI requests block UI thread during analysis

Extension Points

To add new functionality:
1

Add New Screen

Create a new StatefulWidget class in main.dart
2

Add to Navigation

Add a new BottomNavigationBarItem and page in VidaPlusApp
3

Create Hive Box

Open a new box in main() if persistence is needed
4

Define Data Model

Create a class with toMap() and fromMap() methods

Migration Path

If the app grows beyond this architecture:
  1. Extract data layer: Move Hive logic to separate repository classes
  2. Add state management: Introduce Riverpod or BLoC for complex flows
  3. Modularize screens: Split into feature modules (auth, nutrition, exercise, etc.)
  4. Add testing: Extract business logic into testable service classes
  5. Implement CI/CD: Set up automated testing and deployment pipelines

Next Steps

Learn about the Database Schema and AI Integration

Build docs developers (and LLMs) love