Введение
Интеграция API в мобильные приложения имеет свои особенности по сравнению с веб-разработкой. Мобильные приложения должны работать в условиях нестабильного интернета, ограниченной батареи и различных размеров экранов. Каждая платформа (React Native, Flutter, iOS, Android) имеет свои инструменты и библиотеки для работы с API.
В этом руководстве мы рассмотрим интеграцию API на всех популярных мобильных платформах: React Native, Flutter, iOS (Swift) и Android (Kotlin). Каждая секция содержит примеры кода, лучшие практики и особенности работы с API на соответствующей платформе.
✅ Что вы узнаете:
- ✅ Интеграция API в React Native
- ✅ Работа с API в Flutter
- ✅ iOS API интеграция (Swift, URLSession)
- ✅ Android API интеграция (Kotlin, Retrofit)
- ✅ Обработка ошибок на мобильных
- ✅ Офлайн режим и кэширование
- ✅ Best practices для мобильных API
💡 Особенности мобильных API:
Мобильные приложения должны обрабатывать нестабильное соединение, работать офлайн, оптимизировать потребление батареи и данных, а также предоставлять плавный пользовательский опыт.
📋 Содержание
1. React Native + API ⚛️
React Native использует JavaScript для работы с API. Для HTTP запросов можно использовать
встроенный fetch или библиотеки типа axios.
Базовый пример с fetch
// api/users.js
const API_BASE_URL = 'https://api.example.com';
export const getUsers = async () => {
try {
const response = await fetch(`${API_BASE_URL}/users`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
};
export const createUser = async (userData) => {
try {
const response = await fetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
};
Использование React Query
// Установка: npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getUsers, createUser } from './api/users';
// Компонент с использованием React Query
function UsersScreen() {
const queryClient = useQueryClient();
// GET запрос
const { data: users, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: getUsers,
staleTime: 5000, // Кэш на 5 секунд
});
// POST запрос
const createUserMutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
// Инвалидация кэша после создания
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
const handleCreateUser = () => {
createUserMutation.mutate({
name: 'John Doe',
email: 'john@example.com'
});
};
if (isLoading) return Loading... ;
if (error) return Error: {error.message} ;
return (
{item.name} }
/>
);
}
Axios для React Native
// Установка: npm install axios
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// Interceptor для добавления токена
apiClient.interceptors.request.use((config) => {
const token = getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Interceptor для обработки ошибок
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Перенаправление на экран логина
navigateToLogin();
}
return Promise.reject(error);
}
);
export default apiClient;
✅ Преимущества React Native API:
- Один код для iOS и Android
- Использование JavaScript/TypeScript
- Большая экосистема библиотек
- React Query для управления состоянием
2. Flutter + API 🎯
Flutter использует http пакет для HTTP запросов. Также популярны библиотеки
dio и chopper для более продвинутых возможностей.
Базовый пример с http пакетом
// pubspec.yaml
// dependencies:
// http: ^1.1.0
import 'package:http/http.dart' as http;
import 'dart:convert';
class UserService {
final String baseUrl = 'https://api.example.com';
Future> getUsers() async {
try {
final response = await http.get(
Uri.parse('$baseUrl/users'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
}
);
if (response.statusCode == 200) {
final List data = json.decode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
} catch (e) {
throw Exception('Error: $e');
}
}
Future createUser(Map userData) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
},
body: json.encode(userData)
);
if (response.statusCode == 201) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create user');
}
} catch (e) {
throw Exception('Error: $e');
}
}
}
Использование Dio
// pubspec.yaml
// dependencies:
// dio: ^5.3.0
import 'package:dio/dio.dart';
class ApiClient {
late Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseURL: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
headers: {
'Content-Type': 'application/json'
}
));
// Interceptor для добавления токена
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = getAuthToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// Перенаправление на экран логина
navigateToLogin();
}
return handler.next(error);
}
));
}
Future> getUsers() async {
try {
final response = await _dio.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
} on DioException catch (e) {
throw Exception('Error: ${e.message}');
}
}
}
Использование в Widget
class UsersScreen extends StatefulWidget {
@override
_UsersScreenState createState() => _UsersScreenState();
}
class _UsersScreenState extends State {
final userService = UserService();
List users = [];
bool isLoading = true;
@override
void initState() {
super.initState();
loadUsers();
}
Future loadUsers() async {
try {
final fetchedUsers = await userService.getUsers();
setState(() {
users = fetchedUsers;
isLoading = false;
});
} catch (e) {
setState(() {
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e'))
);
}
}
@override
Widget build(BuildContext context) {
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(users[index].name),
subtitle: Text(users[index].email),
);
},
);
}
}
✅ Преимущества Flutter API:
- Один код для iOS и Android
- Строгая типизация (Dart)
- Хорошая производительность
- Dio для продвинутой работы с HTTP
3. iOS (Swift) + API 🍎
iOS использует URLSession для работы с HTTP запросами. Также популярны
библиотеки Alamofire и Moya для упрощения работы с API.
Базовый пример с URLSession
// UserService.swift
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
}
class UserService {
private let baseURL = "https://api.example.com"
private let session: URLSession
init() {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 10
config.timeoutIntervalForResource = 10
self.session = URLSession(configuration: config)
}
func getUsers(completion: @escaping (Result<[User], Error>) -> Void) {
guard let url = URL(string: "\(baseURL)/users") else {
completion(.failure(NSError(domain: "Invalid URL", code: -1)))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data = data else {
completion(.failure(NSError(domain: "Invalid response", code: -1)))
return
}
do {
let users = try JSONDecoder().decode([User].self, from: data)
completion(.success(users))
} catch {
completion(.failure(error))
}
}
task.resume()
}
func createUser(userData: [String: Any], completion: @escaping (Result) -> Void) {
guard let url = URL(string: "\(baseURL)/users") else {
completion(.failure(NSError(domain: "Invalid URL", code: -1)))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: userData)
} catch {
completion(.failure(error))
return
}
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 201,
let data = data else {
completion(.failure(NSError(domain: "Invalid response", code: -1)))
return
}
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}
task.resume()
}
}
Использование Alamofire
// Установка через CocoaPods или SPM
// pod 'Alamofire'
import Alamofire
class UserService {
private let baseURL = "https://api.example.com"
func getUsers(completion: @escaping (Result<[User], AFError>) -> Void) {
AF.request("\(baseURL)/users", method: .get, headers: [
"Authorization": "Bearer \(token)"
])
.validate()
.responseDecodable(of: [User].self) { response in
switch response.result {
case .success(let users):
completion(.success(users))
case .failure(let error):
completion(.failure(error))
}
}
}
func createUser(userData: User, completion: @escaping (Result) -> Void) {
AF.request("\(baseURL)/users", method: .post, parameters: userData, encoder: JSONParameterEncoder.default, headers: [
"Authorization": "Bearer \(token)"
])
.validate()
.responseDecodable(of: User.self) { response in
switch response.result {
case .success(let user):
completion(.success(user))
case .failure(let error):
completion(.failure(error))
}
}
}
}
✅ Преимущества iOS API:
- Нативный доступ к URLSession
- Alamofire для упрощения работы
- Строгая типизация Swift
- Хорошая интеграция с iOS SDK
4. Android (Kotlin) + API 🤖
Android использует Retrofit и OkHttp для работы с API.
Это самые популярные библиотеки для HTTP запросов в Android.
Базовый пример с Retrofit
// build.gradle.kts
// dependencies {
// implementation("com.squareup.retrofit2:retrofit:2.9.0")
// implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// implementation("com.squareup.okhttp3:okhttp:4.11.0")
// }
// User.kt
data class User(
val id: Int,
val name: String,
val email: String
)
// UserService.kt
interface UserService {
@GET("users")
suspend fun getUsers(
@Header("Authorization") token: String
): List
@POST("users")
suspend fun createUser(
@Header("Authorization") token: String,
@Body user: User
): User
}
// ApiClient.kt
object ApiClient {
private const val BASE_URL = "https://api.example.com"
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.header("Authorization", "Bearer $token")
.header("Content-Type", "application/json")
val request = requestBuilder.build()
chain.proceed(request)
}
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val userService: UserService = retrofit.create(UserService::class.java)
}
Использование в Activity/Fragment
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerView)
adapter = UserAdapter()
recyclerView.adapter = adapter
loadUsers()
}
private fun loadUsers() {
lifecycleScope.launch {
try {
val users = ApiClient.userService.getUsers("Bearer $token")
adapter.submitList(users)
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
private fun createUser() {
lifecycleScope.launch {
try {
val newUser = User(0, "John Doe", "john@example.com")
val createdUser = ApiClient.userService.createUser("Bearer $token", newUser)
Toast.makeText(this@MainActivity, "User created!", Toast.LENGTH_SHORT).show()
loadUsers()
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
Использование Coroutines
// ViewModel для работы с API
class UsersViewModel : ViewModel() {
private val _users = MutableLiveData>()
val users: LiveData> = _users
private val _isLoading = MutableLiveData()
val isLoading: LiveData = _isLoading
fun loadUsers() {
viewModelScope.launch {
_isLoading.value = true
try {
val usersList = ApiClient.userService.getUsers("Bearer $token")
_users.value = usersList
} catch (e: Exception) {
// Обработка ошибки
} finally {
_isLoading.value = false
}
}
}
}
✅ Преимущества Android API:
- Retrofit — стандарт де-факто
- Отличная поддержка Kotlin Coroutines
- Простая конфигурация через аннотации
- Интеграция с OkHttp
Сравнение платформ 📊
| Критерий | React Native | Flutter | iOS (Swift) | Android (Kotlin) |
|---|---|---|---|---|
| Библиотека | fetch, axios, React Query | http, dio | URLSession, Alamofire | Retrofit, OkHttp |
| Язык | JavaScript/TypeScript | Dart | Swift | Kotlin |
| Кроссплатформенность | ✅ iOS + Android | ✅ iOS + Android | ❌ iOS only | ❌ Android only |
| Производительность | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Кривая обучения | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Best Practices для мобильных API 🌟
✅ 10 правил мобильной интеграции API:
- Обрабатывайте сетевые ошибки — проверяйте соединение, таймауты
- Используйте индикаторы загрузки — показывайте пользователю состояние
- Кэшируйте данные — для работы офлайн
- Оптимизируйте размер запросов — минимизируйте передачу данных
- Используйте пагинацию — не загружайте все данные сразу
- Обрабатывайте авторизацию — обновление токенов, редирект на логин
- Валидируйте данные на клиенте — перед отправкой на сервер
- Используйте retry логику — для временных ошибок
- Логируйте ошибки — для отладки
- Тестируйте на слабом интернете — имитируйте 3G/EDGE
Офлайн режим и кэширование 📴
React Native с AsyncStorage
// Кэширование данных в React Native
import AsyncStorage from '@react-native-async-storage/async-storage';
const CACHE_KEY = '@users_cache';
const CACHE_DURATION = 5 * 60 * 1000; // 5 минут
export const getUsers = async () => {
// Проверяем кэш
const cached = await AsyncStorage.getItem(CACHE_KEY);
if (cached) {
const { data, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp < CACHE_DURATION) {
return data; // Возвращаем из кэша
}
}
// Загружаем из API
try {
const response = await fetch(`${API_BASE_URL}/users`);
const data = await response.json();
// Сохраняем в кэш
await AsyncStorage.setItem(CACHE_KEY, JSON.stringify({
data,
timestamp: Date.now()
}));
return data;
} catch (error) {
// Если ошибка сети, возвращаем из кэша
if (cached) {
const { data } = JSON.parse(cached);
return data;
}
throw error;
}
};
Flutter с SharedPreferences
// Кэширование в Flutter
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
class CacheService {
static Future cacheUsers(List users) async {
final prefs = await SharedPreferences.getInstance();
final jsonString = json.encode(users.map((u) => u.toJson()).toList());
await prefs.setString('users_cache', jsonString);
await prefs.setInt('users_cache_timestamp', DateTime.now().millisecondsSinceEpoch);
}
static Future?> getCachedUsers() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('users_cache');
final timestamp = prefs.getInt('users_cache_timestamp');
if (jsonString != null && timestamp != null) {
final age = DateTime.now().millisecondsSinceEpoch - timestamp;
if (age < 5 * 60 * 1000) { // 5 минут
final List data = json.decode(jsonString);
return data.map((json) => User.fromJson(json)).toList();
}
}
return null;
}
}
Заключение
Интеграция API в мобильные приложения требует внимания к деталям: обработке ошибок, офлайн режиму, кэшированию и оптимизации. Каждая платформа имеет свои инструменты, но принципы остаются схожими.
💡 Ключевые выводы:
- React Native использует fetch/axios с React Query
- Flutter использует http/dio пакеты
- iOS использует URLSession или Alamofire
- Android использует Retrofit и OkHttp
- Все платформы требуют обработки ошибок и офлайн режима
- Кэширование критически важно для UX
💡 Совет:
Используйте Mock API во время разработки для тестирования различных сценариев без зависимости от backend. Это ускорит разработку и позволит протестировать обработку ошибок и edge cases.
Создайте Mock API за 2 минуты
Используйте Mock API с LightBox API для разработки мобильных приложений без ожидания готовности backend. Поддерживает все форматы и позволяет тестировать различные сценарии ответов.
Попробовать бесплатно →