DEV Community

Cover image for Flutter Architecture Recommendations and Resource
مصعب يوسف
مصعب يوسف

Posted on • Edited on

Flutter Architecture Recommendations and Resource

Building a Flutter application with a solid architectural foundation is crucial for creating maintainable, scalable, and efficient code. The Flutter team provides recommendations for best practices in app architecture. These recommendations are adaptable and should be tailored to your app’s unique needs. Below, we explore these practices with examples and explanations.

Separation of Concerns

Separation of concerns is a fundamental architectural principle. It ensures that your app is divided into distinct layers, each responsible for a specific functionality, such as UI, data handling, and business logic. Here are the key recommendations:

1. Use Clearly Defined Data and UI Layers

  • Recommendation Level: Strongly Recommend
  • Why It Matters: By separating the data and UI layers, your app becomes more modular, testable, and maintainable.
  • Implementation
  • Data Layer: Responsible for managing data sources (e.g., APIs, databases) and business logic.
  • UI Layer: Displays data and listens for user interactions.
// Data Layer
class UserRepository {
  final ApiService apiService;

UserRepository(this.apiService);
  Future<User> getUser(String id) async {
    return await apiService.fetchUser(id);
  }
}
// UI Layer
class UserViewModel extends ChangeNotifier {
  final UserRepository repository;
  UserViewModel(this.repository);
  User? user;
  Future<void> fetchUser(String id) async {
    user = await repository.getUser(id);
    notifyListeners();
  }
}
// Widget
class UserScreen extends StatelessWidget {
  final UserViewModel viewModel;
  UserScreen(this.viewModel);
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: viewModel.fetchUser('123'),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        } else if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        } else {
          return Text('User: ${viewModel.user?.name}');
        }
      },
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Use the Repository Pattern

  • Recommendation Level: Strongly Recommend
  • why It Matters: This pattern isolates data access logic, making the app more flexible and easier to test.

3. Use ViewModels and Views in the UI Layer (MVVM)

  • Recommendation Level: Strongly Recommend

  • Why It Matters: Keeps widgets “dumb,” reducing errors and increasing reusability.

4. Avoid Logic in Widgets

Encapsulate all logic in ViewModels or other helper classes, leaving widgets responsible only for rendering UI.

Handling Data

Handling data with care improves code reliability and prevents errors.

1. Use Unidirectional Data Flow

  • Recommendation Level: Strongly Recommend
  • Implementation
  • Data flows from the data layer to the UI layer.
  • User interactions trigger updates in the data layer.

2. Use Immutable Data Models

  • Recommendation Level: Strongly Recommend
  • Why It Matters: Prevents unintended mutations and makes debugging easier.

Example:

import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    required String id,
    required String name,
  }) = _User;
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode

3. Create Separate API Models and Domain Models

  • Recommendation Level: Conditional
  • Why It Matters: Reduces complexity in large apps by preventing coupling between API responses and business logic.

App Structure

A well-structured app simplifies collaboration and enhances code quality.

1. Use Dependency Injection

  • Recommendation Level: Strongly Recommend
  • Why It Matters: Prevents global state and enhances testability.

Example:

import 'package:provider/provider.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(create: (_) => ApiService()),
        ProxyProvider<ApiService, UserRepository>(
          update: (_, apiService, __) => UserRepository(apiService),
        ),
      ],
      child: MyApp(),
    ),
  );
}

Enter fullscreen mode Exit fullscreen mode

2. Use go_router for Navigation

  • Recommendation Level: Recommend
  • Why It Matters: Simplifies navigation while supporting deep linking and state restoration.

3. Use Abstract Repository Classes

  • Recommendation Level: Strongly Recommend
  • Why It Matters: Allows different implementations for various environments (e.g., development, staging).

Testing

Testing ensures your app’s reliability and reduces the risk of introducing bugs

1. Test Architectural Components Separately and Together

  • Recommendation Level: Strongly Recommend
  • Implementation:
  • Write unit tests for repositories, services, and ViewModels.
  • Write widget tests for views.
void main() {
  test('UserRepository fetches user correctly', () async {
    final mockApiService = MockApiService();
    final repository = UserRepository(mockApiService);

when(mockApiService.fetchUser('123'))
        .thenAnswer((_) async => User(id: '123', name: 'John Doe'));
    final user = await repository.getUser('123');
    expect(user.name, 'John Doe');
  });
}
Enter fullscreen mode Exit fullscreen mode

2. Use Fakes for Testing

  • Recommendation Level: Strongly Recommend
  • Why It Matters: Encourages modular and testable code by focusing on inputs and outputs.

Recommended resources

Code and templates

  • Compass app source code — Source code of a full-featured, robust Flutter application that implements many of these recommendations.
  • very_good_cli — A Flutter application template made by the Flutter experts Very Good Ventures. This template generates a similar app structure.

Documentation

  • Very Good Engineering architecture documentation — Very Good Engineering is a documentation site by VGV that has technical articles, demos, and open-sourced projects. It includes documentation on architecting Flutter applications.
  • State Management with ChangeNotifier walkthrough — A gentle introduction into using the primitives in the Flutter SDK for your state management.

Tooling

  • Flutter developer tools — DevTools is a suite of performance and debugging tools for Dart and Flutter.
  • flutter_lints — A package that contains the lints for Flutter apps recommended by the Flutter team. Use this package to encourage good coding practices across a team.

By following these recommendations, you can create Flutter applications that are efficient, scalable, and easy to maintain.

Thank you for reading

see more https://ms3byoussef.medium.com/

Top comments (0)