Caching and Pagination with Paging 3 in Android | Frost Tech

virtually Caching and Pagination with Paging 3 in Android will cowl the newest and most present help happening for the world. entry slowly appropriately you comprehend properly and appropriately. will enlargement your data expertly and reliably

On this article, we’ll implement caching and paging with Paging 3. We’ll be utilizing Jetpack Compose, however you may also observe alongside and study from this text, even should you will not be utilizing Jetpack Compose. Aside from the UI layer, most of will probably be related.

Desk of Contents

We’ll be utilizing Room, Retrofit, and Hilt on this article, so that you higher know the way they work.

I am going to additionally assume you realize the fundamentals of how Paging 3 works. When you do not, I like to recommend trying out this text earlier than this one.

software degree construct.gradle proceedings,

//Paging 3
def paging_version = "3.1.1"
implementation "androidx.paging:paging-runtime:$paging_version"
implementation "androidx.paging:paging-compose:1.0.0-alpha17"

//Retrofit
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

//Hilt
def hilt_version = "2.44"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"

//Room
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-paging:$room_version"

//Coil
implementation "io.coil-kt:coil-compose:2.2.2"

Remember so as to add web permission in AndroidManifest.xml,

<uses-permission android:identify="android.permission.INTERNET" />

We’re going to use model 3 of TheMovieDB API. You may register and get your API key from this hyperlink. We are going to use /film/widespread last level

API key

response fashions,

Put them in numerous recordsdata. I’ve put them in a code block to make it simpler to learn.

knowledge class MovieResponse(
val web page: Int,
@SerializedName(worth = "outcomes")
val motion pictures: Checklist<Film>,
@SerializedName("total_pages")
val totalPages: Int,
@SerializedName("total_results")
val totalResults: Int
)

@Entity(tableName = "motion pictures")
knowledge class Film(
@PrimaryKey(autoGenerate = false)
val id: Int,
@ColumnInfo(identify = "original_title")
@SerializedName("original_title")
val ogTitle: String,
@ColumnInfo(identify = "overview")
val overview: String,
@ColumnInfo(identify = "reputation")
val reputation: Double,
@ColumnInfo(identify = "poster_path")
@SerializedName("poster_path")
val posterPath: String?,
@ColumnInfo(identify = "release_date")
@SerializedName("release_date")
val releaseDate: String,
@ColumnInfo(identify = "title")
val title: String,
@ColumnInfo(identify = "web page")
var web page: Int,
)

That is all for this half.

Let’s begin by creating and implementing Retrofit. The API service will likely be quite simple since we’re going to use only one endpoint.

interface MoviesApiService 
@GET("film/widespread?api_key=$MOVIE_API_KEY&language=en-US")
droop enjoyable getPopularMovies(
@Question("web page") web page: Int
): MovieResponse

The API service is prepared, we’ll create an replace occasion on the finish of this half after ending the Room deployment.

That is it for Retrofit, now we will implement Room. Earlier than we start, we’ll have to create a brand new mannequin for caching.

@Entity(tableName = "remote_key")
knowledge class RemoteKeys(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(identify = "movie_id")
val movieID: Int,
val prevKey: Int?,
val currentPage: Int,
val nextKey: Int?,
@ColumnInfo(identify = "created_at")
val createdAt: Lengthy = System.currentTimeMillis()
)

WWhen distant keys should not straight related to record gadgets, it’s higher to retailer them in a separate desk within the native database. Though this may be achieved within the Film desk, creating a brand new desk for the subsequent and former distant keys related to a Film it permits us to have a greater separation of considerations.

This mannequin is required to maintain monitor of pagination. When we now have the final aspect loaded from the PagingState, there isn’t any approach to know the index of the web page it belonged to. To unravel this drawback, we added one other desk that shops the subsequent, present, and former web page keys for every film. The keys are web page numbers. createdAt it’s wanted for the cache timeout. When you needn’t test once we final cached knowledge, you may delete it.

Now we will create Dao for each. Film Y RemoteKeys,

@Dao
interface MoviesDao
@Insert(onConflict = OnConflictStrategy.REPLACE)
droop enjoyable insertAll(motion pictures: Checklist<Film>)

@Question("Choose * From motion pictures Order By web page")
enjoyable getMovies(): PagingSource<Int, Film>

@Question("Delete From motion pictures")
droop enjoyable clearAllMovies()

@Dao
interface RemoteKeysDao
@Insert(onConflict = OnConflictStrategy.REPLACE)
droop enjoyable insertAll(remoteKey: Checklist<RemoteKeys>)

@Question("Choose * From remote_key The place movie_id = :id")
droop enjoyable getRemoteKeyByMovieID(id: Int): RemoteKeys?

