Skip to content

Latest commit

 

History

History
507 lines (388 loc) · 9.97 KB

File metadata and controls

507 lines (388 loc) · 9.97 KB

Development Guide

This guide covers development practices, architecture decisions, and contribution guidelines.

Development Environment

Recommended Tools

  • IDE: VS Code or Android Studio

  • VS Code Extensions:

    • Flutter
    • Dart
    • Flutter Riverpod Snippets
    • Error Lens
    • GitLens
  • Chrome DevTools: For debugging and performance profiling

Code Style

Follow the official Dart Style Guide.

Key conventions:

  • Use lowerCamelCase for variables and functions
  • Use UpperCamelCase for classes
  • Use snake_case for file names
  • Maximum line length: 80 characters
  • Use trailing commas for better formatting

Formatting

# Format all Dart files
flutter format .

# Check formatting
flutter format --set-exit-if-changed .

Linting

# Analyze code
flutter analyze

# Fix auto-fixable issues
dart fix --apply

Architecture

State Management: Riverpod

We use Riverpod for state management because:

  • Type-safe
  • Testable
  • No BuildContext required
  • Better performance than Provider

Key providers:

  • userProgressProvider: User stats and progress
  • currentLevelProvider: Current level (computed)
  • totalXPProvider: Total XP (computed)
  • currentStreakProvider: Current streak (computed)

Data Persistence: Hive

Hive is used for local storage:

  • Fast and lightweight
  • Type-safe with code generation
  • No native dependencies

Boxes:

  • user_progress: User statistics
  • lessons: Cached lesson data
  • modules: Module data
  • achievements: Achievement status

Project Structure

lib/
├── main.dart                    # Entry point
├── models/                      # Data models
├── providers/                   # Riverpod providers
├── screens/                     # Full-screen views
├── widgets/                     # Reusable UI components
├── services/                    # Business logic
├── data/                        # Static/generated content
└── utils/                       # Helper functions

Naming Conventions

Files:

  • Screens: *_screen.dart
  • Widgets: *_widget.dart or descriptive name
  • Services: *_service.dart
  • Providers: *_provider.dart
  • Models: descriptive name (e.g., user_progress.dart)

Classes:

  • Screens: *Screen
  • Widgets: Descriptive names
  • Services: *Service
  • Providers: *Provider or *Notifier

Adding New Features

Adding a New Screen

  1. Create screen file in lib/screens/
import 'package:flutter/material.dart';

class NewScreen extends StatelessWidget {
  const NewScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('New Screen')),
      body: Center(child: Text('New Screen')),
    );
  }
}
  1. Add route in main.dart
routes: {
  '/new-screen': (context) => const NewScreen(),
}
  1. Navigate to screen
Navigator.pushNamed(context, '/new-screen');

Adding a New Widget

  1. Create widget file in lib/widgets/
import 'package:flutter/material.dart';

class CustomWidget extends StatelessWidget {
  final String title;
  final VoidCallback onTap;

  const CustomWidget({
    super.key,
    required this.title,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: Text(title),
    );
  }
}
  1. Use widget in screens
CustomWidget(
  title: 'Click me',
  onTap: () => print('Tapped!'),
)

Adding a New Model

  1. Create model in lib/models/
import 'package:equatable/equatable.dart';

class NewModel extends Equatable {
  final String id;
  final String name;

  const NewModel({
    required this.id,
    required this.name,
  });

  @override
  List<Object?> get props => [id, name];
}
  1. For Hive persistence, add annotations:
import 'package:hive/hive.dart';

part 'new_model.g.dart';

@HiveType(typeId: 1)
class NewModel extends HiveObject {
  @HiveField(0)
  String id;

  @HiveField(1)
  String name;

  NewModel({required this.id, required this.name});
}
  1. Generate code
flutter pub run build_runner build

Adding a New Provider

  1. Create provider in lib/providers/
import 'package:flutter_riverpod/flutter_riverpod.dart';

class NewNotifier extends StateNotifier<int> {
  NewNotifier() : super(0);

  void increment() => state++;
}

final newProvider = StateNotifierProvider<NewNotifier, int>((ref) {
  return NewNotifier();
});
  1. Use in widget
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(newProvider);

    return Text('Count: $count');
  }
}

Adding AI-Generated Content

  1. Use AIContentService
