The repository pattern helps organize how your app gets data. It keeps data fetching and storage separate from the UI, making your app easier to manage and test.
Repository pattern in Flutter
abstract class Repository { Future<String> fetchData(); } class ApiRepository implements Repository { @override Future<String> fetchData() async { // Simulate fetching data from API return 'Data from API'; } } class LocalRepository implements Repository { @override Future<String> fetchData() async { // Simulate fetching data from local storage return 'Data from Local Storage'; } }
The Repository is an abstract class or interface that defines data methods.
Concrete classes like ApiRepository and LocalRepository implement these methods to get data from different sources.
class ApiRepository implements Repository { @override Future<String> fetchData() async { return 'Data from API'; } }
class LocalRepository implements Repository { @override Future<String> fetchData() async { return 'Data from Local Storage'; } }
This Flutter app uses the repository pattern to get data. You can switch between ApiRepository and LocalRepository by changing one line. The UI shows the data fetched.
import 'package:flutter/material.dart'; abstract class Repository { Future<String> fetchData(); } class ApiRepository implements Repository { @override Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data from API'; } } class LocalRepository implements Repository { @override Future<String> fetchData() async { await Future.delayed(Duration(seconds: 1)); return 'Data from Local Storage'; } } void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late Repository repository; String data = 'Loading...'; @override void initState() { super.initState(); // Change repository here to switch data source repository = ApiRepository(); loadData(); } void loadData() async { String result = await repository.fetchData(); setState(() { data = result; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Repository Pattern Example')), body: Center(child: Text(data, style: TextStyle(fontSize: 24))), ), ); } }
Use the repository pattern to keep your UI code clean and focused only on showing data.
You can easily add more data sources by creating new repository classes.
Testing is easier because you can mock the repository without touching UI code.
The repository pattern separates data access from UI code.
It helps manage multiple data sources in one place.
It makes your app easier to test and maintain.
Practice
Repository pattern in Flutter app development?Solution
Step 1: Understand the role of Repository pattern
The Repository pattern is designed to separate data access from the UI, so the UI does not directly handle data fetching or storage.Step 2: Identify the correct purpose
Among the options, only separating data access logic from UI code matches the Repository pattern's purpose.Final Answer:
To separate data access logic from UI code -> Option DQuick Check:
Repository pattern = separate data access from UI [OK]
- Confusing Repository with UI widget creation
- Thinking Repository manages navigation
- Assuming Repository handles user input
Solution
Step 1: Recall Dart syntax for abstract classes
In Dart, interfaces are created using abstract classes with method signatures but no bodies.Step 2: Check method signature for async fetching
The method should return a Future to indicate asynchronous fetching.Final Answer:
abstract class UserRepository { Future fetchUsers(); } -> Option AQuick Check:
Dart interface = abstract class with Future method [OK]
- Using 'interface' keyword which Dart doesn't have
- Missing Future for async methods
- Incorrect method syntax or return type
class UserRepositoryImpl implements UserRepository {
@override
Future fetchUsers() async {
return ['Alice', 'Bob'];
}
}
void main() async {
var repo = UserRepositoryImpl();
var users = await repo.fetchUsers();
print(users.length);
}Solution
Step 1: Understand the fetchUsers method
The method returns a Future with a list containing two strings: 'Alice' and 'Bob'.Step 2: Analyze the main function output
The code awaits the fetchUsers call, then prints the length of the list, which is 2.Final Answer:
2 -> Option AQuick Check:
List length = 2 [OK]
- Confusing printed list with its length
- Ignoring async/await causing errors
- Assuming fetchUsers returns empty list
class UserRepositoryImpl implements UserRepository {
Future fetchUsers() {
return ['Alice', 'Bob'];
}
}Solution
Step 1: Check return type of fetchUsers
The method signature says it returns Future, but the returned value is a List<String> without wrapping in Future.Step 2: Identify type mismatch and missing Future
Returning a plain list instead of a Future causes a type mismatch error.Final Answer:
fetchUsers must return Future, but returns List<String> -> Option BQuick Check:
Return type mismatch = fetchUsers must return Future, but returns List<String> [OK]
- Forgetting to wrap return value in Future
- Ignoring type mismatch between User and String
- Assuming async keyword is always required
Solution
Step 1: Understand Repository pattern goal
The pattern centralizes data access, hiding multiple sources behind one interface.Step 2: Evaluate options for combining data sources
Create a repository that fetches from local DB first, then updates from remote API, exposing a single method to UI combines local and remote data inside one repository method, exposing a simple interface to UI, which matches the pattern.Step 3: Reject options that expose data sources to UI
Options B and D let UI handle data sources, breaking separation. Use only remote API and ignore local DB to simplify code ignores local data, losing benefits.Final Answer:
Create a repository that fetches from local DB first, then updates from remote API, exposing a single method to UI -> Option CQuick Check:
Repository hides multiple sources behind one interface [OK]
- Letting UI handle multiple data sources
- Ignoring local cache benefits
- Splitting data sources into separate repositories without abstraction
Ready to prove it?
Pass the test and this pattern moves unknown
