Lab 05: done

This commit is contained in:
abazov73 2023-12-06 03:35:32 +04:00
parent ca9a734bf3
commit 6b3fc223c1
16 changed files with 226 additions and 31 deletions

View File

@ -46,6 +46,11 @@ interface MyServerService {
@Path("id") id: Int,
): PersonRemote
@GET("events/{id}")
suspend fun getEvent(
@Path("id") id: Int,
): EventRemote
companion object {
private const val BASE_URL = "http://10.0.2.2:26000/"

View File

@ -0,0 +1,112 @@
package com.example.mobile_labs.api.events
import android.util.Log
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.toEvent
import com.example.mobile_labs.api.performance.RestPerformanceRepository
import com.example.mobile_labs.database.AppDatabase
import com.example.mobile_labs.database.event.model.Event
import com.example.mobile_labs.database.event.model.EventWithPerformance
import com.example.mobile_labs.database.event.repository.OfflineEventRepository
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 EventRemoteMediator(
private val service: MyServerService,
private val dbEventRepository: OfflineEventRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val performanceRestRepository: RestPerformanceRepository,
private val database: AppDatabase
) : RemoteMediator<Int, EventWithPerformance>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, EventWithPerformance>
): 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 events = service.getEvents(page, state.config.pageSize).map { it.toEvent() }
val endOfPaginationReached = events.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.EVENT)
dbEventRepository.clearEvents()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = events.map {
RemoteKeys(
entityId = it.uid !!,
type = RemoteKeyType.EVENT,
prevKey = prevKey,
nextKey = nextKey
)
}
performanceRestRepository.getAllPerformances()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbEventRepository.insertEvents(events)
}
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<Int, EventWithPerformance>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { event ->
dbRemoteKeyRepository.getAllRemoteKeys(event.event.uid !!, RemoteKeyType.EVENT)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, EventWithPerformance>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { event ->
dbRemoteKeyRepository.getAllRemoteKeys(event.event.uid !!, RemoteKeyType.EVENT)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, EventWithPerformance>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.event?.uid?.let { eventUid ->
dbRemoteKeyRepository.getAllRemoteKeys(eventUid, RemoteKeyType.EVENT)
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.example.mobile_labs.api.events
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.toPerformanceWithPeople
import com.example.mobile_labs.api.performance.RestPerformanceRepository
import com.example.mobile_labs.common.AppDataContainer
import com.example.mobile_labs.common.EventRepository
import com.example.mobile_labs.common.PerformanceRepository
import com.example.mobile_labs.database.AppDatabase
import com.example.mobile_labs.database.event.model.Event
import com.example.mobile_labs.database.event.model.EventWithPerformance
import com.example.mobile_labs.database.event.repository.OfflineEventRepository
import com.example.mobile_labs.database.performance.model.Performance
import com.example.mobile_labs.database.performance.model.PerformanceWithPeople
import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestEventRepository(
private val service: MyServerService,
private val dbEventRepository: OfflineEventRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val performanceRestRepository: RestPerformanceRepository,
private val database: AppDatabase
) : EventRepository {
override fun getAllWithPerformance(): Flow<PagingData<EventWithPerformance>> {
val pagingSourceFactory = { dbEventRepository.getAllEventsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppDataContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = EventRemoteMediator(
service,
dbEventRepository,
dbRemoteKeyRepository,
performanceRestRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -1,6 +1,8 @@
package com.example.mobile_labs.api.models
import com.example.mobile_labs.api.MyServerService
import com.example.mobile_labs.database.event.model.Event
import com.example.mobile_labs.database.event.model.EventWithPerformance
import kotlinx.serialization.Serializable
import java.time.LocalDate
@ -15,4 +17,9 @@ fun EventRemote.toEvent(): Event = Event(
id,
LocalDate.parse(date),
performanceId
)
suspend fun EventRemote.toEventWithPerformance(service: MyServerService): EventWithPerformance = EventWithPerformance(
service.getEvent(id).toEvent(),
service.getPerformance(performanceId).toPerformance()
)

View File

@ -1,10 +1,12 @@
package com.example.mobile_labs.api.people
import android.content.Context
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.TheatreApplication
import com.example.mobile_labs.api.MyServerService
import com.example.mobile_labs.api.models.toPerson
import com.example.mobile_labs.database.AppDatabase

View File

@ -60,7 +60,7 @@ class PerformanceRemoteMediator(
val endOfPaginationReached = performances.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON)
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERFORMANCE)
dbPerformanceRepository.clearPerformances()
}
val prevKey = if (page == 1) null else page - 1
@ -68,7 +68,7 @@ class PerformanceRemoteMediator(
val keys = performances.map {
RemoteKeys(
entityId = it.performance_uid !!,
type = RemoteKeyType.PERSON,
type = RemoteKeyType.PERFORMANCE,
prevKey = prevKey,
nextKey = nextKey
)
@ -88,14 +88,14 @@ class PerformanceRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Performance>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { performance ->
dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON)
dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERFORMANCE)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Performance>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { performance ->
dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON)
dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERFORMANCE)
}
}
@ -104,7 +104,7 @@ class PerformanceRemoteMediator(
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.performance_uid?.let { performanceUid ->
dbRemoteKeyRepository.getAllRemoteKeys(performanceUid, RemoteKeyType.PERSON)
dbRemoteKeyRepository.getAllRemoteKeys(performanceUid, RemoteKeyType.PERFORMANCE)
}
}
}

View File

@ -2,24 +2,23 @@ package com.example.mobile_labs.common
import android.content.Context
import com.example.mobile_labs.api.MyServerService
import com.example.mobile_labs.api.events.RestEventRepository
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
import com.example.mobile_labs.database.person.repository.OfflinePersonRepository
import com.example.mobile_labs.database.AppDatabase
import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository
import com.example.mobile_labs.database.remotekeys.repository.RemoteKeyRepository
interface AppContainer {
val personRestRepository: RestPersonRepository
val eventRepository: EventRepository
val eventRestRepository: RestEventRepository
val performanceRestRepository: RestPerformanceRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val eventRepository: EventRepository by lazy {
class AppDataContainer(val context: Context) : AppContainer {
val eventRepository: OfflineEventRepository by lazy {
OfflineEventRepository(AppDatabase.getInstance(context).eventDao())
}
@ -53,6 +52,16 @@ class AppDataContainer(private val context: Context) : AppContainer {
)
}
override val eventRestRepository: RestEventRepository by lazy {
RestEventRepository(
MyServerService.getInstance(),
eventRepository,
remoteKeyRepository,
performanceRestRepository,
AppDatabase.getInstance(context)
)
}
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 3

View File

@ -17,7 +17,7 @@ object AppViewModelProvider {
PeopleListViewModel(theatreApplication().container.personRestRepository)
}
initializer {
EventListViewModel(theatreApplication().container.eventRepository)
EventListViewModel(theatreApplication().container.eventRestRepository)
}
initializer {
PerformanceListViewModel(theatreApplication().container.performanceRestRepository)

View File

@ -1,4 +1,4 @@
package com.example.mobile_labs.database.event.repository
package com.example.mobile_labs.common
import androidx.paging.PagingData
import com.example.mobile_labs.database.event.model.Event
@ -6,10 +6,5 @@ import com.example.mobile_labs.database.event.model.EventWithPerformance
import kotlinx.coroutines.flow.Flow
interface EventRepository {
fun getAll(): Flow<List<Event>>
fun getAllWithPerformance(): Flow<PagingData<EventWithPerformance>>
fun getEvent(uid: Int): Flow<EventWithPerformance?>
suspend fun insertEvent(event: Event)
suspend fun updateEvent(event: Event)
suspend fun deleteEvent(event: Event)
}

View File

@ -26,11 +26,14 @@ interface EventDao {
fun getByUid(uid: Int): Flow<EventWithPerformance>
@Insert
suspend fun insert(event: Event)
suspend fun insert(vararg event: Event)
@Update
suspend fun update(event: Event)
@Delete
suspend fun delete(event: Event)
@Query("DELETE FROM events")
suspend fun deleteAll()
}

View File

@ -18,8 +18,8 @@ import java.time.LocalDate
entity = Performance::class,
parentColumns = ["performance_uid"],
childColumns = ["performance_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT,
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
),
])
data class Event(

View File

@ -3,14 +3,17 @@ package com.example.mobile_labs.database.event.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.mobile_labs.common.AppDataContainer
import com.example.mobile_labs.common.EventRepository
import com.example.mobile_labs.database.event.dao.EventDao
import com.example.mobile_labs.database.event.model.Event
import com.example.mobile_labs.database.event.model.EventWithPerformance
import com.example.mobile_labs.database.performance.model.Performance
import kotlinx.coroutines.flow.Flow
class OfflineEventRepository(private val eventDao: EventDao) : EventRepository {
override fun getAll(): Flow<List<Event>> = eventDao.getAll();
fun getAll(): Flow<List<Event>> = eventDao.getAll();
override fun getAllWithPerformance(): Flow<PagingData<EventWithPerformance>> = Pager(
config = PagingConfig(
pageSize = AppDataContainer.LIMIT,
@ -19,11 +22,18 @@ class OfflineEventRepository(private val eventDao: EventDao) : EventRepository {
pagingSourceFactory = eventDao::getAllWithPerformance
).flow
override fun getEvent(uid: Int): Flow<EventWithPerformance?> = eventDao.getByUid(uid);
fun getEvent(uid: Int): Flow<EventWithPerformance?> = eventDao.getByUid(uid);
override suspend fun insertEvent(event: Event) = eventDao.insert(event);
suspend fun insertEvent(event: Event) = eventDao.insert(event);
override suspend fun updateEvent(event: Event) = eventDao.update(event);
suspend fun updateEvent(event: Event) = eventDao.update(event);
override suspend fun deleteEvent(event: Event) = eventDao.delete(event);
suspend fun deleteEvent(event: Event) = eventDao.delete(event);
suspend fun clearEvents() = eventDao.deleteAll()
suspend fun insertEvents(events: List<Event>) =
eventDao.insert(*events.toTypedArray())
fun getAllEventsPagingSource(): PagingSource<Int, EventWithPerformance> = eventDao.getAllWithPerformance()
}

View File

@ -23,4 +23,7 @@ interface PerformancePersonDao {
@Delete
suspend fun delete(performancePersonCrossRef: PerformancePersonCrossRef)
@Query("DELETE FROM performance_person")
suspend fun deleteAll()
}

View File

@ -12,15 +12,15 @@ import java.io.Serializable
entity = Person::class,
parentColumns = ["uid"],
childColumns = ["director_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT,
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
),
ForeignKey(
entity = Person::class,
parentColumns = ["uid"],
childColumns = ["author_id"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT,
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
),
]
)

View File

@ -2,7 +2,7 @@ package com.example.mobile_labs.ui.event.list
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.mobile_labs.database.event.repository.EventRepository
import com.example.mobile_labs.common.EventRepository
import com.example.mobile_labs.database.event.model.EventWithPerformance
import kotlinx.coroutines.flow.Flow

View File

@ -104,7 +104,7 @@ fun Navhost(
) {
NavHost(
navController,
startDestination = Screen.Schedule.route,
startDestination = Screen.About.route,
modifier.padding(innerPadding)
) {
composable(Screen.Schedule.route) { EventList(navController) }