final aiService = AIContentService();

// Generate a lesson
final lesson = await aiService.generateLesson(
  moduleId: 'my_module',
  topic: 'Introduction to Python Lists',
  difficulty: DifficultyLevel.beginner,
  type: LessonType.tutorial,
);

// Generate questions
final questions = await aiService.generateQuestions(
  topic: 'Python Lists',
  difficulty: DifficultyLevel.beginner,
  count: 5,
);
  1. Save to storage
await StorageService.saveLesson(lesson.id, lesson.toJson());

Adding New Achievements

  1. Add to learning_content.dart
Achievement(
  id: 'my_achievement',
  title: 'Achievement Title',
  description: 'Achievement description',
  type: AchievementType.lessons,
  tier: AchievementTier.gold,
  iconPath: 'assets/badges/my_badge.png',
  requirement: 10,
  xpReward: 100,
),
  1. Check and unlock
final progress = ref.read(userProgressProvider);
if (progress!.completedLessonsCount >= 10) {
  ref.read(userProgressProvider.notifier)
     .unlockAchievement('my_achievement');
}

Testing

Unit Tests

// test/models/user_progress_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:learn_ai_data_science/models/user_progress.dart';

void main() {
  group('UserProgress', () {
    test('addXP increases total XP', () {
      final progress = UserProgress(
        userId: 'test',
        lastActivityDate: DateTime.now(),
        createdAt: DateTime.now(),
      );

      progress.addXP(50);

      expect(progress.totalXP, 50);
    });
  });
}

Widget Tests

// test/widgets/stat_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:learn_ai_data_science/widgets/stat_card.dart';

void main() {
  testWidgets('StatCard displays correct values', (tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: StatCard(
            icon: Icons.star,
            iconColor: Colors.yellow,
            label: 'Test',
            value: '100',
          ),
        ),
      ),
    );

    expect(find.text('Test'), findsOneWidget);
    expect(find.text('100'), findsOneWidget);
  });
}

Running Tests

# Run all tests
flutter test

# Run specific test file
flutter test test/models/user_progress_test.dart

# Run with coverage
flutter test --coverage

# View coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

Performance Optimization

Best Practices

  1. Use const constructors
const Text('Hello') // Good
Text('Hello')        // Avoid
  1. Minimize rebuilds with Riverpod
// Watch specific values
final level = ref.watch(currentLevelProvider);

// Instead of
final progress = ref.watch(userProgressProvider);
final level = progress?.currentLevel;
  1. Use ListView.builder for long lists
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ItemWidget(items[index]),
)
  1. Cache network images
CachedNetworkImage(
  imageUrl: url,
  placeholder: (context, url) => CircularProgressIndicator(),
)

Debugging

Flutter DevTools

# Launch DevTools
flutter pub global activate devtools
flutter pub global run devtools

Print Debugging

import 'dart:developer' as developer;

developer.log('Message', name: 'MyWidget');
debugPrint('Debug message');

Breakpoints

In VS Code:

  1. Click left gutter to set breakpoint
  2. Press F5 to start debugging
  3. Use Debug Console to inspect variables

Git Workflow

Branch Naming

  • feature/description - New features
  • fix/description - Bug fixes
  • refactor/description - Code refactoring
  • docs/description - Documentation updates

Commit Messages

Follow Conventional Commits:

feat: add new achievement system
fix: resolve streak calculation bug
docs: update setup guide
refactor: improve AI service performance

Pull Request Process

  1. Create feature branch
  2. Make changes and commit
  3. Push to remote
  4. Create pull request
  5. Wait for review
  6. Address feedback
  7. Merge when approved

Building for Release

iOS Release Build

# Update version in pubspec.yaml
flutter build ios --release

# Open in Xcode
open ios/Runner.xcworkspace

# Archive and upload to App Store

Version Bumping

Update pubspec.yaml:

version: 1.1.0+2  # version+build_number

Common Gotchas

  1. Forgetting to run build_runner after model changes

    • Solution: flutter pub run build_runner build
  2. Not disposing controllers

    • Always dispose controllers in dispose() method
  3. Using BuildContext across async gaps

    • Check mounted before using context after await
  4. Not handling null safety properly

    • Use ?., ??, and ! appropriately

Resources


For questions, open an issue or contact the maintainers.