← Назад к вопросам
Как интегрировать REST API во Flutter приложение?
1.0 Junior🔥 262 комментариев
#Архитектура Flutter#Работа с сетью
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Интеграция REST API во Flutter приложение
Работать с REST API — это ежедневная задача во Flutter разработке. Рассмотрю правильный подход от получения данных до кэширования.
1. Базовое использование http пакета
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiClient {
static const String baseUrl = 'https://api.example.com';
// GET запрос
Future<User> getUser(String userId) async {
try {
final response = await http.get(
Uri.parse('$baseUrl/users/$userId'),
headers: {'Content-Type': 'application/json'},
).timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else if (response.statusCode == 404) {
throw Exception('Пользователь не найден');
} else {
throw Exception('Ошибка сервера: ${response.statusCode}');
}
} on TimeoutException {
throw Exception('Timeout: сервер не отвечает');
} catch (e) {
throw Exception('Ошибка: $e');
}
}
// POST запрос
Future<User> createUser(CreateUserRequest request) async {
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode(request.toJson()),
);
if (response.statusCode == 201) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Ошибка создания: ${response.body}');
}
}
// PUT запрос
Future<User> updateUser(String userId, UpdateUserRequest request) async {
final response = await http.put(
Uri.parse('$baseUrl/users/$userId'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(request.toJson()),
);
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Ошибка обновления');
}
}
// DELETE запрос
Future<void> deleteUser(String userId) async {
final response = await http.delete(
Uri.parse('$baseUrl/users/$userId'),
);
if (response.statusCode != 204) {
throw Exception('Ошибка удаления');
}
}
}
2. Архитектура с Dio (лучше)
Dio — это более мощный HTTP клиент с interceptors, retry логикой и т.д.
import 'package:dio/dio.dart';
class DioClient {
late final Dio _dio;
DioClient() {
_dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
contentType: Headers.jsonContentType,
),
);
// Interceptor для логирования
_dio.interceptors.add(
LoggingInterceptor(),
);
// Interceptor для обработки ошибок аутентификации
_dio.interceptors.add(
AuthInterceptor(),
);
// Retry interceptor
_dio.interceptors.add(
RetryInterceptor(_dio),
);
}
Future<User> getUser(String userId) async {
try {
final response = await _dio.get('/users/$userId');
return User.fromJson(response.data);
} on DioException catch (e) {
_handleDioError(e);
rethrow;
}
}
Future<User> createUser(CreateUserRequest request) async {
final response = await _dio.post(
'/users',
data: request.toJson(),
);
return User.fromJson(response.data);
}
void _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
throw Exception('Connection timeout');
case DioExceptionType.sendTimeout:
throw Exception('Send timeout');
case DioExceptionType.receiveTimeout:
throw Exception('Receive timeout');
case DioExceptionType.badResponse:
throw Exception('Error: ${error.response?.statusCode}');
default:
throw Exception('Unknown error');
}
}
}
// Logging Interceptor
class LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('REQUEST[${options.method}] => PATH: ${options.path}');
print('Headers: ${options.headers}');
print('Body: ${options.data}');
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
print('Data: ${response.data}');
return super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
print('Error: ${err.error}');
return super.onError(err, handler);
}
}
// Auth Interceptor
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await getStoredToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return super.onRequest(options, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
// Если 401 Unauthorized, рефрешим токен
if (err.response?.statusCode == 401) {
await refreshToken();
return handler.resolve(await _retry(err.requestOptions));
}
return super.onError(err, handler);
}
}
3. Repository паттерн
abstract class UserRepository {
Future<User> getUser(String userId);
Future<List<User>> getUsers();
Future<void> createUser(CreateUserRequest request);
}
class UserRepositoryImpl implements UserRepository {
final DioClient dioClient;
final UserLocalDataSource localDataSource;
UserRepositoryImpl({
required this.dioClient,
required this.localDataSource,
});
@override
Future<User> getUser(String userId) async {
try {
// Попытка получить с API
final user = await dioClient.getUser(userId);
// Сохранить локально для кэша
await localDataSource.saveUser(user);
return user;
} catch (e) {
// Вернуть кэшированные данные
final cached = await localDataSource.getUser(userId);
if (cached != null) {
return cached;
}
rethrow;
}
}
@override
Future<List<User>> getUsers() async {
try {
final users = await dioClient.getUsers();
await localDataSource.saveUsers(users);
return users;
} catch (e) {
final cached = await localDataSource.getUsers();
if (cached.isNotEmpty) {
return cached;
}
rethrow;
}
}
@override
Future<void> createUser(CreateUserRequest request) async {
await dioClient.createUser(request);
}
}
4. Использование с BLoC
abstract class UserEvent {}
class FetchUserEvent extends UserEvent {
final String userId;
FetchUserEvent({required this.userId});
}
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserSuccess extends UserState {
final User user;
UserSuccess({required this.user});
}
class UserError extends UserState {
final String error;
UserError({required this.error});
}
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository repository;
UserBloc({required this.repository}) : super(UserInitial()) {
on<FetchUserEvent>(_onFetchUser);
}
Future<void> _onFetchUser(
FetchUserEvent event,
Emitter<UserState> emit,
) async {
emit(UserLoading());
try {
final user = await repository.getUser(event.userId);
emit(UserSuccess(user: user));
} catch (e) {
emit(UserError(error: e.toString()));
}
}
}
// UI
class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading) {
return CircularProgressIndicator();
} else if (state is UserSuccess) {
return Text('User: ${state.user.name}');
} else if (state is UserError) {
return Text('Error: ${state.error}');
}
return SizedBox();
},
);
}
}
5. Обработка ошибок
class ApiException implements Exception {
final String message;
final int? statusCode;
final dynamic originalError;
ApiException({
required this.message,
this.statusCode,
this.originalError,
});
@override
String toString() => message;
}
String _getErrorMessage(dynamic error) {
if (error is ApiException) {
return error.message;
} else if (error is DioException) {
switch (error.type) {
case DioExceptionType.badResponse:
return 'Ошибка сервера: ${error.response?.statusCode}';
case DioExceptionType.connectionTimeout:
return 'Нет соединения';
default:
return 'Неизвестная ошибка';
}
}
return error.toString();
}
6. Кэширование
class CachedRepository implements UserRepository {
final UserRepository remote;
final Duration cacheExpiration;
final Map<String, CachedData<User>> _cache = {};
CachedRepository({
required this.remote,
this.cacheExpiration = const Duration(hours: 1),
});
@override
Future<User> getUser(String userId) async {
// Проверить кэш
if (_isCacheValid(userId)) {
return _cache[userId]!.data;
}
// Получить с сервера
final user = await remote.getUser(userId);
// Сохранить в кэш
_cache[userId] = CachedData(
data: user,
timestamp: DateTime.now(),
);
return user;
}
bool _isCacheValid(String userId) {
if (!_cache.containsKey(userId)) return false;
final cached = _cache[userId]!;
final expired = DateTime.now().difference(cached.timestamp) > cacheExpiration;
return !expired;
}
}
class CachedData<T> {
final T data;
final DateTime timestamp;
CachedData({required this.data, required this.timestamp});
}
Чеклист для production
- Используй Dio вместо http
- Обработай все ошибки (timeout, network, server)
- Кэшируй данные локально
- Добавь retry логику
- Логируй запросы/ответы
- Используй Token refresh
- Валидируй JSON ответы
- Установи timeout для запросов
- Обработай 401/403 ошибки аутентификации
- Используй Repository паттерн
# pubspec.yaml
dependencies:
dio: ^5.0.0
retrofit: ^4.0.0
json_serializable: ^6.0.0