Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
implementation "androidx.paging:paging-runtime:$pagingVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
kapt "androidx.room:room-compiler:$roomVersion"
Expand Down
14 changes: 10 additions & 4 deletions app/src/main/java/com/example/android/codelabs/paging/Injection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import android.content.Context
import androidx.lifecycle.ViewModelProvider
import com.example.android.codelabs.paging.api.GithubService
import com.example.android.codelabs.paging.data.GithubRepository
import com.example.android.codelabs.paging.db.RepoDatabase
import com.example.android.codelabs.paging.ui.ViewModelFactory
import kotlinx.coroutines.Dispatchers

/**
* Class that handles object creation.
Expand All @@ -33,15 +35,19 @@ object Injection {
* Creates an instance of [GithubRepository] based on the [GithubService] and a
* [GithubLocalCache]
*/
private fun provideGithubRepository(): GithubRepository {
return GithubRepository(GithubService.create())
private fun provideGithubRepository(context: Context): GithubRepository {
return GithubRepository(GithubService.create(), provideDatabase(context), Dispatchers.IO)
}

private fun provideDatabase(context: Context): RepoDatabase {
return RepoDatabase.getInstance(context)
}

/**
* Provides the [ViewModelProvider.Factory] that is then used to get a reference to
* [ViewModel] objects.
*/
fun provideViewModelFactory(): ViewModelProvider.Factory {
return ViewModelFactory(provideGithubRepository())
fun provideViewModelFactory(context: Context): ViewModelProvider.Factory {
return ViewModelFactory(provideGithubRepository(context))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.example.android.codelabs.paging.data

import android.util.Log
import androidx.paging.PagingSource
import com.example.android.codelabs.paging.api.GithubService
import com.example.android.codelabs.paging.api.IN_QUALIFIER
Expand All @@ -25,7 +26,7 @@ import retrofit2.HttpException
import java.io.IOException

// GitHub page API is 1 based: https://developer.github.com/v3/#pagination
private const val GITHUB_STARTING_PAGE_INDEX = 1
const val GITHUB_STARTING_PAGE_INDEX = 1

@ExperimentalCoroutinesApi
class GithubPagingSource(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.codelabs.paging.data

import android.util.Log
import androidx.paging.LoadType
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.android.codelabs.paging.data.GithubRepository.Companion.NETWORK_PAGE_SIZE
import com.example.android.codelabs.paging.db.RepoDatabase
import com.example.android.codelabs.paging.model.Repo
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

class GithubRemoteMediator(
private val pagingSource: GithubPagingSource,
private val repoDatabase: RepoDatabase,
private val ioDispatcher: CoroutineContext
) : RemoteMediator<Int, Repo>() {

override suspend fun load(loadType: LoadType, state: PagingState<Int, Repo>?): MediatorResult {
Log.d("GithubRemoteMediator", "load type: $loadType, state: $state")

val key = when (loadType) {
LoadType.REFRESH -> GITHUB_STARTING_PAGE_INDEX
LoadType.START -> state?.pages?.last()?.prevKey ?: GITHUB_STARTING_PAGE_INDEX
LoadType.END -> state?.pages?.last()?.nextKey ?: GITHUB_STARTING_PAGE_INDEX
}

val result = pagingSource.load(PagingSource.LoadParams(
loadType = loadType,
key = key,
loadSize = NETWORK_PAGE_SIZE,
placeholdersEnabled = false,
pageSize = NETWORK_PAGE_SIZE
))

return when (result) {
is PagingSource.LoadResult.Page -> {
withContext(ioDispatcher) {
repoDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
repoDatabase.reposDao().clearRepos()
}
repoDatabase.reposDao().insert(result.data)
}
}
MediatorResult.Success(hasMoreData = result.nextKey != null)
}
is PagingSource.LoadResult.Error -> MediatorResult.Error(result.throwable)

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingDataFlow
import com.example.android.codelabs.paging.api.GithubService
import com.example.android.codelabs.paging.db.RepoDatabase
import com.example.android.codelabs.paging.model.Repo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlin.coroutines.CoroutineContext

/**
* Repository class that works with local and remote data sources.
*/
@ExperimentalCoroutinesApi
class GithubRepository(private val service: GithubService) {
class GithubRepository(
private val service: GithubService,
private val database: RepoDatabase,
private val ioDispatcher: CoroutineContext
) {

/**
* Search repositories whose names match the query, exposed as a stream of data that will emit
Expand All @@ -38,13 +44,19 @@ class GithubRepository(private val service: GithubService) {
fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {
Log.d("GithubRepository", "New query: $query")

val pagingSourceFactory = database.reposDao().reposByName().asPagingSourceFactory()
return PagingDataFlow(
config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),
pagingSourceFactory = { GithubPagingSource(service, query) }
remoteMediator = GithubRemoteMediator(
GithubPagingSource(service, query),
database,
ioDispatcher
),
pagingSourceFactory = pagingSourceFactory
)
}

companion object {
private const val NETWORK_PAGE_SIZE = 50
const val NETWORK_PAGE_SIZE = 50
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.codelabs.paging.db

import androidx.paging.DataSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.android.codelabs.paging.model.Repo

/**
* Room data access object for accessing the [Repo] table.
*/
@Dao
interface RepoDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(posts: List<Repo>)

// Do a similar query as the search API:
// Look for repos that contain the query string in the name or in the description
// and order those results descending, by the number of stars and then by name
@Query("SELECT * FROM repos ORDER BY stars DESC, name ASC")
fun reposByName(): DataSource.Factory<Int, Repo>

@Query("DELETE FROM repos")
suspend fun clearRepos()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.codelabs.paging.db

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
import com.example.android.codelabs.paging.model.Repo

/**
* Database schema that holds the list of repos.
*/
@Database(
entities = [Repo::class],
version = 1,
exportSchema = false
)
abstract class RepoDatabase : RoomDatabase() {

abstract fun reposDao(): RepoDao

companion object {

@Volatile
private var INSTANCE: RepoDatabase? = null

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

private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
RepoDatabase::class.java, "Github.db")
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class SearchRepositoriesActivity : AppCompatActivity() {
setContentView(view)

// get the view model
viewModel = ViewModelProvider(this, Injection.provideViewModelFactory())
viewModel = ViewModelProvider(this, Injection.provideViewModelFactory(applicationContext))
.get(SearchRepositoriesViewModel::class.java)

// add dividers between RecyclerView's row items
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ allprojects {
repositories {
google()
jcenter()
maven { url 'https://androidx-dev-prod.appspot.com/snapshots/builds/6295087/artifacts/repository' }
maven { url 'https://androidx-dev-prod.appspot.com/snapshots/builds/6345264/artifacts/repository' }
}
}

Expand Down