diff --git a/android/festago/app/build.gradle.kts b/android/festago/app/build.gradle.kts index 842d69f06..d8ef87b2a 100644 --- a/android/festago/app/build.gradle.kts +++ b/android/festago/app/build.gradle.kts @@ -158,6 +158,11 @@ dependencies { // splash implementation("androidx.core:core-splashscreen:1.1.0-alpha02") + + // room + implementation("androidx.room:room-runtime:2.6.0") + implementation("androidx.room:room-ktx:2.6.0") + kapt("androidx.room:room-compiler:2.6.0") } fun getSecretKey(propertyKey: String): String { diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dao/FestagoDatabase.kt b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestagoDatabase.kt new file mode 100644 index 000000000..99d30b089 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestagoDatabase.kt @@ -0,0 +1,29 @@ +package com.festago.festago.data.dao + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database( + entities = [ + FestivalEntity::class, + ], + version = 1, + exportSchema = true, +) +abstract class FestagoDatabase : RoomDatabase() { + abstract fun festivalDao(): FestivalDao + + companion object { + private const val DATABASE_NAME = "festago.db" + + fun buildDatabase(context: Context): FestagoDatabase { + return Room.databaseBuilder( + context, + FestagoDatabase::class.java, + DATABASE_NAME, + ).build() + } + } +} diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalDao.kt b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalDao.kt new file mode 100644 index 000000000..619999338 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalDao.kt @@ -0,0 +1,24 @@ +package com.festago.festago.data.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +@Dao +interface FestivalDao { + @Query("SELECT * FROM festivals") + fun getFestivals(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertFestivals(festivals: List): List + + @Query("DELETE FROM festivals") + fun clearFestivals() + + fun replaceFestivals(festivals: List): List { + clearFestivals() + return insertFestivals(festivals) + } +} diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalEntity.kt b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalEntity.kt new file mode 100644 index 000000000..6207ee259 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/data/dao/FestivalEntity.kt @@ -0,0 +1,15 @@ +package com.festago.festago.data.dao + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "festivals") +data class FestivalEntity( + @PrimaryKey + val id: Int, + val schoolId: Int, + val name: String, + val startDate: String, + val endDate: String, + val thumbnail: String, +) diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dao/mapper/FestivalMapper.kt b/android/festago/app/src/main/java/com/festago/festago/data/dao/mapper/FestivalMapper.kt new file mode 100644 index 000000000..cabe0f6a7 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/data/dao/mapper/FestivalMapper.kt @@ -0,0 +1,26 @@ +package com.festago.festago.data.dao.mapper + +import com.festago.festago.data.dao.FestivalEntity +import com.festago.festago.model.Festival +import java.time.LocalDate + +fun List.toDomain() = map(FestivalEntity::toDomain) + +fun FestivalEntity.toDomain() = Festival( + id = id.toLong(), + name = name, + startDate = LocalDate.parse(startDate), + endDate = LocalDate.parse(endDate), + thumbnail = thumbnail, +) + +fun List.toEntity() = map(Festival::toEntity) + +fun Festival.toEntity() = FestivalEntity( + id = this.id.toInt(), + schoolId = -1, + name = this.name, + startDate = this.startDate.toString(), + endDate = this.endDate.toString(), + thumbnail = this.thumbnail, +) diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/DaoModule.kt b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/DaoModule.kt new file mode 100644 index 000000000..f6aef1e21 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/DaoModule.kt @@ -0,0 +1,18 @@ +package com.festago.festago.data.di.singletonscope + +import android.content.Context +import com.festago.festago.data.dao.FestagoDatabase +import com.festago.festago.data.dao.FestivalDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +object DaoModule { + @Provides + fun provideFestivalDao(@ApplicationContext context: Context): FestivalDao = + FestagoDatabase.buildDatabase(context).festivalDao() +} diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt index 12326dd03..a2cd88765 100644 --- a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt +++ b/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt @@ -1,19 +1,37 @@ package com.festago.festago.data.repository +import com.festago.festago.data.dao.FestivalDao +import com.festago.festago.data.dao.mapper.toDomain +import com.festago.festago.data.dao.mapper.toEntity import com.festago.festago.data.service.FestivalRetrofitService import com.festago.festago.data.util.onSuccessOrCatch import com.festago.festago.data.util.runCatchingResponse import com.festago.festago.model.Festival import com.festago.festago.model.Reservation import com.festago.festago.repository.FestivalRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.launch import javax.inject.Inject class FestivalDefaultRepository @Inject constructor( + private val festivalDao: FestivalDao, private val festivalRetrofitService: FestivalRetrofitService, ) : FestivalRepository { + override val festivals: Flow> + get() = festivalDao.getFestivals().transform { emit(it.toDomain()) } + override suspend fun loadFestivals(): Result> = runCatchingResponse { festivalRetrofitService.getFestivals() } - .onSuccessOrCatch { it.toDomain() } + .onSuccessOrCatch { festivals -> + festivals.toDomain().also { + CoroutineScope(Dispatchers.IO).launch { + festivalDao.replaceFestivals(it.toEntity()) + } + } + } override suspend fun loadFestivalDetail(festivalId: Long): Result = runCatchingResponse { festivalRetrofitService.getFestivalDetail(festivalId) } diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt index 78d86dc88..3db1348c8 100644 --- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt @@ -28,26 +28,34 @@ class FestivalListViewModel @Inject constructor( private val _event = MutableSharedFlow() val event: SharedFlow = _event.asSharedFlow() + init { + viewModelScope.launch { + festivalRepository.festivals.collect { + _uiState.value = FestivalListUiState.Success( + festivals = it.map { festival -> + FestivalItemUiState( + id = festival.id, + name = festival.name, + startDate = festival.startDate, + endDate = festival.endDate, + thumbnail = festival.thumbnail, + onFestivalDetail = ::showTicketReserve, + ) + }, + ) + } + } + } + fun loadFestivals() { viewModelScope.launch { - festivalRepository.loadFestivals() - .onSuccess { - _uiState.value = FestivalListUiState.Success( - festivals = it.map { festival -> - FestivalItemUiState( - id = festival.id, - name = festival.name, - startDate = festival.startDate, - endDate = festival.endDate, - thumbnail = festival.thumbnail, - onFestivalDetail = ::showTicketReserve, - ) - }, - ) - }.onFailure { - _uiState.value = FestivalListUiState.Error - analyticsHelper.logNetworkFailure(KEY_LOAD_FESTIVALS_LOG, it.message.toString()) - } + festivalRepository.loadFestivals().onFailure { + _uiState.value = FestivalListUiState.Error + analyticsHelper.logNetworkFailure( + KEY_LOAD_FESTIVALS_LOG, + it.message.toString(), + ) + } } } diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt index db08d52dd..6b0bcedeb 100644 --- a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt +++ b/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt @@ -2,8 +2,11 @@ package com.festago.festago.repository import com.festago.festago.model.Festival import com.festago.festago.model.Reservation +import kotlinx.coroutines.flow.Flow interface FestivalRepository { + val festivals: Flow> + suspend fun loadFestivals(): Result> suspend fun loadFestivalDetail(festivalId: Long): Result }