Как объединить данные с нескольких API-запросов, исключив дубликаты перед отображением
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Объединение данных из нескольких API с исключением дубликатов
В современной разработке Android часто приходится работать с несколькими источниками данных. Объединение ответов от разных API с фильтрацией дубликатов — типичная задача, требующая внимательного подхода к архитектуре и обработке данных.
Основные стратегии и подходы
Ключевые принципы:
- Определение критерия уникальности — что делает объект дублем? (ID, комбинация полей).
- Выбор точки объединения — на уровне сетевых запросов, в репозитории или при преобразовании.
- Обеспечение реактивности и производительности — особенно при большом объеме данных.
Практическая реализация на 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) предоставляет наиболее декларативный и эффективный способ. Для сложных случаев стоит рассмотреть пагинацию и кеширование в локальной базе, что также решает проблему дублей на уровне персистентного хранилища.