@Question("Delete From remote_key")
droop enjoyable clearRemoteKeys()

@Question("Choose created_at From remote_key Order By created_at DESC LIMIT 1")
droop enjoyable getCreationTime(): Lengthy?

Lastly, we have to create the database class.

@Database(
entities = [Movie::class, RemoteKeys::class],
model = 1,
)
summary class MoviesDatabase: RoomDatabase()
summary enjoyable getMoviesDao(): MoviesDao
summary enjoyable getRemoteKeysDao(): RemoteKeysDao

That is it. Now we’re going to create cases of Retrofit & Room.

@Module
@InstallIn(SingletonComponent::class)
class SingletonModule
@Singleton
@Offers
enjoyable provideRetrofitInstance(): MoviesApiService =
Retrofit.Builder()
.baseUrl("https://api.themoviedb.org/3/")
.addConverterFactory(GsonConverterFactory.create())
.construct()
.create(MoviesApiService::class.java)

@Singleton
@Offers
enjoyable provideMovieDatabase(@ApplicationContext context: Context): MoviesDatabase =
Room
.databaseBuilder(context, MoviesDatabase::class.java, "movies_database")
.construct()

@Singleton
@Offers
enjoyable provideMoviesDao(moviesDatabase: MoviesDatabase): MoviesDao = moviesDatabase.getMoviesDao()

@Singleton
@Offers
enjoyable provideRemoteKeysDao(moviesDatabase: MoviesDatabase): RemoteKeysDao = moviesDatabase.getRemoteKeysDao()

Earlier than we begin implementing, let’s attempt to perceive what Distant Mediator is and why we’d like it.

Distant Mediator acts as a sign to the paging library when the appliance has run out of cached knowledge. You need to use this token to load extra knowledge from the community and retailer it within the native database, the place a PagingSource you may load it and supply it to the UI to show.

When extra knowledge is required, the paging library calls the load() technique of the Distant Mediator implementation. This operate sometimes will get the brand new knowledge from a community supply and saves it to native storage.

A Distant Mediator implementation helps load paged knowledge from the community to the database, however doesn’t load knowledge on to the person interface. As a substitute, the appliance makes use of the database as a supply of knowledge. In different phrases, the app solely shows knowledge that has been cached within the database.

Now, we will begin implementing Distant Mediator. Let’s implement half by half. First, we’ll implement load technique.

@OptIn(ExperimentalPagingApi::class)
class MoviesRemoteMediator (
non-public val moviesApiService: MoviesApiService,
non-public val moviesDatabase: MoviesDatabase,
): RemoteMediator<Int, Film>() {

override droop enjoyable load(
loadType: LoadType,
state: PagingState<Int, Film>
): MediatorResult
val web page: Int = when (loadType)
LoadType.REFRESH ->
//...

LoadType.PREPEND ->
//...

LoadType.APPEND ->
//...

strive
val apiResponse = moviesApiService.getPopularMovies(web page = web page)

val motion pictures = apiResponse.motion pictures
val endOfPaginationReached = motion pictures.isEmpty()

moviesDatabase.withTransaction
if (loadType == LoadType.REFRESH)
moviesDatabase.getRemoteKeysDao().clearRemoteKeys()
moviesDatabase.getMoviesDao().clearAllMovies()

val prevKey = if (web page > 1) web page - 1 else null
val nextKey = if (endOfPaginationReached) null else web page + 1
val remoteKeys = motion pictures.map
RemoteKeys(movieID = it.id, prevKey = prevKey, currentPage = web page, nextKey = nextKey)

moviesDatabase.getRemoteKeysDao().insertAll(remoteKeys)
moviesDatabase.getMoviesDao().insertAll(motion pictures.onEachIndexed _, film -> film.web page = web page )

return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
catch (error: IOException)
return MediatorResult.Error(error)
catch (error: HttpException)
return MediatorResult.Error(error)


}

state The parameter provides us details about the pages that had been loaded earlier than, probably the most just lately accessed index within the record, and the PagingConfig we outline when initializing the paging movement.

loadType tells us if we have to load knowledge on the finish (LoadType.APPEND) or at the start of the information (LoadType.PREPEND) that we beforehand loaded,
or if it’s the first time we’re loading knowledge (LoadType.REFRESH).

we’ll implement web page attribute later, so let’s begin with the strive/catch block. First, we make an API request and get motion pictures and set up endOfPaginationReach a motion pictures.isEmpty. If there are not any gadgets left to add, we assume it’s out of inventory.

Then we begin the database transaction. Inside it, we test if loadType is REFRESH and clear caches. After that, we create RemoteKeys by mapping motion pictures and extract film.id. Lastly, we cache the whole lot retrieved. motion pictures Y remoteKeys.

Now, let’s have a look at how we retrieve the web page quantity with RemoteKeys,

@OptIn(ExperimentalPagingApi::class)
class MoviesRemoteMediator (
non-public val moviesApiService: MoviesApiService,
non-public val moviesDatabase: MoviesDatabase,
): RemoteMediator<Int, Film>()
override droop enjoyable load(
loadType: LoadType,
state: PagingState<Int, Film>
): MediatorResult
val web page: Int = when (loadType)
LoadType.REFRESH ->
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1

LoadType.PREPEND ->
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevKey = remoteKeys?.prevKey
prevKey ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)

LoadType.APPEND ->
val remoteKeys = getRemoteKeyForLastItem(state)
val nextKey = remoteKeys?.nextKey
nextKey ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)

strive
//Beforehand applied
//...

non-public droop enjoyable getRemoteKeyClosestToCurrentPosition(state: PagingState<Int, Film>): RemoteKeys?
return state.anchorPosition?.let place ->
state.closestItemToPosition(place)?.id?.let id ->
moviesDatabase.getRemoteKeysDao().getRemoteKeyByMovieID(id)


non-public droop enjoyable getRemoteKeyForFirstItem(state: PagingState<Int, Film>): RemoteKeys?
return state.pages.firstOrNull
it.knowledge.isNotEmpty()
?.knowledge?.firstOrNull()?.let film ->
moviesDatabase.getRemoteKeysDao().getRemoteKeyByMovieID(film.id)

non-public droop enjoyable getRemoteKeyForLastItem(state: PagingState<Int, Film>): RemoteKeys?
return state.pages.lastOrNull
it.knowledge.isNotEmpty()
?.knowledge?.lastOrNull()?.let film ->
moviesDatabase.getRemoteKeysDao().getRemoteKeyByMovieID(film.id)


LoadType.REFRESH, receives a name when it’s the first time we load knowledge, or when refresh() is known as.

LoadType.ANTEPEND, when we have to load knowledge to the start of the at the moment loaded knowledge set, the load parameter is LoadType.PREPEND.

LoadType.APPEND, when we have to load knowledge on the finish of the at the moment loaded knowledge set, the load parameter is LoadType.APPEND.

getRemoteKeyClosestToCurrentPositionbased mostly on anchorPosition of the state, we will get nearer Film merchandise to that place by calling closestItemToPosition and get better RemoteKeys from the database Sure RemoteKeys is null, we return the primary web page quantity which is 1 in our instance.

getRemoteKeyForFirstItemwe get the primary Film merchandise loaded from database.

getRemoteKeyForLastItem, we get the final Film merchandise loaded from database.

Lastly, let’s implement the caching timeout,

@OptIn(ExperimentalPagingApi::class)
class MoviesRemoteMediator (
non-public val moviesApiService: MoviesApiService,
non-public val moviesDatabase: MoviesDatabase,
): RemoteMediator<Int, Film>()

override droop enjoyable initialize(): InitializeAction
val cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)

return if (System.currentTimeMillis() - (moviesDatabase.getRemoteKeysDao().getCreationTime() ?: 0) < cacheTimeout)
InitializeAction.SKIP_INITIAL_REFRESH
else
InitializeAction.LAUNCH_INITIAL_REFRESH

//...

initialize this technique is to test if the cached knowledge is old-fashioned and resolve whether or not to set off a distant replace. This technique is executed earlier than any add is finished, so you may manipulate the database (for instance, to delete previous knowledge) earlier than triggering any native or distant add.

In circumstances the place native knowledge must be totally up to date, initialize I ought to return LAUNCH_INITIAL_REFRESH. This causes the Distant Mediator to carry out a distant replace to completely reload the information.

In circumstances the place there isn’t any have to replace native knowledge, initialize I ought to return SKIP_INITIAL_REFRESH. This causes the Distant Mediator to skip the distant replace and cargo the cached knowledge.

In our instance, we set the timeout to 1 hour and retrieve the cache time from RemoteKeys database.

That is it. you’ll find the RemoteMediator code right here, you may also discover the complete code on the finish of this text.

That is going to be a easy one,

const val PAGE_SIZE = 20

@HiltViewModel
class MoviesViewModel @Inject constructor(
non-public val moviesApiService: MoviesApiService,
non-public val moviesDatabase: MoviesDatabase,
): ViewModel()
@OptIn(ExperimentalPagingApi::class)
enjoyable getPopularMovies(): Stream<PagingData<Film>> =
Pager(
config = PagingConfig(
pageSize = PAGE_SIZE,
prefetchDistance = 10,
initialLoadSize = PAGE_SIZE,
),
pagingSourceFactory =
moviesDatabase.getMoviesDao().getMovies()
,
remoteMediator = MoviesRemoteMediator(
moviesApiService,
moviesDatabase,
)
).movement

That is much like making a Pager from a easy community knowledge supply, however there are two issues it’s good to do in a different way:

As a substitute of spending a PagingSource constructor straight, you have to present the question technique that returns a PagingSource dao object.

You have to present an occasion of your RemoteMediator implementation just like the remoteMediator parameter.

The pagingSourceFactory lambda ought to all the time return a brand new one PagingSource when invoked as PagingSource cases should not reusable.

Lastly, we will begin to implement the UI layer.

record configuration

The implementation of the record will likely be quite simple,

@Composable
enjoyable MainScreen() {
val moviesViewModel = hiltViewModel<MoviesViewModel>()

val motion pictures = moviesViewModel.getPopularMovies().collectAsLazyPagingItems()

LazyColumn {
gadgets(
gadgets = motion pictures
) { film ->
film?.let {
Row(
horizontalArrangement = Association.Middle,
verticalAlignment = Alignment.CenterVertically,
)
if (film.posterPath != null)
var isImageLoading by bear in mind mutableStateOf(false)

val painter = rememberAsyncImagePainter(
mannequin = "https://picture.tmdb.org/t/p/w154" + film.posterPath,
)

isImageLoading = when(painter.state)
is AsyncImagePainter.State.Loading -> true
else -> false

Field (
contentAlignment = Alignment.Middle
)
Picture(
modifier = Modifier
.padding(horizontal = 6.dp, vertical = 3.dp)
.top(115.dp)
.width(77.dp)
.clip(RoundedCornerShape(8.dp)),
painter = painter,
contentDescription = "Poster Picture",
contentScale = ContentScale.FillBounds,
)

if (isImageLoading)
CircularProgressIndicator(
modifier = Modifier
.padding(horizontal = 6.dp, vertical = 3.dp),
colour = MaterialTheme.colours.main,
)



Textual content(
modifier = Modifier
.padding(vertical = 18.dp, horizontal = 8.dp),
textual content = it.title
)

Divider()
}
}
}
}

For an in depth clarification of the record implementation, you may discuss with this hyperlink.

record UI

Loading and error dealing with

@Composable
enjoyable MainScreen() {
val moviesViewModel = hiltViewModel<MoviesViewModel>()

val motion pictures = moviesViewModel.getPopularMovies().collectAsLazyPagingItems()

LazyColumn {
//... Film gadgets

val loadState = motion pictures.loadState.mediator
merchandise
}
}

Since we’re utilizing Distant Mediator, we’ll use loadState.mediator. we’ll simply test refresh Y append,

When refresh is LoadState.Loading we’ll present the loading display screen.

refresh Loading State

When append is LoadState.Loading we’ll present the pagination load.

add Loading

For errors, we test if refresh both append is LoadState.Error. If we now have an error in refresh meaning we acquired an error within the preliminary search and can present an error display screen. If we now have an error in append meaning we acquired an error whereas paginating and we’ll present the error on the finish of the record.

Let’s have a look at the top end result.

That is it! I hope you’ve gotten been useful. 👋👋

full code

MrNtlu/JetpackCompose-PaginationCaching (github.com)

Sources:

I hope the article roughly Caching and Pagination with Paging 3 in Android provides notion to you and is beneficial for complement to your data

Caching and Pagination with Paging 3 in Android

News

Samsung’s SmartThings Station is a Minimal Method to Use Matter | Murderer Tech

roughly Samsung’s SmartThings Station is a Minimal Method to Use Matter will cowl the newest and most present help roughly the world. proper to make use of slowly suitably you comprehend competently and accurately. will layer your information adroitly and reliably The Samsung SmartThings Station is a Matter-compatible hub and smartphone charger in a single! […]

Read More
News

Report: FTC may file antitrust lawsuit in opposition to Amazon | Tech Ready

roughly Report: FTC may file antitrust lawsuit in opposition to Amazon will lid the newest and most present steering one thing just like the world. entry slowly thus you comprehend with out problem and appropriately. will lump your data effectively and reliably The US Federal Commerce Fee might quickly launch an antitrust lawsuit in opposition […]

Read More
News

‘Nothing, Without end,’ an AI ‘Seinfeld’ spoof, is the subsequent ‘Twitch Performs Pokémon’ • TechCrunch | Wire Tech

roughly ‘Nothing, Without end,’ an AI ‘Seinfeld’ spoof, is the subsequent ‘Twitch Performs Pokémon’ • TechCrunch will lid the most recent and most present advice practically the world. gate slowly suitably you perceive competently and appropriately. will addition your data adroitly and reliably “So, I used to be within the retailer the opposite day, and […]

Read More
x