Mobile API интеграция: React Native, Flutter, iOS, Android

← Вернуться к статьям

Введение

Интеграция API в мобильные приложения имеет свои особенности по сравнению с веб-разработкой. Мобильные приложения должны работать в условиях нестабильного интернета, ограниченной батареи и различных размеров экранов. Каждая платформа (React Native, Flutter, iOS, Android) имеет свои инструменты и библиотеки для работы с API.

В этом руководстве мы рассмотрим интеграцию API на всех популярных мобильных платформах: React Native, Flutter, iOS (Swift) и Android (Kotlin). Каждая секция содержит примеры кода, лучшие практики и особенности работы с 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:

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:

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:

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:

Сравнение платформ 📊

Критерий 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:

  1. Обрабатывайте сетевые ошибки — проверяйте соединение, таймауты
  2. Используйте индикаторы загрузки — показывайте пользователю состояние
  3. Кэшируйте данные — для работы офлайн
  4. Оптимизируйте размер запросов — минимизируйте передачу данных
  5. Используйте пагинацию — не загружайте все данные сразу
  6. Обрабатывайте авторизацию — обновление токенов, редирект на логин
  7. Валидируйте данные на клиенте — перед отправкой на сервер
  8. Используйте retry логику — для временных ошибок
  9. Логируйте ошибки — для отладки
  10. Тестируйте на слабом интернете — имитируйте 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 в мобильные приложения требует внимания к деталям: обработке ошибок, офлайн режиму, кэшированию и оптимизации. Каждая платформа имеет свои инструменты, но принципы остаются схожими.

💡 Ключевые выводы:

💡 Совет:

Используйте Mock API во время разработки для тестирования различных сценариев без зависимости от backend. Это ускорит разработку и позволит протестировать обработку ошибок и edge cases.

Создайте Mock API за 2 минуты

Используйте Mock API с LightBox API для разработки мобильных приложений без ожидания готовности backend. Поддерживает все форматы и позволяет тестировать различные сценарии ответов.

Попробовать бесплатно →
← Вернуться к статьям