Data Layer Structure
When building Flutter apps that interact with REST APIs, keeping your data layer organized is crucial. You can achieve this by separating your API service, repository, and model classes. Each of these plays a distinct role:
- The API service handles all HTTP requests and responses;
- The repository manages data logic and acts as a bridge between your app and the API service;
- The model classes define the data structures your app works with.
This separation makes your code easier to understand, test, and maintain.
Giving each layer a single responsibility helps you avoid tangled code and makes it easier to debug, extend, and test your app.
main.dart
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859import 'dart:convert'; import 'package:http/http.dart' as http; // Model class representing a User class User { final int id; final String name; final String email; User({required this.id, required this.name, required this.email}); factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], name: json['name'], email: json['email'], ); } } // API Service class for making HTTP requests class UserApiService { final String baseUrl; UserApiService({required this.baseUrl}); Future<User> fetchUser(int userId) async { final response = await http.get(Uri.parse('$baseUrl/users/$userId')); if (response.statusCode == 200) { return User.fromJson(json.decode(response.body)); } else { throw Exception('Failed to load user'); } } } // Repository class that acts as a bridge between API service and app logic class UserRepository { final UserApiService apiService; UserRepository({required this.apiService}); Future<User> getUser(int userId) async { return await apiService.fetchUser(userId); } } void main() async { final apiService = UserApiService(baseUrl: 'https://jsonplaceholder.typicode.com'); final repository = UserRepository(apiService: apiService); try { final user = await repository.getUser(1); print('User: ${user.name}, Email: ${user.email}'); } catch (e) { print('Error: $e'); } }
By keeping your API service, repository, and model classes separate as shown above, you make each part of your data layer responsible for only one thing. This clear division means you can update your API logic, data handling, or data models independently without breaking unrelated code. If your API changes, you only need to update the service or model, not your entire app. If you want to add caching or mock data for tests, you can do so in the repository without touching network code.
Thanks for your feedback!
Ask AI
Ask AI
Ask anything or try one of the suggested questions to begin our chat
Awesome!
Completion rate improved to 6.67
Data Layer Structure
Swipe to show menu
When building Flutter apps that interact with REST APIs, keeping your data layer organized is crucial. You can achieve this by separating your API service, repository, and model classes. Each of these plays a distinct role:
- The API service handles all HTTP requests and responses;
- The repository manages data logic and acts as a bridge between your app and the API service;
- The model classes define the data structures your app works with.
This separation makes your code easier to understand, test, and maintain.
Giving each layer a single responsibility helps you avoid tangled code and makes it easier to debug, extend, and test your app.
main.dart
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859import 'dart:convert'; import 'package:http/http.dart' as http; // Model class representing a User class User { final int id; final String name; final String email; User({required this.id, required this.name, required this.email}); factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], name: json['name'], email: json['email'], ); } } // API Service class for making HTTP requests class UserApiService { final String baseUrl; UserApiService({required this.baseUrl}); Future<User> fetchUser(int userId) async { final response = await http.get(Uri.parse('$baseUrl/users/$userId')); if (response.statusCode == 200) { return User.fromJson(json.decode(response.body)); } else { throw Exception('Failed to load user'); } } } // Repository class that acts as a bridge between API service and app logic class UserRepository { final UserApiService apiService; UserRepository({required this.apiService}); Future<User> getUser(int userId) async { return await apiService.fetchUser(userId); } } void main() async { final apiService = UserApiService(baseUrl: 'https://jsonplaceholder.typicode.com'); final repository = UserRepository(apiService: apiService); try { final user = await repository.getUser(1); print('User: ${user.name}, Email: ${user.email}'); } catch (e) { print('Error: $e'); } }
By keeping your API service, repository, and model classes separate as shown above, you make each part of your data layer responsible for only one thing. This clear division means you can update your API logic, data handling, or data models independently without breaking unrelated code. If your API changes, you only need to update the service or model, not your entire app. If you want to add caching or mock data for tests, you can do so in the repository without touching network code.
Thanks for your feedback!