Flutter Repository Pattern: Keep API Calls Out of Widgets
Open any Flutter project that has been alive for more than six months and you will find it: a widget with an http.get buried in initState , a Firestore query inside a build method, a SharedPreferences read next to a setState . It works. And it is exactly how codebases become untestable. The Flutter repository pattern is the fix: a single class per feature that owns all data access, so your…
In many Flutter projects that have been active for over six months, there's a common sight: widgets containing http.get calls within the initState method, Firestore queries nested within the build method, and SharedPreferences reads placed next to a setState call. While this approach may seem to work, it ultimately leads to codebases that become challenging to test.
This is where the Flutter repository pattern comes into play as a solution. The repository pattern involves creating a single class for each feature, responsible for all data access operations. As a result, widgets will no longer interact directly with network calls, databases, or caches. The essence of the Flutter repository pattern lies in providing a clear interface for widgets to request data, while encapsulating all data access mechanisms behind this interface.
To illustrate, let's consider a product catalog example that initially fetches data from a REST API but may later incorporate a caching mechanism. By following the steps outlined below, we can successfully implement the repository pattern:
1. Define the data model (the information the app understands):
Create a Product class that encapsulates the relevant data properties, such as id, name, and price. Implement a factory method to create a Product instance from a JSON object.
2. Implement the data source (the external data provider):
The data source is where raw HTTP requests and other data access mechanisms take place. In this case, we have the ProductsApi class, which handles REST API interactions without any knowledge of the app's features.
3. Create the repository (the contract between the app and the data source):
The repository serves as an abstract interface that the rest of the app codes against. In our example, the ProductsRepository interface defines a method named getProducts, which returns a list of Product objects. The implementation of this interface, ApiProductsRepository, communicates with the actual backend through the ProductsApi class.
By adhering to the Flutter repository pattern, we ensure that widgets and ViewModels remain above the repository layer and never import HTTP, Dio, cloud_firestore, or shared_preferences packages directly. All data-related operations are confined to the repository layer, making the codebase more maintainable, testable, and adaptable to future changes.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.