Room과 Flow을 결합하는 방법을 학습하여 정리함
샘플 기능 내용
데이터 구조
data class User(
val id : Long,
val name :String,
val favorites : List<Favorite>
){
data class Favirite(
val id : Int,
val name : String
)
}
도메인 레이어에서는 User 모델을 사용하고
Local 레이어에서는 UserEntity, FavoriteEntity로 분리하여 저장
1 : N으로 정의된 Room 테이블에서 데이터가 업데이트 될때마다 Listen을 할 수 있게 만드는게 목적
1. 버튼을 누를때마다 현재 유저의 favoriteList에 추가된다.
2. 업데이트 될때마다 Flow형태로 구독할 수 있다.
3. Room Interface Dao를 정의할때 데이터를 가져오는 방법이 크게 두가지가 있다
- 중간 데이터 클래스를 만들어 관계를 지정하는 방법
- Query는 간단하지만 Entity 클래스를 별도로 정의해야함
- 멀티 맵핑 ( Room 2.4 이상)
- Query는 복잡하지만 코드의 복잡도는 낮아짐
- 구글에서 추천하는 방법
4. 기능 정의
- fetchUser() : Flow<User> -> 현재 UserId로 User객체 ( List<Favorite> 포함 ) 를 가져오는 함수
- 중간 데이터 클래스로 가져옴
- fetchAll() : Flow<List<User>> -> 저장되어 있는 모든 유저정보를 가져오는 함수
- 멀티 맵핑 형태로 가져옴
- updateFavorite(favorite: User.Favorite)
(가정)
- 여러개의 계정을 로그인, 로그아웃 허용
- 로그인 할때마다 저장되어 있는 유저정보가 있다면 가져와서 사용
- 멀티모듈, Hilt 적용
- DI는 이전글의 DataStore와 비슷하기에 생략
UserEntitiy
@Entity(tableName = "user")
internal data class UserEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: Long,
@ColumnInfo(name = "name")
val name: String
)
FavoriteEntity
@Entity(tableName = "favorite", primaryKeys = ["id", "userId"])
internal data class FavoriteEntity(
@ColumnInfo(name = "id")
val id: Int,
@ColumnInfo(name = "userId")
val userId: Long,
@ColumnInfo(name = "name")
val name: String,
)
UserWithFavoritesEntity
internal data class UserWithFavoriteEntity(
@Embedded val user: UserEntity,
@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val favorites: List<FavoriteEntity>
)
UserDao
@Query("SELECT * FROM user WHERE user.id = :userId")
fun fetchUser(userId: Long): Flow<UserWithFavoriteEntity> // 중간 데이터 클래스 정의
@Query("SELECT * FROM user JOIN favorite ON user.id = favorite.userId")
fun fetchAll(): Flow<Map<UserEntity, List<FavoriteEntity>>> // 멀티 맵핑 정의
FavoriteDao
@Insert(onConflict = REPLACE)
suspend fun insert(favorite: FavoriteEntity)
UserRepository
internal class UserRepositoryImpl @Inject constructor(
private val local: UserLocalDataSource,
private val session: SessionLocalDataSource
) : UserRepository {
override fun fetchUser(): Flow<User> {
return session.fetchUserId() // Flow<Long?> 형태
.filterNotNull()
.flatMapLatest {
local.fetchUser(it).map { user ->
User(
id = user.id,
name = user.name,
favorites = user.favorites.map { favorite ->
User.Favorite(
id = favorite.id,
name = favorite.name
)
}
)
}
}
}
}
override fun fetchAll() = local.fetchAll()
.map {
it.map { user ->
User(
id = user.id,
name = user.name,
favorites = user.favorites.map { favorite ->
User.Favorite(
id = favorite.id,
name = favorite.name
)
}
)
}
}
//Favorite 테이블에 데이터를 추가하는 기능
override suspend fun updateFavorite(favorite: User.Favorite) {
val userId = session.fetchUserId().firstOrNull() ?: return
local.updateFavorite(
userId = userId,
favorite = UserData.Favorite(
id = favorite.id,
name = favorite.name
)
)
}
}
UseCase
@Singleton
class FetchUserUseCase @Inject constructor(
private val repository: UserRepository
) {
operator fun invoke(): Flow<User> = repository.fetchUser() ?: emptyFlow()
}
@Singleton
class FetchUserListUseCase @Inject constructor(
private val repository: UserRepository
) {
operator fun invoke(): Flow<List<User>> = repository.fetchAll()
}
@Singleton
class UpdateFavoriteUseCase @Inject constructor(
private val repository: UserRepository
) {
suspend operator fun invoke(
id: Int,
name: String
) = repository.updateFavorite(
User.Favorite(
id = id,
name = name
)
)
}
//ViewModel
val user = fetchUserUseCase.invoke()
val userList = fetchUserListUseCase.invoke()
fun updateFavorite() = viewModelScope.launch {
updateFavoriteUseCase.invoke(
id = Random.nextInt(),
name = UUID.randomUUID().toString()
)
}
//Activity
repeatOnStarted {
viewModel.user.collect {
Log.e("ERROR", "user: $it")
}
}
repeatOnStarted {
viewModel.userList.collect {
Log.e("ERROR", "userList: ${it.joinToString("\n")}")
}
}
btn.setOnClickListener { // 버튼을 누를때마다 FavoriteTable에 데이터를 입력한다
viewModel.updateFavorite()
}
정리
유저정보를 가져오고 싶은데 단순히 유저정보만이 아닌 Relation 관계에 있는 정보들도 같이 가져오려고 한다.
이때 유저정보를 업데이트했을때 Noti 받는건 당연하고
Relation 정보를 업데이트 했을때도 Noti 를 받으려고 한다.
이런 상황에서 Room의 Interface가 잘 정의되어 있어 우리는 User 객체 하나로 추상화 할 수 있게 된다.
'Android' 카테고리의 다른 글
소셜 로그인 모듈화 과정 ( 추상화 및 액티비티 의존성 제거 ) (1) | 2022.09.29 |
---|---|
transition animation 적용 (0) | 2022.08.14 |
DataStore 적용하기 (0) | 2022.07.27 |
코루틴 공부 (0) | 2022.07.18 |
Facebook Login Trouble Shooting (0) | 2022.07.18 |