From ca9a734bf356770598cfd086150cd79c1b7bc4b0 Mon Sep 17 00:00:00 2001 From: abazov73 <92822431+abazov73@users.noreply.github.com> Date: Wed, 6 Dec 2023 02:29:08 +0400 Subject: [PATCH] Lab 05: add performances rest --- .../mobile_labs/api/MyServerService.kt | 7 +- .../api/models/PerformanceRemote.kt | 21 +++- .../performance/PerformanceRemoteMediator.kt | 112 ++++++++++++++++++ .../performance/RestPerformanceRepository.kt | 53 +++++++++ .../mobile_labs/common/AppContainer.kt | 15 ++- .../common/AppViewModelProvider.kt | 4 +- .../common/PerformanceRepository.kt | 5 +- .../performance/dao/PerformanceDao.kt | 5 +- .../OfflinePerformanceRepository.kt | 18 ++- .../performance/view/PerformanceViewModel.kt | 2 - 10 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt create mode 100644 app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt diff --git a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt index d65f264..3924a2d 100644 --- a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt +++ b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt @@ -28,7 +28,7 @@ interface MyServerService { suspend fun getPerformances( @Query("_page") page: Int, @Query("_limit") limit: Int, - ): List + ): List @GET("events") suspend fun getEvents( @@ -41,6 +41,11 @@ interface MyServerService { @Path("id") id: Int, ): PerformanceRemote + @GET("persons/{id}") + suspend fun getPerson( + @Path("id") id: Int, + ): PersonRemote + companion object { private const val BASE_URL = "http://10.0.2.2:26000/" diff --git a/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt index 5e1728f..306ed57 100644 --- a/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt +++ b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt @@ -1,6 +1,8 @@ package com.example.mobile_labs.api.models +import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.database.performance.model.Performance +import com.example.mobile_labs.database.performance.model.PerformanceWithPeople import com.example.mobile_labs.database.person.model.Person import kotlinx.serialization.Serializable @@ -12,7 +14,8 @@ data class PerformanceRemote( val authorId: Int = 0, val directorId: Int = 0, val imageURL: String = "", - val previewImageURL: String = "" + val previewImageURL: String = "", + val actors: List = listOf() ) fun PerformanceRemote.toPerformance(): Performance = Performance( @@ -23,4 +26,18 @@ fun PerformanceRemote.toPerformance(): Performance = Performance( directorId, imageURL, previewImageURL -) \ No newline at end of file +) + +suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService): PerformanceWithPeople { + val actorsList = ArrayList(); + actors.forEach {id -> + actorsList.add(service.getPerson(id).toPerson()) + } + + return PerformanceWithPeople( + service.getPerformance(id).toPerformance(), + service.getPerson(authorId).toPerson(), + service.getPerson(directorId).toPerson(), + actorsList.toList() + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt b/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt new file mode 100644 index 0000000..9b1eaa1 --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt @@ -0,0 +1,112 @@ +package com.example.mobile_labs.api.performance + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.api.models.toPerformance +import com.example.mobile_labs.api.models.toPerson +import com.example.mobile_labs.api.people.RestPersonRepository +import com.example.mobile_labs.database.AppDatabase +import com.example.mobile_labs.database.performance.model.Performance +import com.example.mobile_labs.database.performance.repository.OfflinePerformanceRepository +import com.example.mobile_labs.database.person.repository.OfflinePersonRepository +import com.example.mobile_labs.database.remotekeys.model.RemoteKeyType +import com.example.mobile_labs.database.remotekeys.model.RemoteKeys +import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class PerformanceRemoteMediator( + private val service: MyServerService, + private val dbPerformanceRepository: OfflinePerformanceRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val personRestRepository: RestPersonRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val performances = service.getPerformances(page, state.config.pageSize).map { it.toPerformance() } + val endOfPaginationReached = performances.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON) + dbPerformanceRepository.clearPerformances() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = performances.map { + RemoteKeys( + entityId = it.performance_uid !!, + type = RemoteKeyType.PERSON, + prevKey = prevKey, + nextKey = nextKey + ) + } + personRestRepository.getAllPeople() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbPerformanceRepository.insertPerformances(performances) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { performance -> + dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { performance -> + dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.performance_uid?.let { performanceUid -> + dbRemoteKeyRepository.getAllRemoteKeys(performanceUid, RemoteKeyType.PERSON) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt new file mode 100644 index 0000000..2725e5e --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt @@ -0,0 +1,53 @@ +package com.example.mobile_labs.api.performance + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.api.models.toPerformance +import com.example.mobile_labs.api.models.toPerformanceWithPeople +import com.example.mobile_labs.api.people.RestPersonRepository +import com.example.mobile_labs.common.AppDataContainer +import com.example.mobile_labs.common.PerformanceRepository +import com.example.mobile_labs.common.PersonRepository +import com.example.mobile_labs.database.AppDatabase +import com.example.mobile_labs.database.performance.model.Performance +import com.example.mobile_labs.database.performance.model.PerformanceWithPeople +import com.example.mobile_labs.database.performance.repository.OfflinePerformanceRepository +import com.example.mobile_labs.database.person.model.Person +import com.example.mobile_labs.database.person.repository.OfflinePersonRepository +import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository +import kotlinx.coroutines.flow.Flow + +class RestPerformanceRepository( + private val service: MyServerService, + private val dbPerformanceRepository: OfflinePerformanceRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val personRestRepository: RestPersonRepository, + private val database: AppDatabase +) : PerformanceRepository { + override fun getAllPerformances(): Flow> { + + val pagingSourceFactory = { dbPerformanceRepository.getAllPerformancesPagingSource() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppDataContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = PerformanceRemoteMediator( + service, + dbPerformanceRepository, + dbRemoteKeyRepository, + personRestRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + + override suspend fun getPerformance(uid: Int): PerformanceWithPeople = + service.getPerformance(uid).toPerformanceWithPeople(service) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt index ddf580e..6bf6d2c 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt @@ -3,6 +3,7 @@ package com.example.mobile_labs.common import android.content.Context import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.api.people.RestPersonRepository +import com.example.mobile_labs.api.performance.RestPerformanceRepository import com.example.mobile_labs.database.event.repository.EventRepository import com.example.mobile_labs.database.event.repository.OfflineEventRepository import com.example.mobile_labs.database.performance.repository.OfflinePerformanceRepository @@ -14,7 +15,7 @@ import com.example.mobile_labs.database.remotekeys.repository.RemoteKeyRepositor interface AppContainer { val personRestRepository: RestPersonRepository val eventRepository: EventRepository - val performanceRepository: PerformanceRepository + val performanceRestRepository: RestPerformanceRepository } class AppDataContainer(private val context: Context) : AppContainer { @@ -22,7 +23,7 @@ class AppDataContainer(private val context: Context) : AppContainer { OfflineEventRepository(AppDatabase.getInstance(context).eventDao()) } - override val performanceRepository: PerformanceRepository by lazy { + val performanceRepository: OfflinePerformanceRepository by lazy { OfflinePerformanceRepository(AppDatabase.getInstance(context).performanceDao()) } @@ -42,6 +43,16 @@ class AppDataContainer(private val context: Context) : AppContainer { ) } + override val performanceRestRepository: RestPerformanceRepository by lazy { + RestPerformanceRepository( + MyServerService.getInstance(), + performanceRepository, + remoteKeyRepository, + personRestRepository, + AppDatabase.getInstance(context) + ) + } + companion object { const val TIMEOUT = 5000L const val LIMIT = 3 diff --git a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt index a66a63a..6322cd4 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt @@ -20,10 +20,10 @@ object AppViewModelProvider { EventListViewModel(theatreApplication().container.eventRepository) } initializer { - PerformanceListViewModel(theatreApplication().container.performanceRepository) + PerformanceListViewModel(theatreApplication().container.performanceRestRepository) } initializer { - PerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRepository) + PerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository) } } } diff --git a/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt index cc3a23a..65e2161 100644 --- a/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt @@ -7,8 +7,5 @@ import kotlinx.coroutines.flow.Flow interface PerformanceRepository { fun getAllPerformances(): Flow> - fun getPerformance(uid: Int): Flow - suspend fun insertPerformance(performance: Performance) - suspend fun updatePerformance(performance: Performance) - suspend fun deletePerformance(performance: Performance) + suspend fun getPerformance(uid: Int): PerformanceWithPeople } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformanceDao.kt b/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformanceDao.kt index dfb9a87..62b662a 100644 --- a/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformanceDao.kt +++ b/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformanceDao.kt @@ -20,11 +20,14 @@ interface PerformanceDao { fun getByUid(uid: Int): Flow @Insert - suspend fun insert(performance: Performance) + suspend fun insert(vararg performance: Performance) @Update suspend fun update(performance: Performance) @Delete suspend fun delete(performance: Performance) + + @Query("DELETE FROM performances") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt index c4cb845..a408aef 100644 --- a/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt @@ -3,12 +3,15 @@ package com.example.mobile_labs.database.performance.repository import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import androidx.paging.PagingSource import com.example.mobile_labs.common.PerformanceRepository import com.example.mobile_labs.common.AppDataContainer import com.example.mobile_labs.database.performance.dao.PerformanceDao import com.example.mobile_labs.database.performance.model.Performance import com.example.mobile_labs.database.performance.model.PerformanceWithPeople +import com.example.mobile_labs.database.person.model.Person import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first class OfflinePerformanceRepository(private val performanceDao: PerformanceDao) : PerformanceRepository { @@ -19,11 +22,18 @@ class OfflinePerformanceRepository(private val performanceDao: PerformanceDao) : ), pagingSourceFactory = performanceDao::getAll ).flow - override fun getPerformance(uid: Int): Flow = performanceDao.getByUid(uid); + override suspend fun getPerformance(uid: Int): PerformanceWithPeople = performanceDao.getByUid(uid).first(); - override suspend fun insertPerformance(performance: Performance) = performanceDao.insert(performance); + suspend fun insertPerformance(performance: Performance) = performanceDao.insert(performance); - override suspend fun updatePerformance(performance: Performance) = performanceDao.update(performance); + suspend fun updatePerformance(performance: Performance) = performanceDao.update(performance); - override suspend fun deletePerformance(performance: Performance) = performanceDao.delete(performance); + suspend fun deletePerformance(performance: Performance) = performanceDao.delete(performance); + + suspend fun clearPerformances() = performanceDao.deleteAll() + + suspend fun insertPerformances(performances: List) = + performanceDao.insert(*performances.toTypedArray()) + + fun getAllPerformancesPagingSource(): PagingSource = performanceDao.getAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/ui/performance/view/PerformanceViewModel.kt b/app/src/main/java/com/example/mobile_labs/ui/performance/view/PerformanceViewModel.kt index 630cb34..c07fbfc 100644 --- a/app/src/main/java/com/example/mobile_labs/ui/performance/view/PerformanceViewModel.kt +++ b/app/src/main/java/com/example/mobile_labs/ui/performance/view/PerformanceViewModel.kt @@ -26,8 +26,6 @@ class PerformanceViewModel( viewModelScope.launch { if (studentUid > 0) { performanceUiState = performanceRepository.getPerformance(studentUid) - .filterNotNull() - .first() .toUiState() } }