Большинство Flutter приложений работают с сервером через REST API. Разберём лучшие практики работы с API.
HTTP пакет
Базовый запрос
import 'package:http/http.dart' as http;
Future<void> fetchData() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
print(data);
}
}
POST запрос
Future<void> createUser(String name, String email) async {
final response = await http.post(
Uri.parse('https://api.example.com/users'),
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'name': name,
'email': email,
}),
);
if (response.statusCode == 201) {
print('User created');
}
}
PUT и DELETE
Future<void> updateUser(String id, String name) async {
final response = await http.put(
Uri.parse('https://api.example.com/users/$id'),
headers: {'Content-Type': 'application/json'},
body: json.encode({'name': name}),
);
}
Future<void> deleteUser(String id) async {
final response = await http.delete(
Uri.parse('https://api.example.com/users/$id'),
);
}
Dio пакет
Установка
dependencies:
dio: ^5.4.0
Базовая настройка
import 'package:dio/dio.dart';
class ApiService {
late final Dio _dio;
ApiService() {
_dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
),
);
_setupInterceptors();
}
void _setupInterceptors() {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// Добавить токен
final token = storage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onResponse: (response, handler) {
return handler.next(response);
},
onError: (error, handler) {
// Обработка ошибок
if (error.response?.statusCode == 401) {
// Обновить токен
}
return handler.next(error);
},
),
);
}
}
GET запросы
Future<List<User>> getUsers() async {
try {
final response = await _dio.get('/users');
final List<dynamic> data = response.data;
return data.map((json) => User.fromJson(json)).toList();
} catch (e) {
throw Exception('Failed to load users: $e');
}
}
Future<User?> getUser(String id) async {
try {
final response = await _dio.get('/users/$id');
return User.fromJson(response.data);
} catch (e) {
return null;
}
}
POST запрос
Future<User> createUser(CreateUserRequest request) async {
try {
final response = await _dio.post(
'/users',
data: request.toJson(),
);
return User.fromJson(response.data);
} catch (e) {
throw Exception('Failed to create user: $e');
}
}
Загрузка файлов
Future<String> uploadFile(File file) async {
try {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(file.path),
});
final response = await _dio.post(
'/upload',
data: formData,
);
return response.data['url'];
} catch (e) {
throw Exception('Failed to upload file: $e');
}
}
Download файлов
Future<void> downloadFile(String url, String savePath) async {
try {
await _dio.download(
url,
savePath,
onReceiveProgress: (received, total) {
final progress = (received / total) * 100;
print('Downloading: $progress%');
},
);
} catch (e) {
throw Exception('Failed to download: $e');
}
}
Retrofit
Установка
dependencies:
retrofit: ^4.0.0
json_annotation: ^4.9.0
dev_dependencies:
retrofit_generator: ^8.0.0
build_runner: ^2.4.0
json_serializable: ^6.8.0
API интерфейс
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';
part 'api_service.g.dart';
@RestApi(baseUrl: 'https://api.example.com')
abstract class ApiService {
factory ApiService(Dio dio) = _ApiService;
@GET('/users')
Future<List<User>> getUsers();
@GET('/users/{id}')
Future<User> getUser(@Path('id') String id);
@POST('/users')
Future<User> createUser(@Body() CreateUserRequest request);
@PUT('/users/{id}')
Future<User> updateUser(
@Path('id') String id,
@Body() UpdateUserRequest request,
);
@DELETE('/users/{id}')
Future<void> deleteUser(@Path('id') String id);
@POST('/upload')
Future<String> uploadFile(@Part() File file);
@GET('/search')
Future<List<Item>> searchItems(
@Query('q') String query,
@Query('page') int page,
@Query('limit') int limit,
);
}
Использование
final dio = Dio();
final apiService = ApiService(dio);
final users = await apiService.getUsers();
final user = await apiService.getUser('123');
final newUser = await apiService.createUser(
CreateUserRequest(name: 'John', email: 'john@example.com'),
);
Error Handling
Custom exceptions
abstract class AppException implements Exception {
final String message;
final int? statusCode;
AppException(this.message, {this.statusCode});
@override
String toString() => message;
}
class NetworkException extends AppException {
NetworkException(String message) : super(message);
}
class ServerException extends AppException {
ServerException(String message, {int? statusCode})
: super(message, statusCode: statusCode);
}
class AuthException extends AppException {
AuthException(String message) : super(message);
}
class NotFoundException extends AppException {
NotFoundException(String message) : super(message, statusCode: 404);
}
Error handler
class ErrorHandler {
static AppException handleError(dynamic error) {
if (error is DioException) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return NetworkException('Connection timeout');
case DioExceptionType.badResponse:
final statusCode = error.response?.statusCode;
switch (statusCode) {
case 401:
return AuthException('Unauthorized');
case 404:
return NotFoundException('Resource not found');
case 500:
return ServerException('Internal server error', statusCode: 500);
default:
return ServerException(
'Request failed with status $statusCode',
statusCode: statusCode,
);
}
case DioExceptionType.cancel:
return NetworkException('Request cancelled');
case DioExceptionType.connectionError:
return NetworkException('No internet connection');
default:
return AppException('Unexpected error occurred');
}
}
return AppException(error.toString());
}
}
Caching
HTTP Cache
dependencies:
dio_cache_interceptor: ^3.4.0
final cacheOptions = CacheOptions(
store: MemCacheStore(),
policy: CachePolicy.request,
hitCacheOnErrorExcept: [401, 403],
maxStale: Duration(days: 7),
);
final dio = Dio();
dio.interceptors.add(DioCacheInterceptor(options: cacheOptions));
Локальное кэширование
class CachedApiService {
final ApiService _api;
final Map<String, CachedData> _cache = {};
CachedApiService(this._api);
Future<T> get<T>(
String key,
Future<T> Function() fetch, {
Duration duration = const Duration(minutes: 5),
}) async {
// Проверка кэша
if (_cache.containsKey(key)) {
final cached = _cache[key]!;
if (!cached.isExpired) {
return cached.data as T;
}
_cache.remove(key);
}
// Загрузка данных
final data = await fetch();
// Сохранение в кэш
_cache[key] = CachedData(
data: data,
expiry: DateTime.now().add(duration),
);
return data;
}
void invalidate(String key) {
_cache.remove(key);
}
void clear() {
_cache.clear();
}
}
class CachedData<T> {
final T data;
final DateTime expiry;
CachedData({required this.data, required this.expiry});
bool get isExpired => DateTime.now().isAfter(expiry);
}
Repository Pattern
abstract class UserRepository {
Future<List<User>> getUsers();
Future<User?> getUser(String id);
Future<User> createUser(CreateUserRequest request);
}
class UserRepositoryImpl implements UserRepository {
final ApiService _api;
final CacheService _cache;
UserRepositoryImpl(this._api, this._cache);
@override
Future<List<User>> getUsers() async {
try {
return await _api.getUsers();
} catch (e) {
throw ErrorHandler.handleError(e);
}
}
@override
Future<User?> getUser(String id) async {
try {
return await _cache.get(
'user_$id',
() => _api.getUser(id),
duration: Duration(minutes: 10),
);
} catch (e) {
throw ErrorHandler.handleError(e);
}
}
@override
Future<User> createUser(CreateUserRequest request) async {
try {
final user = await _api.createUser(request);
// Инвалидация кэша
_cache.clear();
return user;
} catch (e) {
throw ErrorHandler.handleError(e);
}
}
}
Заключение
Работа с REST API во Flutter — это важная часть разработки. Используйте Dio для сложных сценариев и Retrofit для типобезопасности.