이력서랑 포트폴리오, 지원서, 자소서 준비하다보니 이걸 건드릴 시간이 없었다...
✔ 요약
RoomDatabase 생성 및 Hilt 적용
데이터 불러올 때 Paging3와 StateFlow 사용
1. RoomDatabase 생성
plugins {
...
id("kotlin-kapt")
}
dependencies {
...
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-paging:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
}
시작은 Data 모듈에서 Entity를 만드는 걸로 시작했다.
@Entity 어노테이션과 @PrimaryKey 어노테이션을 붙여 테이블 구성요소가 어떤 형태인지 알려줬다.
@Entity(tableName = "table_card")
data class CardEntity(
@PrimaryKey
val cardCode: String = "",
val name: String = "",
val rank: String = "",
val company: String = "",
val cardImage: String = "",
val addedDate: String = "",
val badge: Boolean = false
)
그 다음은 데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스인 DAO를 정의하는 것이다.
@Dao 어노테이션으로 DAO임을 알려준다.
메소드를 정의할 때는 @Insert, @Query, @Delete 어노테이션을 쓰면 된다.@Insert 어노테이션에는 onConflict 파라미터가 있는데 충돌처리방식을 처리하는 방식을 결정하는 것이다.
충돌처리방식의 종류
1. OnConflictStrategy.ABORT : 충돌이 발생할 경우 처리 중단
2. OnConflictStrategy.FAIL : 충돌이 발생할 경우 실패처리
3. OnConflictStrategy.IGNORE : 충돌이 발생할 경우 무시
4. OnConflictStrategy.REPLACE : 충돌이 발생할 경우 덮어쓰기
5. OnConflictStrategy.ROLLBACK : 충돌이 발생할 경우 이전으로 되돌리기
@Query 어노테이션에서는 SQL 쿼리문을 작성하면 된다.
파라미터로 들어온 변수값을 쿼리문을 사용하고 싶다면 :(콜론)을 변수명 앞에 붙여주면 된다.
@Dao
interface CardDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCard(card: CardEntity)
@Query("DELETE FROM table_card WHERE name = :name")
fun deleteCard(name: String)
@Query("SELECT * FROM table_card")
fun getAllCards(): PagingSource<Int, CardEntity>
@Query("SELECT * FROM table_card WHERE company = :company")
fun getAllCardsWithCompany(company: String): PagingSource<Int, CardEntity>
@Query("SELECT * FROM table_card ORDER BY addedDate ASC")
fun getAllSortedCardsWithAddedDate(): PagingSource<Int, CardEntity>
}
마지막은 데이터베이스를 생성하고 관리하는 데이터베이스 객체 Database를 만드는 것이다.
@Database 어노테이션을 사용하고 사용하는 Entity의 클래스와 version을 적어준다.
그리고 추상 클래스로 RoomDatabase를 상속받도록 하고 그 안에는 추상 함수가 전에 만든 Dao를 상속받도록 한다.
Hilt를 사용하지 않았다면 클래스 안에서 companion object를 만들어서 싱글턴 객체를 만들어줘야할텐데 이 프로젝트에서는 Hilt를 사용하기 때문에 그 과정은 없다.
@Database(entities = [CardEntity::class], version = 1)
abstract class CardDatabase: RoomDatabase() {
abstract fun cardDao(): CardDao
}
2. RoomDatabase를 Hilt에 적용
다른 건 없이 DAO와 Database 객체를 만드는 함수를 전에 Firebase 적용할 때와 같이 만들면 된다.
둘다 싱글턴 객체여야하기 때문에 @Singleton 어노테이션을 붙여주고 함수를 완성한다.
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
...
@Provides
@Singleton
fun provideCardDao(cardDatabase: CardDatabase): CardDao = cardDatabase.cardDao()
@Provides
@Singleton
fun provideCardDatabase(@ApplicationContext context: Context): CardDatabase = Room.databaseBuilder(
context,
CardDatabase::class.java,
"Card.db"
).build()
...
}
3. RoomDatabase 사용
DAO를 사용하는 CardLocalDataSource 인터페이스를 정의하고 구현하는 클래스를 만들었다.
그리고 그 인터페이스를 사용하는 CardRepository 인터페이스를 Domain 모듈에서 정의하고 Data 모듈에서 구현 클래스를 만들었다.
CardRepository는 그냥 바로 Presentation 모듈에서 사용되지 않고 유즈케이스를 통하여 사용된다.
뷰모델에서 해당 유즈케이스를 주입받아 사용하면 지금까지 말한 거에 역순으로 흘러가며 RoomDatabase에 접근해 데이터를 저장하고 불러오고 수정하고 삭제한다.
@HiltViewModel
class StorageViewModel @Inject constructor(
private val saveBusinessCard: SaveBusinessCardUsecase,
private val getBusinessCards: GetAllCardsUsecase
): ViewModel() {
private val _pagingCards: StateFlow<PagingData<StorageItem>> =
getBusinessCards.invoke()
.map { pagingData ->
pagingData.map { card ->
StorageItem(
cardCode = card.cardCode,
name = card.name,
rank = card.rank,
company = card.company,
cardImage = card.cardImage,
addedDate = card.addedDate,
badge = card.badge
)
}
}
.cachedIn(viewModelScope)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), PagingData.empty())
val pagingCards get() = _pagingCards
fun save(card: Card) {
viewModelScope.launch(Dispatchers.IO) {
saveBusinessCard(card)
}
}
}
RoomDatabase에서 데이터를 불러와서 RecyclerView에 보여줄 때 Paging3 라이브러리와 StateFlow를 사용했다.
그리고 PagingData를 처리해서 화면에 보여주는 PagingDataAdapter를 만들어서 연결해줬다.
PagingDataAdapter는 ListAdapter와 거의 똑같고 getItem(position)이 null일 수 있어서 이 부분 처리해주는 것만 달랐다.
Paging3 라이브러리와 StateFlow는 따로 정리할 예정..!
만든 유즈케이스도 의존성 주입을 위해 모듈 클래스에서 객체 생성방법을 정의해줬다.
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
...
@Provides
fun provideSaveBusinessCardUsecase(cardRepository: CardRepository): SaveBusinessCardUsecase = SaveBusinessCardUsecase(cardRepository)
@Provides
fun provideGetAllCardsUsecase(cardRepository: CardRepository): GetAllCardsUsecase = GetAllCardsUsecase(cardRepository)
...
}
'Android > Kotlin' 카테고리의 다른 글
<정리> SelfCheckout & BoxReading (0) | 2025.01.31 |
---|---|
<정리> 명함 앱 만들기 4일차 (0) | 2024.06.17 |
<정리> 명함 앱 만들기 3일차 (1) | 2024.06.12 |
<정리> 명함 앱 만들기 2일차 (0) | 2024.06.10 |
<정리> 명함 앱 만들기 1일차 (1) | 2024.06.05 |