2024. 3. 29. 20:13ㆍIT/Android
<미리 보기>
<소스 코드>
<정리>
Flow<T>.collect() - 값이 변경될 때마다 수집하고 방출한다.
Flow<T>.single() - 가장 첫 번째 값을 수집하고 방출하지만, 비어 있거나 두 개 이상의 값을 수집하면 예외를 발생시킨다.
Flow<T>.first() - 가장 첫 번째 값만 수집하고 방출하는데 이후의 값은 무시하고 비어 있는 경우 예외를 발생시킨다.
위의 프로그램에선 DataStore<Preferences>를 사용하여 텍스트 필드의 값을 저장하고 프로그램이 시작할 때 초기화 함수로 단 한번만 수집하여 출력하면 되기 때문에 Flow<T>.first()를 사용했다.
init {
initialiseFlight()
}
private fun initialiseFlight() {
viewModelScope.launch {
_uiState.update { flightUiState ->
flightUiState.copy(
currentText = flightsPreferencesAccessor.currentText.first()
) //flightsPreferencesAccessor.currentText은 Flow<String>을 반환.
}
updateFavoriteAirports()
updateRelatedAirports(_uiState.value.currentText)
}
}
Room 데이터 베이스에 Kotlin 기본 타입이 아닌 사용자 지정 타입 데이터의 삽입은 변환기를 사용하면 가능하다.
Favorite타입의 객체를 데이터 베이스에 삽입하려고 하지만 해당 필드의 타입이 Airport인 관계로 컴파일러는 에러를 발생시킨다.
@Entity(tableName = "airport")
data class Airport(
@PrimaryKey(autoGenerate = true)
val id: Int = 1,
@ColumnInfo(name = "iata_code")
val iataCode: String,
val name: String,
val passengers: Int
)
@Entity(tableName = "favorite")
data class Favorite(
@PrimaryKey
val id: String = "",
@ColumnInfo(name = "departure_airport")
val departureAirport: Airport,
@ColumnInfo(name = "destination_airport")
val destinationAirport: Airport
)
컴파일러가 이해할 수 있도록 변환기를 정의하는데, 쉽게 말해 사용자 지정 타입 객체의 프로퍼티들을 하나의 문자열로 변환하여 저장했다가 꺼낼 때 이 문자열을 쪼개서 다시 사용자 타입 객체를 생성하는 방식이다. Airport 객체의 프로퍼티 값들을 하나의 문자열로 변환할 때 각각을 쉼표로 구분하였고, 이를 기준으로 다시 Airport 객체를 생성한다.
class AirportConverter {
@TypeConverter
fun fromAirport(airport: Airport): String {
return "${airport.id},${airport.iataCode},${airport.name},${airport.passengers}"
} //저장할 때 사용되는 변환기.
@TypeConverter
fun toAirport(value: String): Airport {
val parts = value.split(",")
return Airport(parts[0].toInt(), parts[1], parts[2], parts[3].toInt())
} //꺼낼 때 사용되는 변환기.
}
정의한 변환기를 데이터 베이스에 어노테이션을 사용하여 적용하면 끝.
@Database(entities = [Airport::class, Favorite::class], version = 4, exportSchema = false)
@TypeConverters(AirportConverter::class)
abstract class FlightDatabase: RoomDatabase() {
abstract fun airportDao(): AirportDao
abstract fun favoriteDao(): FavoriteDao
//...생략
}
파라미터로 값을 받아 해당 텍스트를 포함하는 쿼리문의 작성은 '||' 기호를 사용하면 가능하다. ( '+' 기호는 안됨 )
@Dao
interface AirportDao {
@Query("""
SELECT * FROM airport
WHERE name LIKE '%' || :text || '%' OR iata_code LIKE '%' || :text || '%'
""")
suspend fun getRelatedAirports(text: String): List<Airport>
//...생략
}
테이블 정의에서 자동으로 증분하는 기본 키 설정 어노테이션과 초기 값을 설정해도 해당 객체를 생성할 때 기본 키 값을 전달하지 않으면 해당 설정은 적용되지 않는다.
이렇게 자동 증분 키 생성 어노테이션과 초기 값을 설정하고,
@Entity(tableName = "favorite")
data class Favorite(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "departure_airport")
val departureAirport: Airport,
@ColumnInfo(name = "destination_airport")
val destinationAirport: Airport
)
기본 키를 제외하고 객체를 생성하면,
Favorite(departureAirport = this, destinationAirport = airport)
데이터 베이스는 해당 객체를 생성할 때 증분 되는 키의 기준이 없다고 생각해 초기 값을 적용한다. 초기 값을 기준으로 증분되는 키를 생성하지 않으니 꼭 기본 키의 값을 전달하도록 하자.
테이블의 스키마를 변경은 마이그레이션을 정의를 통해 가능하다.
자세한 내용은 https://medium.com/androiddevelopers/understanding-migrations-with-room-f01e04b07929 참고.
@Database(entities = [Airport::class, Favorite::class], version = 2, exportSchema = false)
@TypeConverters(AirportConverter::class)
abstract class FlightDatabase: RoomDatabase() {
abstract fun airportDao(): AirportDao
abstract fun favoriteDao(): FavoriteDao
companion object {
@Volatile
private var INSTANCE: FlightDatabase? = null
val migration1to2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE new_favorite (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, departureAirport TEXT NOT NULL, destinationAirport TEXT NOT NULL)")
database.execSQL("DROP TABLE favorite")
database.execSQL("ALTER TABLE new_favorite RENAME TO favorite")
}
}
fun getDatabase(context: Context): FlightDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(context, FlightDatabase::class.java, "flight_search")
.createFromAsset("database/flight_search.db")
.addMigrations(migration1to2)
.build()
.also { INSTANCE = it }
}
}
}
}
비동기적으로 함수를 호출하여 해당 결과를 받아 반환하는 함수를 정의하는 바보는 본인 하나로 충분하다.
fun favoriteIntoAirports(favorite: Favorite): Pair<Airport, Airport> {
lateinit var airports: Pair<Airport, Airport>
viewModelScope.launch {
airports = Pair(flightsAccessor.getAirport(favorite.departureCode), flightsAccessor.getAirport(favorite.destinationCode))
}
return airports
}
airports는 초기화 되지 않고 반환된다.
'IT > Android' 카테고리의 다른 글
Jetpack Compose - WorkManager Practice (0) | 2024.04.03 |
---|---|
Jetpack Compose - WorkManager (0) | 2024.04.03 |
Jetpack Compose - DataStore<Preferences> (0) | 2024.03.20 |
Jetpack Compose - Room Practice (0) | 2024.03.19 |
Jetpack Compose - Room (0) | 2024.03.19 |