NotesWhat is notes.io?

Notes brand slogan

Notes - notes.io

// Paging Library 3

// Data

// Mediator

@ExperimentalPagingApi
class DogMediator(val DogApiService: DogApiService, val appDatabase: AppDatabase) :
RemoteMediator<Int, DogModel>() {

override suspend fun load(
loadType: LoadType, state: PagingState<Int, DogModel>
): MediatorResult {

val pageKeyData = getKeyPageData(loadType, state)
val page = when (pageKeyData) {
is MediatorResult.Success -> {
return pageKeyData
}
else -> {
pageKeyData as Int
}
}

try {
val response = DogApiService.getDogImages(page, state.config.pageSize)
val isEndOfList = response.isEmpty()
appDatabase.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH) {
appDatabase.getRepoDao().clearRemoteKeys()
appDatabase.getDogModelDao().clearAllDogs()
}
val prevKey = if (page == DEFAULT_PAGE_INDEX) null else page - 1
val nextKey = if (isEndOfList) null else page + 1
val keys = response.map {
RemoteKeys(repoId = it.id.toString(), prevKey = prevKey, nextKey = nextKey)
}
appDatabase.getRepoDao().insertAll(keys)
appDatabase.getDogModelDao().insertAll(response)
}
return MediatorResult.Success(endOfPaginationReached = isEndOfList)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}

/**
* this returns the page key or the final end of list success result
*/
suspend fun getKeyPageData(loadType: LoadType, state: PagingState<Int, DogModel>): Any? {
return when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getClosestRemoteKey(state)
remoteKeys?.nextKey?.minus(1) ?: DEFAULT_PAGE_INDEX
}
LoadType.APPEND -> {
val remoteKeys = getLastRemoteKey(state)
?: throw InvalidObjectException("Remote key should not be null for $loadType")
remoteKeys.nextKey
}
LoadType.PREPEND -> {
// val remoteKeys = getFirstRemoteKey(state)
// ?: throw InvalidObjectException("Invalid state, key should not be null")
val remoteKeys = getFirstRemoteKey(state)
?: null
//end of list condition reached
remoteKeys?.prevKey ?: return MediatorResult.Success(endOfPaginationReached = true)
remoteKeys?.prevKey
}
}
}

/**
* get the last remote key inserted which had the data
*/
private suspend fun getLastRemoteKey(state: PagingState<Int, DogModel>): RemoteKeys? {
return state.pages
.lastOrNull { it.data.isNotEmpty() }
?.data?.lastOrNull()
?.let { Dog -> appDatabase.getRepoDao().remoteKeysDogId(Dog.id.toString()) }
}

/**
* get the first remote key inserted which had the data
*/
private suspend fun getFirstRemoteKey(state: PagingState<Int, DogModel>): RemoteKeys? {
return state.pages
.firstOrNull() { it.data.isNotEmpty() }
?.data?.firstOrNull()
?.let { Dog -> appDatabase.getRepoDao().remoteKeysDogId(Dog.id.toString()) }
}

/**
* get the closest remote key inserted which had the data
*/
private suspend fun getClosestRemoteKey(state: PagingState<Int, DogModel>): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { repoId ->
appDatabase.getRepoDao().remoteKeysDogId(repoId.toString())
}
}
}
}

// Paging Source
@ExperimentalPagingApi
class DogPagingSource(val doggoApiService: DogApiService) :
PagingSource<Int, DogModel>() {

/**
* calls api if there is any error getting results then return the [LoadResult.Error]
* for successful response return the results using [LoadResult.Page] for some reason if the results
* are empty from service like in case of no more data from api then we can pass [null] to
* send signal that source has reached the end of list
*/
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DogModel> {
//for first case it will be null, then we can pass some default value, in our case it's 1
val page = params.key ?: DEFAULT_PAGE_INDEX
return try {
val response = doggoApiService.getDogImages(page, params.loadSize)
LoadResult.Page(
response, prevKey = if (page == DEFAULT_PAGE_INDEX) null else page - 1,
nextKey = if (response.isEmpty()) null else page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}

}

// Repository
@ExperimentalPagingApi
class DogRepository(
val DogApiService: DogApiService = RemoteInjector.injectDogApiService(),
val appDatabase: AppDatabase? = LocalInjector.injectDb()
) {

companion object {
const val DEFAULT_PAGE_INDEX = 1
const val DEFAULT_PAGE_SIZE = 20

//get Dog repository instance
fun getInstance() = DogRepository()
}

//for live data users
fun letDogsLiveData(pagingConfig: PagingConfig = getDefaultPageConfig()): LiveData<PagingData<DogModel>> {
return Pager(
config = pagingConfig,
pagingSourceFactory = { DogPagingSource(DogApiService) }
).liveData
}

/**
* let's define page size, page size is the only required param, rest is optional
*/
fun getDefaultPageConfig(): PagingConfig {
return PagingConfig(pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = true)
}

fun letDogsLiveDataDB(pagingConfig: PagingConfig = getDefaultPageConfig()): LiveData<PagingData<DogModel>> {
if (appDatabase == null) throw IllegalStateException("Database is not initialized")
val pagingSourceFactory = { appDatabase.getDogModelDao().getAllDogModel() }
return Pager(
config = pagingConfig,
pagingSourceFactory = pagingSourceFactory,
remoteMediator = DogMediator(DogApiService, appDatabase)
).liveData
}

}
// Model
@Entity
data class DogModel(@PrimaryKey val id: String, val url: String)

// Repository
// Repository - Remote

interface DogApiService {
@GET("v1/images/search")
suspend fun getDogImages(@Query("page") page: Int, @Query("limit") size: Int): List<DogModel>
}

object RemoteInjector {

const val API_KEY = "d6fd31ff-2b46-4600-b25d-cbcd09f0ac14"
const val API_ENDPOINT = "https://api.thedogapi.com"
const val HEADER_API_KEY = "x-api-key"

fun injectDogApiService(retrofit: Retrofit = getRetrofit()): DogApiService {
return retrofit.create(DogApiService::class.java)
}

private fun getRetrofit(okHttpClient: OkHttpClient = getOkHttpClient()): Retrofit {
return Retrofit.Builder()
.baseUrl(API_ENDPOINT)
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
}

private fun getOkHttpNetworkInterceptor(): Interceptor {
return object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val newRequest =
chain.request().newBuilder().addHeader(HEADER_API_KEY, API_KEY).build()
return chain.proceed(newRequest)
}
}
}

private fun getHttpLogger(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}

private fun getOkHttpClient(
okHttpLogger: HttpLoggingInterceptor = getHttpLogger(),
okHttpNetworkInterceptor: Interceptor = getOkHttpNetworkInterceptor()
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(okHttpLogger)
.addInterceptor(okHttpNetworkInterceptor)
.build()
}

}

// Repository - Local

// DataBase
@Database(version = 1, entities = [DogModel::class, RemoteKeys::class], exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

abstract fun getRepoDao(): RemoteKeysDao
abstract fun getDogModelDao(): DogModelDao

companion object {

val DOG_DB = "dog.db"

@Volatile
private var INSTANCE: AppDatabase? = null

fun getInstance(context: Context): AppDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE
?: buildDatabase(context).also { INSTANCE = it }
}

private fun buildDatabase(context: Context) =
Room
.databaseBuilder(context.applicationContext, AppDatabase::class.java, DOG_DB)
.build()
}

}

// Data Dao
@Dao
interface DogModelDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(dogModel: List<DogModel>)

@Query("SELECT * FROM dogmodel")
fun getAllDogModel(): PagingSource<Int, DogModel>

@Query("DELETE FROM dogmodel")
suspend fun clearAllDogs()
}

// Local Injector
object LocalInjector {

var appDatabase: AppDatabase? = null

fun injectDb(): AppDatabase? {
return appDatabase
}
}

// Remotekey Data Entity Class
@Entity
data class RemoteKeys(@PrimaryKey val repoId: String, val prevKey: Int?, val nextKey: Int?)

// Dao RemoteKeys
@Dao
interface RemoteKeysDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)

@Query("SELECT * FROM remotekeys WHERE repoId = :id")
suspend fun remoteKeysDogId(id: String): RemoteKeys?

@Query("DELETE FROM remotekeys")
suspend fun clearRemoteKeys()
}

// View - Remote

// View
@ExperimentalPagingApi
class RemoteFragment : Fragment() {
lateinit var recyclerView: RecyclerView
lateinit var viewModel: RemoteViewModel
lateinit var adapter: RemoteDogAdapter

companion object {
fun newInstance() = RemoteFragment()
}


override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.remote_fragment, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initMembers()
setUpView(view)
fetchDoggoImagesLiveData()
}

//call this for live data based paging
private fun fetchDoggoImagesLiveData() {
viewModel.fetchDogsLiveData().observe(viewLifecycleOwner, Observer {
lifecycleScope.launch {
adapter.submitData(it)
}
})
}

private fun initMembers(){
viewModel = defaultViewModelProviderFactory.create(RemoteViewModel::class.java)
adapter = RemoteDogAdapter()
}

private fun setUpView(view: View){
recyclerView = view.findViewById(R.id.recyclerViewRoom)
recyclerView.layoutManager = GridLayoutManager(context, 2)
recyclerView.adapter = adapter
}

}

//View Model
@ExperimentalPagingApi
class RemoteViewModel(val repository: DogRepository = DogRepository.getInstance()) : ViewModel() {

//live data use case
fun fetchDogsLiveData(): LiveData<PagingData<String>> {
return repository.letDogsLiveData()
.map { it.map { it.url } }
.cachedIn(viewModelScope)
}

}

// Adapter
class RemoteDogAdapter: PagingDataAdapter<String, RecyclerView.ViewHolder>(REPO_COMPARATOR) {

companion object{
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<String>(){
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}

override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
}
}


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as? DoggoImageViewHolder)?.bind(item = getItem(position))
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return DoggoImageViewHolder.getInstance(parent)
}

class DoggoImageViewHolder(view: View): RecyclerView.ViewHolder(view){
companion object{
fun getInstance(parent: ViewGroup): DoggoImageViewHolder{
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.data_item, parent, false)
return DoggoImageViewHolder(view)
}
}
var ivDoggoMain: ImageView = view.findViewById(R.id.imageView)

fun bind(item: String?){
//loads image from network using coil extension function
ivDoggoMain.load(item) {
placeholder(R.drawable.dog_placeholder)
}
}
}


}

// View - Room

// View
@ExperimentalPagingApi
class RoomFragment : Fragment() {

lateinit var recyclerView: RecyclerView
lateinit var adapter: RoomDogAdapter
private lateinit var viewModel: RoomViewModel
companion object {
fun newInstance() = RoomFragment()
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.room_fragment, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initMembers()
setUpView(view)
fetchImages()
}

private fun fetchImages(){
viewModel.getImages().observe(viewLifecycleOwner, Observer {
lifecycleScope.launch {
adapter.submitData(it)
}
})
}


private fun initMembers(){
viewModel = defaultViewModelProviderFactory.create(RoomViewModel::class.java)
adapter = RoomDogAdapter()
}

private fun setUpView(view: View){
recyclerView = view.findViewById(R.id.recyclerViewRoom)
recyclerView.layoutManager = GridLayoutManager(context, 2)
recyclerView.adapter = adapter
}

}

//View Model

@ExperimentalPagingApi
class RoomViewModel(val repository: DogRepository = DogRepository.getInstance()) : ViewModel() {
fun getImages(): LiveData<PagingData<DogModel>> {
return repository.letDogsLiveDataDB().cachedIn(viewModelScope)
}
}

// Adapter
class RoomDogAdapter:
PagingDataAdapter<DogModel, RoomDogAdapter.DoggoImageViewHolder>(
REPO_COMPARATOR
) {
companion object{
private val REPO_COMPARATOR = object: DiffUtil.ItemCallback<DogModel>(){
override fun areItemsTheSame(
oldItem: DogModel,
newItem: DogModel
): Boolean = oldItem == newItem

override fun areContentsTheSame(
oldItem: DogModel,
newItem: DogModel
): Boolean = oldItem == newItem
}
}

override fun onBindViewHolder(holder: RoomDogAdapter.DoggoImageViewHolder, position: Int) {
holder.bind(item = getItem(position))
}

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RoomDogAdapter.DoggoImageViewHolder {
return DoggoImageViewHolder.getInstance(parent)
}

class DoggoImageViewHolder(view: View): RecyclerView.ViewHolder(view){

companion object{
fun getInstance(parent: ViewGroup): DoggoImageViewHolder{
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.data_item, parent, false)
return DoggoImageViewHolder(view)
}
}

var ivDoggoMain: ImageView = view.findViewById(R.id.imageView)

fun bind(item: DogModel?){
ivDoggoMain.load(item?.url){placeholder(R.drawable.dog_placeholder)}
}

}
}

     
 
what is notes.io
 

Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...

With notes.io;

  • * You can take a note from anywhere and any device with internet connection.
  • * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
  • * You can quickly share your contents without website, blog and e-mail.
  • * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
  • * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.

Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.

Easy: Notes.io doesn’t require installation. Just write and share note!

Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )

Free: Notes.io works for 14 years and has been free since the day it was started.


You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;


Email: [email protected]

Twitter: http://twitter.com/notesio

Instagram: http://instagram.com/notes.io

Facebook: http://facebook.com/notesio



Regards;
Notes.io Team

     
 
Shortened Note Link
 
 
Looding Image
 
     
 
Long File
 
 

For written notes was greater than 18KB Unable to shorten.

To be smaller than 18KB, please organize your notes, or sign in.