DEV Community

vmodal_ai
vmodal_ai

Posted on

Repository Pattern in Flutter: Build Clean, Scalable & Testable Apps

As your Flutter application grows, directly calling APIs or databases from your UI quickly becomes difficult to maintain.

This is where the Repository Pattern comes in. It separates your business logic from your data sources, making your code cleaner, easier to test, and more scalable.

In this tutorial, you'll learn what the Repository Pattern is, why it's useful, and how to implement it in a Flutter application.


What is the Repository Pattern?

The Repository Pattern acts as a bridge between your application's business logic and its data sources.

Instead of your UI communicating directly with an API or database, it communicates with a repository.

UI
 │
 ▼
Repository
 │
 ├── REST API
 ├── Local Database
 └── Cache
Enter fullscreen mode Exit fullscreen mode

This separation makes your app easier to maintain and extend.


Why Use the Repository Pattern?

Using a repository provides several benefits:

  • Cleaner and more organized code
  • Easier unit testing
  • Supports multiple data sources
  • Better code reusability
  • Simplifies future maintenance

It's commonly used with BLoC, Riverpod, Provider, and Clean Architecture.


Step 1: Create a Model

Suppose we want to display a list of users.

class User {
  final int id;
  final String name;

  User({
    required this.id,
    required this.name,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create an API Service

This class is responsible for communicating with the backend.

class ApiService {

  Future<List<User>> getUsers() async {

    // Call your REST API here

    return [];

  }

}
Enter fullscreen mode Exit fullscreen mode

Notice that the UI never talks directly to this class.


Step 3: Create the Repository

The repository fetches data from the API.

class UserRepository {

  final ApiService api;

  UserRepository(this.api);

  Future<List<User>> getUsers() {

    return api.getUsers();

  }

}
Enter fullscreen mode Exit fullscreen mode


Later, you can update this repository to return cached or offline data without changing the UI.


Step 4: Use the Repository

Instead of calling the API directly:

final users =
    await repository.getUsers();
Enter fullscreen mode Exit fullscreen mode

Your UI doesn't need to know where the data comes from.

It could be:

  • REST API
  • Hive
  • SQLite
  • Firebase
  • Local cache

The repository handles everything.


Recommended Folder Structure

A simple project structure might look like this:

lib/
│
├── models/
│     user.dart
│
├── services/
│     api_service.dart
│
├── repositories/
│     user_repository.dart
│
├── screens/
│     home_screen.dart
│
└── main.dart
Enter fullscreen mode Exit fullscreen mode

This structure keeps your project organized as it grows.


Best Practices

  • Keep repositories focused on data access.
  • Avoid API calls directly from widgets.
  • Use dependency injection for repositories.
  • Return models instead of raw JSON.
  • Combine repositories with state management like BLoC or Riverpod.

Conclusion

The Repository Pattern is one of the most useful design patterns for Flutter development. It separates data access from the UI, making your applications easier to maintain, test, and scale.

Whether you're building a small app or a large production project, using repositories will help you write cleaner and more professional Flutter code.

If you're planning to learn Clean Architecture, BLoC, or Riverpod, mastering the Repository Pattern is an excellent first step.


SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter
SDK Android: https://github.com/v-modal/vmodal_sdk_android
Discord: https://discord.gg/K72z28KUx

flutterdartarchitecturecleanarchitecturemobiledevelopment

Top comments (0)