Repository Pattern — ett mönster som lägger till ett abstraktionslager mellan affärslogik och datakällor. Istället för direkta anrop till API, databas eller cache tillhandahåller Repository ett enhetligt gränssnitt för att hämta och spara data. Detta förenklar testning och växling mellan källor. Mer i Android Data Layer-dokumentationen.
Huvudpunkter
Repository Pattern — ett strukturellt mönster som isolerar affärslogik från direkt åtkomst till datakällor. Istället för att Activity, UIViewController eller ViewModel direkt anropar Retrofit, URLSession, Room eller CoreData, vänder de sig till Repository. Repository bestämmer var data ska hämtas: från nätverket, databasen eller cachen, och returnerar resultatet i ett enhetligt format. Detta är en implementering av principen om enskilt ansvar — UI vet inte hur och var data har hämtats.
Repository-komponenter inkluderar gränssnitt (protocol), implementering och en eller flera DataSources. DataSource — en klass som arbetar med en källa: RemoteDataSource anropar API via HTTP-klient, LocalDataSource läser och skriver till databasen. Repository tar emot DataSources via konstruktorn (Dependency Injection) och väljer vilken källa att använda. Vid en begäran om användarlista kontrollerar Repository först cachen, sedan databasen, därefter nätverket.
Fördelar med Repository Pattern: isolering av ändringar i datakällor (API-ändring, DB-migrering) påverkar inte UI-lagret; enhetstestning genom att ersätta Repository eller DataSource; cachning transparent för UI; växling mellan online- och offlineläge utan att ändra skärmlogik. Android-communityt rekommenderar Repository som ett obligatoriskt lager i Clean Architecture.
iOS-implementering av Repository bygger på Swift-protokoll. Repository-protokollet deklarerar metoder för att hämta och spara data. Den faktiska implementeringen injiceras via initieraren — detta möjliggör byte av implementering i tester och SwiftUI-förhandsvisningar. DataSources deklareras också med protokoll: Protocol RemoteDataSource, Protocol LocalDataSource. ViewModel eller Interactor känner inte till den konkreta implementeringen — bara Repository-protokollet.
protocol UserRepository {
func getUsers() async throws -> [User]
}
protocol UserRemoteDataSource {
func fetchUsers() async throws -> [User]
}
protocol UserLocalDataSource {
func getCachedUsers() throws -> [User]
func saveUsers(_: [User]) throws
}
final class UserRepositoryImpl: UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
init(remote: UserRemoteDataSource, local: UserLocalDataSource) {
self.remote = remote
self.local = local
}
func getUsers() async throws -> [User] {
if let cached = try? local.getCachedUsers() {
return cached
}
let users = try await remote.fetchUsers()
try local.saveUsers(users)
return users
}
}
Dependency Injection i iOS för Repository konfigureras vanligtvis via en fabrik eller DI-container (Swinject, Factory). I tester ersätts UserRepository-protokollet med en mock-implementering som returnerar fördefinierade data. Async-await gör koden synkron och läsbar utan closures och delegater. För Combine-reaktivitet returnerar Repository-metoder AnyPublisher istället för async throws.
Android-implementering av Repository använder flitigt Kotlin Coroutines och Flow för asynkront arbete. Google rekommenderar Repository i den officiella Android-arkitekturguiden (Android Architecture Components). Repository tar emot RemoteDataSource (Retrofit) och LocalDataSource (Room) via konstruktorn, och ViewModel prenumererar på Flow från Repository. Repository hanterar datastrategi: först cache, sedan nätverk, eller alltid nätverk med skrivning till cache.
interface UserRepository {
fun getUsers(): Flow<Result<List<User>>>
}
interface UserRemoteDataSource {
suspend fun fetchUsers(): List<User>
}
interface UserLocalDataSource {
fun getCachedUsers(): Flow<List<User>>
suspend fun saveUsers(users: List<User>)
}
class UserRepositoryImpl(
private val remote: UserRemoteDataSource,
private val local: UserLocalDataSource
) : UserRepository {
override fun getUsers(): Flow<Result<List<User>>> = flow {
emit(Result.Loading)
local.getCachedUsers().collect { cached ->
if (cached.isNotEmpty()) {
emit(Result.Success(cached))
}
}
try {
val users = remote.fetchUsers()
local.saveUsers(users)
emit(Result.Success(users))
} catch (e: Exception) {
emit(Result.Error(e))
}
}
}
Result-omslag i exemplet ovan är standard för Android: sealed class Result informerar ViewModel om laddningsstatus (Loading, Success, Error). ViewModel prenumererar via collect och uppdaterar StateFlow eller LiveData. Repository med Flow meddelar automatiskt UI om ändringar i databasen — detta är den viktigaste skillnaden från engångsfrågor där UI inte får reda på ändringar utan manuell uppdatering.
DataSource — klasser som ansvarar för att arbeta med en specifik datakälla. RemoteDataSource använder HTTP-klient (URLSession, Retrofit, Ktor) för att hämta data från API. LocalDataSource arbetar med lokal lagring (CoreData, Realm, Room, UserDefaults, DataStore). Varje DataSource har ett snävt ansvar: RemoteDataSource känner bara till API-begäranformatet, LocalDataSource — databasschemat. Repository kombinerar dem och implementerar en cachelagringsstrategi.
| DataSource | iOS-plattform | Android-plattform | Källa |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | SQLite på enheten |
| Local (cache) | NSCache, UserDefaults | DataStore, EncryptedSP | I minnet / disk |
| Preferens | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Inställningar, tokens |
Cachelagringsstrategier i Repository: Cache-First (först cache, sedan bakgrundsladdning), Network-Only (endast nätverk, för betalningsskärmar), Network-First-With-Cache-Backup (först nätverk, vid fel — cache). Valet av strategi beror på scenario: landslista kan cachas länge, valutakurser — 15 minuter, plånbokssaldo — endast från nätverket. Repository implementerar strategin och ändrar den utan att ändra ViewModel eller UI.
Repository och Service — olika mönster med överlappande funktioner. Repository ansvarar för dataåtkomst och cachning och returnerar datamodeller. Service (eller Interactor, Use Case) innehåller affärslogik: validering, datatransformering, orkestrering av anrop till flera Repository. Service kan kombinera UserRepository, OrderRepository och NotificationRepository för att behandla en beställning. Repository innehåller ingen affärslogik — endast CRUD och cachning.
När välja Repository — datanavigering med flera källor (API + DB + cache), offline-first-arkitektur, behov av cachning och transparent växling mellan källor. Repository är obligatoriskt i Clean Architecture och rekommenderas av Google för Android-applikationer. I VIPER-arkitektur på iOS utförs Repository-rollen av Interactor-lagret, som interagerar med Manager eller Service för dataåtkomst.
När räcker Service — enkla applikationer med en datakälla, skrivskyddade skärmar utan skrivning, projekt utan offlineläge. I sådana fall används DataSource direkt av ViewModel eller Presenter, och Repository blir ett överflödigt lager. Att lägga till Repository i ett tidigt skede kräver dock inte stora kostnader och underlättar framtida tillägg av cachning och tester.
Vanliga frågor
DataSource — en klass som arbetar med en källa (API, DB, cache). Repository — en klass som hanterar flera DataSources och tillhandahåller ett enhetligt gränssnitt. Repository bestämmer från vilken DataSource data ska hämtas och samordnar cachning. DataSource vet inte om andra källors existens, Repository känner inte till implementeringsdetaljerna för varje källa.
Ja, Repository är användbart i SwiftUI för att separera data från View. ViewModel prenumererar på Publisher från Repository, och Repository hanterar cachning och synkronisering. I enkla applikationer kan URLSession användas direkt i ViewModel, men för testbarhet och skalbarhet är Repository att föredra. Apple påtvingar inte detta mönster, men det är kompatibelt med SwiftData och Network.framework.
DataSources ersätts med mock-objekt via Dependency Injection. Testet skapar en mock RemoteDataSource (returnerar fördefinierad JSON) och en mock LocalDataSource (kontrollerar att data har sparats). Repository testas isolerat: cachelagringsstrategi, felhantering och korrekt anropsordning kontrolleras. För integrationstester används TestDispatcher (Kotlin) eller MainActor.run (Swift).
Det går, men rekommenderas inte. Utan protokoll går det inte att byta implementering i tester och förhandsvisningar. I Kotlin tillåter Repository-gränssnittet byte av implementering via DI (Dagger, Hilt, Koin). I Swift är Repository-protokollet obligatoriskt för att testa async-await och Combine-kod. Undantag — enkla projekt med en datakälla där Repository inte innehåller cachelagringslogik.
Offline-first — en strategi där applikationen fungerar utan internet med hjälp av lokal data. Repository spelar en nyckelroll: det returnerar först data från den lokala DataSource, sedan synkroniseras med servern i bakgrunden. Användaren ser data omedelbart och Repository uppdaterar dem efter inläsning från nätverket. Room med Flow ger reaktiva UI-uppdateringar när data ändras i den lokala databasen.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också