Skip to content

Philosophy

Introduction

Bond's philosophy is rooted in the belief that mobile app development should be productive, enjoyable, and focused on solving real business problems rather than wrestling with infrastructure concerns. Every design decision in Bond reflects our commitment to developer experience, code quality, and long-term maintainability.

Core Principles

Convention over Configuration

Bond provides sensible defaults and established patterns that work for the majority of use cases. While you can customize and override these conventions when needed, the default path should be the right path for most developers.

Example: When you create a new feature with bond create feature auth, Bond generates a complete feature structure with Service Provider, API service, repository, and form controllers following established patterns. You don't need to decide how to organize these files or wire them together - Bond provides a proven structure.

// Bond generates this structure automatically
features/
  auth/
    auth_service_provider.dart
    data/
      repositories/
        auth_repository.dart
      api/
        auth_api_service.dart
    presentation/
      controllers/
        login_form_controller.dart
      pages/
        login_page.dart

Explicit Dependencies

Bond makes all dependencies explicit through the Service Provider pattern and dependency injection. There are no hidden global variables, singletons, or magic - every dependency is clearly declared and can be easily swapped for testing or different implementations.

Why this matters: Explicit dependencies make code easier to understand, test, and maintain. When you see a class constructor, you immediately know what it depends on.

// ✅ Explicit dependencies - clear what this class needs
class UserRepository {
  final UserApiService apiService;
  final TokenStorage tokenStorage;
  final UserCache cache;

  UserRepository({
    required this.apiService,
    required this.tokenStorage,
    required this.cache,
  });
}

// ❌ Hidden dependencies - unclear what this class uses
class UserRepository {
  void getUser() {
    final api = GlobalApiClient.instance; // Hidden dependency!
    final token = GlobalAuth.currentToken; // Another hidden dependency!
  }
}

Typed Everything

Bond embraces Dart's type system to catch errors at compile time rather than runtime. From API responses to form validation, Bond uses types to provide better IDE support, catch bugs early, and make code self-documenting.

Example: BondFire's typed responses ensure you always know what data structure you're working with:

// The type system tells you exactly what you'll get
final response = await bondFire.get<ListResponse<User>>('/users')
  .factory(ListResponse<User>.fromJson)
  .execute();

// IDE knows response.data is List<User>
for (final user in response.data) {
  print(user.name); // Type-safe access
}

Developer Experience First

Every API, error message, and tool in Bond is designed with developer experience in mind. This means:

  • Clear Error Messages: When something goes wrong, Bond tells you exactly what happened and how to fix it
  • Comprehensive Documentation: Every feature is documented with examples and common use cases
  • Helpful CLI Tools: The Bond CLI guides you through setup and generation with interactive prompts
  • IDE Integration: Bond works seamlessly with VS Code, Android Studio, and other Flutter IDEs

Testability by Design

Bond's architecture makes testing natural and straightforward. The Service Provider pattern, dependency injection, and clear separation of concerns mean you can easily test individual components in isolation.

// Easy to test - just provide mock dependencies
void main() {
  group('UserRepository', () {
    late UserRepository repository;
    late MockUserApiService mockApi;
    late MockTokenStorage mockStorage;

    setUp(() {
      mockApi = MockUserApiService();
      mockStorage = MockTokenStorage();
      repository = UserRepository(
        apiService: mockApi,
        tokenStorage: mockStorage,
        cache: InMemoryUserCache(),
      );
    });

    test('should return user when API call succeeds', () async {
      // Test implementation
    });
  });
}

Architectural Decisions

Service Provider Pattern

Bond uses the Service Provider pattern (inspired by Laravel) to organize dependency registration and feature bootstrapping. This pattern provides several benefits:

  1. Clear Organization: Each feature has its own provider that registers all related services
  2. Lazy Loading: Services are only created when needed
  3. Easy Testing: Providers can be swapped with test implementations
  4. Modular Architecture: Features can be developed and tested independently

Feature-Based Structure

Rather than organizing code by technical layers (models, views, controllers), Bond organizes by business features (auth, posts, notifications). This approach:

  • Makes it easier to find related code
  • Enables teams to work on different features independently
  • Simplifies testing and deployment of individual features
  • Reduces merge conflicts in large teams

Shared Decoding Pipeline

Bond uses a shared response decoding system that works across networking and caching. This means you define your JSON-to-model conversion once, and it works everywhere:

class UserServiceProvider extends ServiceProvider with ResponseDecoding {
  @override
  Map<Type, JsonFactory> get factories => {
    User: (json) => User.fromJson(json),
  };
}

// This factory is used automatically by:
// - BondFire for API responses
// - Bond Cache for cached objects
// - Any other Bond package that needs JSON conversion

Design Trade-offs

Every framework makes trade-offs. Bond is no exception. Here are the conscious decisions we've made and why:

Opinionated Structure vs. Flexibility

Trade-off: Bond provides an opinionated project structure and conventions.

Why: While this reduces flexibility, it eliminates decision fatigue and ensures consistency across teams and projects. The structure is based on years of Flutter development experience and proven patterns.

Escape Hatch: You can always customize or override Bond's conventions when needed. The framework doesn't lock you in.

Bundle Size vs. Features

Trade-off: Bond includes many packages and features out of the box.

Why: The convenience of having everything work together seamlessly outweighs the small increase in bundle size. Modern app stores and devices handle larger apps well.

Mitigation: Bond packages are modular - you can use only what you need. Tree shaking eliminates unused code in release builds.

Learning Curve vs. Power

Trade-off: Bond has concepts like Service Providers that developers need to learn.

Why: These patterns provide significant long-term benefits in terms of maintainability, testability, and team productivity. The upfront learning investment pays dividends over time.

Support: Comprehensive documentation, examples, and CLI tools help developers learn Bond concepts quickly.

Influences and Inspiration

Bond draws inspiration from several successful frameworks and patterns:

Laravel (PHP)

  • Service Provider pattern for dependency registration
  • Comprehensive documentation approach
  • Developer-friendly CLI tools
  • Convention over configuration philosophy

Spring Boot (Java)

  • Dependency injection and inversion of control
  • Auto-configuration and sensible defaults
  • Modular architecture with clear boundaries

Ruby on Rails

  • Convention over configuration
  • Developer happiness as a primary goal
  • Comprehensive tooling and generators

Flutter/Dart Ecosystem

  • Embracing Dart's type system
  • Integration with existing Flutter patterns (Riverpod, GetIt, etc.)
  • Respect for Flutter's widget-based architecture

Evolution and Future

Bond's philosophy continues to evolve based on community feedback and real-world usage. Key areas of ongoing development include:

Performance Optimization

Continuously improving the performance characteristics of Bond packages while maintaining developer experience.

Ecosystem Integration

Better integration with popular Flutter packages and tools while maintaining Bond's cohesive experience.

Developer Tooling

Enhanced CLI tools, IDE plugins, and debugging support to make Bond development even more productive.

Community Patterns

Incorporating proven patterns and solutions from the Bond community back into the core framework.

Conclusion

Bond's philosophy centers on making Flutter development more productive and enjoyable by providing proven patterns, eliminating boilerplate, and focusing on developer experience. While this requires some upfront learning, the long-term benefits in terms of code quality, team productivity, and maintainability make it worthwhile.

The framework is designed to grow with your needs - from simple prototypes to complex enterprise applications. By following Bond's conventions and patterns, you're building on a foundation that has been tested in real-world applications and refined based on community feedback.

Next Steps

Now that you understand Bond's philosophy and design principles: