← Назад к вопросам

Как объединить данные с нескольких API-запросов, исключив дубликаты перед отображением

2.3 Middle🔥 121 комментариев
#Сетевое взаимодействие

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Объединение данных из нескольких API с исключением дубликатов

В современной разработке Android часто приходится работать с несколькими источниками данных. Объединение ответов от разных API с фильтрацией дубликатов — типичная задача, требующая внимательного подхода к архитектуре и обработке данных.

Основные стратегии и подходы

Ключевые принципы:

  1. Определение критерия уникальности — что делает объект дублем? (ID, комбинация полей).
  2. Выбор точки объединения — на уровне сетевых запросов, в репозитории или при преобразовании.
  3. Обеспечение реактивности и производительности — особенно при большом объеме данных.

Практическая реализация на Kotlin

1. Использование Kotlin Flow для реактивного объединения

Это современный подход, рекомендованный для Android с Kotlin Coroutines.

class DataRepository(
    private val apiService1: ApiService1,
    private val apiService2: ApiService2
) {
    fun getCombinedData(): Flow<List<Item>> = flow {
        val flow1 = apiService1.fetchItems().map { it.toDomain() }
        val flow2 = apiService2.fetchItems().map { it.toDomain() }
        
        combine(flow1, flow2) { list1, list2 ->
            (list1 + list2).distinctBy { it.id } // Удаление дублей по ID
        }.collect { emit(it) }
    }
}

2. Объединение с помощью операторов RxJava

Для проектов, использующих RxJava.

fun mergeApiData(): Observable<List<Item>> {
    return Observable.merge(
        apiService1.fetchItemsRx().map { it.toDomain() },
        apiService2.fetchItemsRx().map { it.toDomain() }
    )
    .toList()
    .map { list -> 
        list.flatten().distinctBy { it.uniqueKey }
    }
    .toObservable()
}

3. Прямое объединение в репозитории с Kotlin Coroutines

Простой подход для последовательных или параллельных запросов.

suspend fun loadCombinedData(): List<Item> = coroutineScope {
    val deferred1 = async { apiService1.fetchItemsSuspend() }
    val deferred2 = async { apiService2.fetchItemsSuspend() }
    
    val list1 = deferred1.await().toDomain()
    val list2 = deferred2.await().toDomain()
    
    val combined = list1 + list2
    
    // Использование HashSet для эффективного удаления дублей
    val uniqueItems = combined.distinctBy { it.id }
    
    // Альтернатива: фильтрация по нескольким полям
    // val uniqueItems = combined.distinctBy { "${it.id}-${it.type}" }
    
    uniqueItems
}

Детализация метода distinctBy

Kotlin предоставляет удобные функции для работы с коллекциями:

// Базовый пример удаления дублей
val uniqueList = rawList.distinctBy { item -> item.id }

// Сложный ключ уникальности (комбинация полей)
val uniqueList = rawList.distinctBy { item -> 
    "${item.sourceApi}-${item.externalId}-${item.hashCode()}"
}

// Использование custom сравнителя
val uniqueList = rawList.filter { item ->
    rawList.indexOfFirst { it.id == item.id } == rawList.indexOf(item)
}

Архитектурные рекомендации

Где выполнять объединение:

  • В репозитории — наиболее логичное место, так он абстрагирует источники данных.
  • В UseCase/Interactor — если бизнес-логика сложная и требует дополнительной обработки.
  • В ViewModel — только для простых случаев, но это смешивает логику и представление.

Оптимизации для больших данных:

  • Использовать хеш-таблицы (HashSet) для быстрого сравнения.
  • Рассмотреть пагинацию и объединение на уровне страниц.
  • Добавить локальную базу данных (Room) как промежуточный кэш с автоматическим удалением дублей.

Обработка ошибок и состояний:

fun getCombinedDataWithState(): Flow<State<List<Item>>> = flow {
    emit(State.Loading)
    
    try {
        val result = loadCombinedData() // Реализация объединения
        emit(State.Success(result))
    } catch (e: Exception) {
        // Частый случай: один API отказал, используем данные другого
        val partialData = loadPartialData()
        emit(State.PartialSuccess(partialData, e))
    }
}

Заключение

Объединение данных из нескольких API с удалением дубликатов требует четкого определения критерия уникальности и выбора оптимальной точки в архитектуре для этой операции. Использование Kotlin Flow или Coroutines с функциями коллекций Kotlin (distinctBy) предоставляет наиболее декларативный и эффективный способ. Для сложных случаев стоит рассмотреть пагинацию и кеширование в локальной базе, что также решает проблему дублей на уровне персистентного хранилища.