-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from JayaSuryaT/char-list-paging
Add pagination for character list
- Loading branch information
Showing
14 changed files
with
161 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
...c/main/java/com/jayasuryat/characterlist/data/repositories/CharacterListRemoteMediator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.jayasuryat.characterlist.data.repositories | ||
|
||
import androidx.paging.ExperimentalPagingApi | ||
import androidx.paging.LoadType | ||
import androidx.paging.PagingState | ||
import androidx.paging.RemoteMediator | ||
import com.apollographql.apollo.exception.ApolloNetworkException | ||
import com.bumptech.glide.load.HttpException | ||
import com.jayasuryat.basedata.mappers.Mapper | ||
import com.jayasuryat.basedata.mappers.map | ||
import com.jayasuryat.characterlist.CharacterListQuery | ||
import com.jayasuryat.characterlist.data.sources.local.definitions.CharacterListLocalDataSource | ||
import com.jayasuryat.characterlist.data.sources.local.entities.CharacterEntity | ||
import com.jayasuryat.characterlist.data.sources.remote.definitions.CharacterListNetworkDataSource | ||
import java.io.IOException | ||
|
||
|
||
@OptIn(ExperimentalPagingApi::class) | ||
internal class CharacterListRemoteMediator( | ||
private val networkClient: CharacterListNetworkDataSource, | ||
private val cacheClient: CharacterListLocalDataSource, | ||
private val characterDtoToEntityMapper: Mapper<CharacterListQuery.Result, CharacterEntity>, | ||
) : RemoteMediator<Int, CharacterEntity>() { | ||
|
||
override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH | ||
|
||
override suspend fun load( | ||
loadType: LoadType, | ||
state: PagingState<Int, CharacterEntity>, | ||
): MediatorResult { | ||
|
||
return try { | ||
|
||
val loadKey: Int = when (loadType) { | ||
LoadType.REFRESH -> REMOTE_API_PAGE_START_INDEX | ||
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) | ||
LoadType.APPEND -> (state.getPageNumber() ?: 0) + 1 | ||
} | ||
|
||
if (loadType == LoadType.REFRESH) cacheClient.deleteAllCharacters() | ||
|
||
val characters = networkClient.getCharacters(loadKey).data?.characters()?.results() | ||
characters?.let { | ||
cacheClient.saveCharacters(characterDtoToEntityMapper.map(characters)) | ||
} | ||
|
||
MediatorResult.Success(endOfPaginationReached = characters.isNullOrEmpty()) | ||
|
||
} catch (ex: ApolloNetworkException) { | ||
MediatorResult.Error(ex) | ||
} catch (e: IOException) { | ||
return MediatorResult.Error(e) | ||
} catch (e: HttpException) { | ||
return MediatorResult.Error(e) | ||
} | ||
} | ||
|
||
private fun PagingState<Int, CharacterEntity>.getPageNumber(): Int? { | ||
val last = lastItemOrNull() ?: return null | ||
val position = last.id.toInt() | ||
val size = config.pageSize | ||
val currentPage = (position / size) + (if (position % size == 0) -1 else 0) | ||
return currentPage + REMOTE_API_PAGE_START_INDEX | ||
} | ||
|
||
private companion object { | ||
|
||
const val REMOTE_API_PAGE_START_INDEX: Int = 1 | ||
} | ||
} |
51 changes: 21 additions & 30 deletions
51
...er-list/src/main/java/com/jayasuryat/characterlist/data/repositories/CharacterListRepo.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,41 @@ | ||
package com.jayasuryat.characterlist.data.repositories | ||
|
||
import androidx.paging.* | ||
import com.jayasuryat.basedata.mappers.Mapper | ||
import com.jayasuryat.basedata.mappers.map | ||
import com.jayasuryat.basedata.models.KResult | ||
import com.jayasuryat.basedata.models.wrapAsResult | ||
import com.jayasuryat.basedata.providers.DispatcherProvider | ||
import com.jayasuryat.characterlist.CharacterListQuery | ||
import com.jayasuryat.characterlist.data.sources.local.definitions.CharacterListLocalDataSource | ||
import com.jayasuryat.characterlist.data.sources.local.entities.CharacterEntity | ||
import com.jayasuryat.characterlist.data.sources.remote.definitions.CharacterListNetworkDataSource | ||
import com.jayasuryat.characterlist.domain.models.Character | ||
import com.jayasuryat.characterlist.domain.repos.definitions.CharacterListRepository | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.map | ||
|
||
internal class CharacterListRepo( | ||
private val dispatcher: DispatcherProvider, | ||
private val networkClient: CharacterListNetworkDataSource, | ||
@OptIn(ExperimentalPagingApi::class) | ||
internal class CharacterListRepo constructor( | ||
private val mediator: RemoteMediator<Int, CharacterEntity>, | ||
private val cacheClient: CharacterListLocalDataSource, | ||
private val characterDtoToEntityMapper: Mapper<CharacterListQuery.Result, CharacterEntity>, | ||
private val characterEntityToDomainMapper: Mapper<CharacterEntity, Character>, | ||
) : CharacterListRepository { | ||
|
||
override suspend fun getCharacters( | ||
page: Int, | ||
): KResult<List<Character>> = wrapAsResult(dispatcher.io()) { | ||
override fun getPagedCharacters(): Flow<PagingData<Character>> { | ||
|
||
val networkResponse = networkClient.getCharacters(page).data?.characters() | ||
val networkCharacters = networkResponse?.results() | ||
val pagingConfig = PagingConfig( | ||
pageSize = PAGE_SIZE, | ||
initialLoadSize = PAGE_SIZE, | ||
) | ||
|
||
if (networkCharacters.isNullOrEmpty()) | ||
throw RuntimeException("No characters found") // TODO: 15/10/21 | ||
|
||
val mappedCharacters = characterDtoToEntityMapper.map(networkCharacters) | ||
cacheClient.saveCharacters(mappedCharacters) | ||
|
||
val cachedCharacters = cacheClient.getCharacters(limit = PAGE_SIZE, offset = page - 1) | ||
|
||
characterEntityToDomainMapper.map(cachedCharacters) | ||
} | ||
|
||
override suspend fun getAllCharactersInCache(): KResult<List<Character>> = | ||
wrapAsResult(dispatcher.io()) { | ||
characterEntityToDomainMapper.map(cacheClient.getAllCharacters()) | ||
return Pager( | ||
config = pagingConfig, | ||
remoteMediator = mediator, | ||
) { | ||
cacheClient.getPagedCharacters() | ||
}.flow.map { | ||
it.map(characterEntityToDomainMapper::map) | ||
} | ||
} | ||
|
||
companion object { | ||
private companion object { | ||
|
||
private const val PAGE_SIZE: Int = 20 | ||
const val PAGE_SIZE: Int = 20 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 3 additions & 6 deletions
9
...ain/java/com/jayasuryat/characterlist/domain/repos/definitions/CharacterListRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,10 @@ | ||
package com.jayasuryat.characterlist.domain.repos.definitions | ||
|
||
import com.jayasuryat.basedata.models.KResult | ||
import androidx.paging.PagingData | ||
import com.jayasuryat.characterlist.domain.models.Character | ||
import kotlinx.coroutines.flow.Flow | ||
|
||
interface CharacterListRepository { | ||
|
||
suspend fun getCharacters( | ||
page: Int, | ||
): KResult<List<Character>> | ||
|
||
suspend fun getAllCharactersInCache(): KResult<List<Character>> | ||
fun getPagedCharacters(): Flow<PagingData<Character>> | ||
} |
7 changes: 7 additions & 0 deletions
7
ui-character-list/src/main/java/com/jayasuryat/characterlist/presentation/CharacterDef.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.jayasuryat.characterlist.presentation | ||
|
||
internal data class CharacterDef( | ||
val id: Long, | ||
val name: String, | ||
val imageUrl: String, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 15 additions & 36 deletions
51
...er-list/src/main/java/com/jayasuryat/characterlist/presentation/CharacterListViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,30 @@ | ||
package com.jayasuryat.characterlist.presentation | ||
|
||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.asLiveData | ||
import androidx.paging.cachedIn | ||
import androidx.paging.map | ||
import com.jayasuryat.base.arch.BaseViewModel | ||
import com.jayasuryat.characterlist.domain.models.Character | ||
import com.jayasuryat.characterlist.domain.repos.definitions.CharacterListRepository | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.flow.map | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class CharacterListViewModel @Inject constructor( | ||
private val charactersRepository: CharacterListRepository, | ||
private val charactersPagingRepository: CharacterListRepository, | ||
) : BaseViewModel() { | ||
|
||
private val _obsCharactersList: MutableLiveData<List<CharacterDef>> = MutableLiveData() | ||
internal val obsCharactersList: LiveData<List<CharacterDef>> = _obsCharactersList | ||
|
||
init { | ||
ioScope.launch { doWhileLoading { loadCharacters() } } | ||
internal val obsCharactersList by lazy { | ||
charactersPagingRepository.getPagedCharacters() | ||
.cachedIn(ioScope) | ||
.map { it.map { character -> character.mapToDef() } } | ||
.asLiveData(ioScope.coroutineContext) | ||
} | ||
|
||
private suspend fun loadCharacters() { | ||
|
||
doWhileLoading { | ||
|
||
charactersRepository.getAllCharactersInCache() | ||
.getOrNull() | ||
.mapToDef() | ||
.let(_obsCharactersList::postValue) | ||
|
||
charactersRepository.getCharacters(0) | ||
.logError() | ||
.getOrNull() | ||
.mapToDef() | ||
.let(_obsCharactersList::postValue) | ||
} | ||
} | ||
|
||
private fun List<Character>?.mapToDef(): List<CharacterDef>? { | ||
return if (this.isNullOrEmpty()) null | ||
else this.map { character -> | ||
CharacterDef( | ||
id = character.id, | ||
name = character.name, | ||
imageUrl = character.imageUrl, | ||
) | ||
} | ||
} | ||
private fun Character.mapToDef(): CharacterDef = CharacterDef( | ||
id = id, | ||
name = name, | ||
imageUrl = imageUrl, | ||
) | ||
} |
Oops, something went wrong.