
SOPT 34기 안드로이드 파트 7주차 과제이자 개인적으로 공부해보고 싶었던 주제를 가져와봤다!

repository는 말 그대로 '저장소'이다. 그럼 저장소 패턴은 무엇일까?
Data의 출처 상관없이 동일한 인터페이스로 데이터에 접근 할 수 있게 해주는 패턴
즉 repository 패턴은 저장소에서 Local이나 Remote에 있는 필요한 데이터를 가져와 ViewModel에게 전달하는 방식이다.
그렇게 되면 ViewModel은 데이터에 직접 접근하지 않아도 되며, Data Layer를 캡슐화 할 수 있게 된다.
🌟 캡슐화란? - 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것
1. 코드의 가독성과 유지보수성 향상
2. 데이터 소스 교체 용이
3. 테스트 용이성
4. 재사용성
① 데이터 모델 정의
data class User(
val id: Int,
val name: String,
val email: String
)
② 데이터 소스 인터페이스 정의 : 데이터를 가져오는 방법을 정의하는 인터페이스를 만든다.
interface UserDataSource {
fun getUser(userId: Int): User?
fun getAllUsers(): List<User>
fun saveUser(user: User)
fun deleteUser(userId: Int)
}
③-𝟙 로컬 데이터 소스 구현 : 데이터를 로컬 데이터베이스에서 가져오는 로직 구현
class LocalUserDataSource(private val userDao: UserDao) : UserDataSource {
override fun getUser(userId: Int): User? {
return userDao.getUserById(userId)
}
override fun getAllUsers(): List<User> {
return userDao.getAllUsers()
}
override fun saveUser(user: User) {
userDao.insertUser(user)
}
override fun deleteUser(userId: Int) {
userDao.deleteUserById(userId)
}
}
③-𝟚 원격 데이터 소스 구현 : 데이터를 원격 서버(API)에서 가져오는 로직 구현
class RemoteUserDataSource(private val apiService: ApiService) : UserDataSource {
override fun getUser(userId: Int): User? {
val response = apiService.getUser(userId).execute()
return if (response.isSuccessful) response.body() else null
}
override fun getAllUsers(): List<User> {
val response = apiService.getAllUsers().execute()
return if (response.isSuccessful) response.body() ?: emptyList() else emptyList()
}
override fun saveUser(user: User) {
apiService.saveUser(user).execute()
}
override fun deleteUser(userId: Int) {
apiService.deleteUser(userId).execute()
}
}
④ 레포지터리 클래스 구현 : 레포지터리 클래스는 여러 데이터 소스를 관리하고 필요한 데이터를 제공하는 역할
class UserRepository(
private val localDataSource: UserDataSource,
private val remoteDataSource: UserDataSource
) {
fun getUser(userId: Int): User? {
// 우선 로컬 데이터 소스에서 데이터를 가져오고 없으면 원격 데이터 소스에서 가져옴
var user = localDataSource.getUser(userId)
if (user == null) {
user = remoteDataSource.getUser(userId)
if (user != null) {
localDataSource.saveUser(user) // 원격 데이터 소스에서 가져온 데이터를 로컬에 저장
}
}
return user
}
fun getAllUsers(): List<User> {
// 동일한 로직으로 모든 사용자 데이터를 가져옴
val users = localDataSource.getAllUsers()
return if (users.isEmpty()) {
val remoteUsers = remoteDataSource.getAllUsers()
remoteUsers.forEach { localDataSource.saveUser(it) }
remoteUsers
} else {
users
}
}
fun saveUser(user: User) {
localDataSource.saveUser(user)
remoteDataSource.saveUser(user) // 원격에도 저장
}
fun deleteUser(userId: Int) {
localDataSource.deleteUser(userId)
remoteDataSource.deleteUser(userId) // 원격에서도 삭제
}
}
⑤ ViewModel에서 레포지터리 사용 : ViewModel에서 레포지터리를 사용하여 데이터를 관리
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
fun loadUser(userId: Int) {
viewModelScope.launch {
val user = userRepository.getUser(userId)
_user.postValue(user)
}
}
fun saveUser(user: User) {
viewModelScope.launch {
userRepository.saveUser(user)
}
}
fun deleteUser(userId: Int) {
viewModelScope.launch {
userRepository.deleteUser(userId)
}
}
}
⑥ 의존성 주입 : 의존성 주입을 통해 ViewModel과 Repository를 연결 / Hilt 또는 Dagger와 같은 라이브러리를 사용
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
fun provideLocalUserDataSource(userDao: UserDao): UserDataSource = LocalUserDataSource(userDao)
@Provides
fun provideRemoteUserDataSource(apiService: ApiService): UserDataSource = RemoteUserDataSource(apiService)
@Provides
fun provideUserRepository(
localDataSource: UserDataSource,
remoteDataSource: UserDataSource
): UserRepository = UserRepository(localDataSource, remoteDataSource)
}
@HiltViewModel
class UserViewModel @Inject constructor(private val userRepository: UserRepository) : ViewModel() {
// ViewModel 코드
}