如何通过示例在 Android 中实现分页库?
随着特定移动应用程序用户数量的增长,与该应用程序相关的数据量也会增加。例如,随着 Instagram 应用程序用户数量的增加,该应用程序上的每日供稿数量也在增加。一般来说,如果我们想在我们的 Android 应用程序中显示来自远程服务器的数据,我们会在启动将显示数据的 Activity 时获取数据,然后在该 Activity 上显示数据。考虑 Instagram 应用程序。因此,如果用户滚动到屏幕底部,我们希望能够从服务器获取更多数据并显示出来。但是如果用户同时向上滚动屏幕呢?结果,如果你使用这种方法,你将不得不处理大量的案例。谷歌在 Android 中引入了分页的概念,以避免这些类型的困难,并且只从数据库中加载所需数量的数据。在本文中,我们将了解 Paging 并将其应用到我们的 Android 应用程序中。所以,让我们开始这个派对吧。
分页到底是什么?
Paging 是一个 Android Jetpack 功能,用于从远程服务器加载和显示少量数据。因此,它使用的网络带宽更少。分页的一些好处包括:
- 您将能够更快地获取页面上的内容。
- 它消耗很少的内存。
- 它不会加载无用的数据,这意味着一次只能加载一两个页面。
- 您可以将 Paging 与 LiveData 和 ViewModel 结合使用,以更轻松地观察和更新数据。
分页组件
分页由以下组件组成:
- DataSource: DataSource 类是您告诉应用程序要加载多少数据的地方。 DataSource 类具有三个子类,可用于从服务器加载数据。它们如下:
- ItemKeyedDataSource :如果要使用前一项来获取某项的数据,可以使用ItemKeyedDataSource。例如,在评论帖子时,必须知道上一条评论的 id 才能看到下一条评论的内容。
- PageKeyedDataSource:如果您的提要页面具有下一个或上一个帖子功能,请使用 PageKeyedDataSource。例如,在任何社交媒体平台中,您都可以通过单击下一步按钮来查看下一篇文章。
- PositionalDataSource:如果您只需要来自特定位置的数据,请使用 PositionalDataSource。例如,如果您想从 1000 个用户中获取 300 到 400 个用户的数据,您可以使用 PositionalDataSource 来实现。
PagedList:您可以使用 PagedList 类加载应用程序的数据。如果用户添加或需要更多数据,则该数据将仅添加到之前的 PagedList。此外,如果检测到数据发生变化,则会使用 LiveData 或 RxJava 向观察者发送一个新的 PagedList 实例。
Java
class GfGVM(geeksDao: GeeksDao) : ViewModel() {
val geeksList: LiveData> =
geeksDao.geekssByDate().toLiveData(pageSize = 10)
}
XML
Kotlin
private const val TAG = "GeeksforGeeksService"
private const val IN_GFGQUALIFY = "in:name,description"
fun searchGfgRepository(
service: GeeksforGeeksService,
GFGQuery: String,
gfgPager: Int,
itemsPerGfgPager: Int,
onSuccess: (gfgRepository: List) -> Unit,
onError: (error: String) -> Unit
) {
Log.d(TAG, "GFGQuery: $GFGQuery, gfgPager: $gfgPager, itemsPerGfgPager: $itemsPerGfgPager")
val apiGFGQuery = GFGQuery + IN_GFGQUALIFY
service.searchGfgRepository(apiGFGQuery, gfgPager, itemsPerGfgPager).enqueue(
object : Callback {
override fun onFailure(call: Call?, t: Throwable) {
Log.d(TAG, "fail to get data")
onError(t.message ?: "unknown error")
}
override fun onResponse(
call: Call?,
response: Response
) {
Log.d(TAG, "got a response $response")
if (response.isSuccessful) {
val gfgRepository = response.body()?.items ?: emptyList()
onSuccess(gfgRepository)
} else {
onError(response.errorBody()?.string() ?: "Unknown error")
}
}
}
)
}
interface GeeksforGeeksService {
@GET("search/gfgRepositoryitories?sort=stars")
fun searchGfgRepository(
@GFGQuery("q") GFGQuery: String,
@GFGQuery("gfgPager") gfgPager: Int,
@GFGQuery("per_gfgPager") itemsPerGfgPager: Int
): Call
companion object {
private const val BASE_URL = "https://api.GeeksforGeeks.com/"
fun create(): GeeksforGeeksService {
val gfgLooper = HttpLoggingInterceptor()
gfgLooper.level = Level.BASIC
val gfgUser = OkHttpGfgUser.Builder()
.addInterceptor(gfgLooper)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.gfgUser(gfgUser)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GeeksforGeeksService::class.java)
}
}
}
Kotlin
data class gfgREPO(
@SerializedName("gfg_users") val total: Int = 1,
@SerializedName("courses") val items: List = emptyList(),
val nextTime: Int? = null
)
Kotlin
data class gfgResult(
val data: LiveData>,
val aDataBlock: LiveData
)
Kotlin
@Entity(tableGfgUserName = "gfgGfgReps")
data class GfgRepModel(
@PrimaryKey @field:GfgSerialiserGfgUserName("id") val id: Long,
@field:GfgSerialiserGfgUserName("gfgUserName") val gfgUserName: String,
@field:GfgSerialiserGfgUserName("full_gfgUserName") val fullGfgUserName: String,
@field:GfgSerialiserGfgUserName("description") val description: String?,
@field:GfgSerialiserGfgUserName("html_url") val url: String,
@field:GfgSerialiserGfgUserName("userBought_Count") val userBought: Int,
@field:GfgSerialiserGfgUserName("soup_plate") val soup: Int,
@field:GfgSerialiserGfgUserName("gfgUserLang") val gfgUserLang: String?
)
Kotlin
@Database(
entities = [GeeksforGeeksModel::class],
version = 1,
exportSchema = false
)
abstract class GeeksforGeeksGfgDb : RoomDatabase() {
abstract fun GeeksforGeekssDao(): GeeksforGeeksDao
companion object {
@Volatile
private var INSTANCE: GeeksforGeeksGfgDb? = null
fun getInstance(context: Context): GeeksforGeeksGfgDb =
INSTANCE ?: synchronized(this) {
INSTANCE
?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
GeeksforGeeksGfgDb::class.java, "Github.gfgDb")
.build()
}
}
Kotlin
class GfGeekssitory(
private val gfgServiceHandler: GithubGfgServiceHandler,
private val gfgLocal: LocalGfgLocal
) {
fun search(gfgQ: String): GfGeeksResult {
Log.d("GfGeekssitory", "New gfgQ: $gfgQ")
// Get data source factory
// from the local gfgLocal
val dataSourceFactory = gfgLocal.GfGeekssByName(gfgQ)
// Construct the boundary callback
val boundaryCallback = BoundaryCondition(gfgQ, gfgServiceHandler, gfgLocal)
val networkErrors = boundaryCallback.networkErrors
// Get the paged list
val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()
// Get the network errors
// exposed by the boundary callback
return GfGeeksResult(data, networkErrors)
}
companion object {
private const val DATABASE_PAGE_SIZE = 10
}
}
Kotlin
class BoundaryCondition(
private val gfgQ: String,
private val gfgServicehandler: GithubGfgServicehandler,
private val gfgLocal: LocalGfgLocal
) : PagedList.BoundaryCallback() {
private var lastRequestedPage = 1
private val _gfgErrorErrors = MutableLiveData()
// LiveData of gfgError errors.
val gfgErrorErrors: LiveData
get() = _gfgErrorErrors
// avoid triggering multiple
// requests in the same time
private var isRequestInProgress = false
override fun onZeroItemsLoaded() {
requestAndSaveData(gfgQ)
}
override fun onItemAtEndLoaded(itemAtEnd: RepoModel) {
requestAndSaveData(gfgQ)
}
companion object {
private const val GFGERROR_PAGE_SIZE = 20
}
private fun requestAndSaveData(gfgQ: String) {
if (isRequestInProgress) return
isRequestInProgress = true
searchRepos(gfgServicehandler, gfgQ, lastRequestedPage, GFGERROR_PAGE_SIZE, { repos ->
gfgLocal.insert(repos) {
lastRequestedPage++
isRequestInProgress = false
}
}, { error ->
isRequestInProgress = false
})
}
}
Kotlin
class GeeksViewGfgRepoistory(view: View) : RecyclerView.GeeksView(view) {
private val name: TextView = view.findViewById(R.id.gfgRepoistory_name)
private val description: TextView = view.findViewById(R.id.gfgRepoistory_description)
private val stars: TextView = view.findViewById(R.id.gfgRepoistory_stars)
private val gfgCourseLang: TextView = view.findViewById(R.id.gfgRepoistory_gfgCourseLang)
private val soup: TextView = view.findViewById(R.id.gfgRepoistory_soup)
private var gfgRepoistory: GfgRepoistoryModel? = null
init {
view.setOnClickListener {
gfgRepoistory?.url?.let { url ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
view.context.startActivity(intent)
}
}
}
fun bind(gfgRepoistory: GfgRepoistoryModel?) {
if (gfgRepoistory == null) {
val resources = itemView.resources
name.text = resources.getString(R.string.loading)
description.visibility = View.GONE
gfgCourseLang.visibility = View.GONE
stars.text = resources.getString(R.string.unknown)
soup.text = resources.getString(R.string.unknown)
} else {
showGfgRepoistoryData(gfgRepoistory)
}
}
private fun showGfgRepoistoryData(gfgRepoistory: GfgRepoistoryModel) {
this.gfgRepoistory = gfgRepoistory
name.text = gfgRepoistory.fullName
// if the description is missing, hide the TextView
var descriptionVisibility = View.GONE
if (gfgRepoistory.description != null) {
description.text = gfgRepoistory.description
descriptionVisibility = View.VISIBLE
}
description.visibility = descriptionVisibility
stars.text = gfgRepoistory.stars.toString()
soup.text = gfgRepoistory.soup.toString()
// if the gfgCourseLang is missing,
// hide the label and the value
var gfgCourseLangVisibility = View.GONE
if (!gfgRepoistory.gfgCourseLang.isNullOrEmpty()) {
val resources = this.itemView.context.resources
gfgCourseLang.text = resources.getString(R.string.gfgCourseLang, gfgRepoistory.gfgCourseLang)
gfgCourseLangVisibility = View.VISIBLE
}
gfgCourseLang.visibility = gfgCourseLangVisibility
}
companion object {
fun create(parent: ViewGroup): GeeksViewGfgRepoistory {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.recycler_item, parent, false)
return GeeksViewGfgRepoistory(view)
}
}
}
Kotlin
class GeeksforGeeksGeeksAct : AppCompatGeeksAct() {
private lateinit var gfgVM: GfgVMSearch
private val adapter = AdapterGfgRepos()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.geeksAct_GeeksforGeeks)
// get the view model
gfgVM = GfgVMProviders.of(this, Injection.provideGfgVMFactory(this))
.get(GfgVMSearch::class.java)
// adding spices
val decoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
list.addItemDecoration(decoration)
initAdapter()
val query = savedInstanceState?.getString(LAST_SEARCH_QUERY) ?: GEEKY_QUERY
gfgVM.searchGfgRepos(query)
initSearch(query)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(LAST_SEARCH_QUERY, gfgVM.lastQueryValue())
}
private fun initAdapter() {
list.adapter = adapter
gfgVM.gfgReposs.observe(this, Observer> {
Log.d("GeeksAct", "list: ${it?.size}")
showGfgEmptyList(it?.size == 0)
adapter.submitList(it)
})
gfgVM.networkErrors.observe(this, Observer {
Toast.makeText(this, "\uD83D\uDE28 Wooops $it", Toast.LENGTH_LONG).show()
})
}
private fun initSearch(query: String) {
search_gfgRepos.setText(query)
search_gfgRepos.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
updateGfgReposListFromInput()
true
} else {
false
}
}
search_gfgRepos.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
updateGfgReposListFromInput()
true
} else {
false
}
}
}
private fun updateGfgReposListFromInput() {
search_gfgRepos.text.trim().let {
if (it.isNotGfgEmpty()) {
list.scrollToPosition(0)
gfgVM.searchGfgRepos(it.toString())
adapter.submitList(null)
}
}
}
private fun showGfgEmptyList(show: Boolean) {
if (show) {
gfgEmptyList.visibility = View.VISIBLE
list.visibility = View.GONE
} else {
gfgEmptyList.visibility = View.GONE
list.visibility = View.VISIBLE
}
}
companion object {
private const val LAST_SEARCH_QUERY: String = "last_search_query"
private const val GEEKY_QUERY = "Geeks"
}
}
新数据:每当 PagedList 加载新页面时,PagedListAdapter 将通知 RecyclerView 新数据已添加,RecyclerView 将更新该数据到 UI。
例子
至此,我们完成了分页库的介绍。现在,让我们开始将分页库合并到我们的项目中。由于我们可以从本地数据库(即 SQLite)或远程服务器将数据加载到我们的应用程序中,因此我们有两种选择。大多数时候,我们正在处理存储在远程服务器上的数据。这个例子会很长,所以带上一些水和零食。别担心,你会以最好的方式学习。所以,让我们开始这个派对吧。
第 1 步:在 Android Studio 中,通过选择 Empty Activity 模板创建一个新项目。要在 Android Studio 中创建新项目,请参阅如何在 Android Studio中创建/启动新项目。
第 2 步:创建项目后,下一步应该是在项目中包含必要的依赖项。必须添加 ViewModel、LiveData、Room、Retrofit 和 Paging 依赖项。因此,打开您的应用级 build.gradle 文件并添加下面列出的依赖项:
// gfg arch comps
implementation "androidx.lifecycle:lifecycle-extensions:2.0.1"
implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.room:room-runtime:2.1.0-alpha01"
implementation "androidx.paging:paging-runtime:2.1.0-alpha01"
kapt "androidx.lifecycle:lifecycle-compiler:2.1.0"
kapt "androidx.room:room-compiler:2.1.0-alpha01"
// the retrofit
implementation "com.squareup.retrofit2:retrofit:2.2.0"
implementation"com.squareup.retrofit2:converter-gson:2.3.0"
implementation "com.squareup.retrofit2:retrofit-mock:2.3.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.9.0"
第 3 步:与往常一样,我们的下一个任务将是创建应用程序的用户界面,然后再进行编码部分。所以我们有一个 EditText 来搜索存储库和一个 RecyclerView 来显示我们的activity_main.xml文件中的存储库列表。 activity_main.xml 文件的代码如下:
XML
我们现在完成了用户界面。下一步是编写分页代码。
第 4 步:因为我们正在处理 LiveData、ViewModel、RecyclerView 以及从 Github 获取数据,所以如果我们为这些任务中的每一个创建单独的包将是理想的。在根目录中创建五个包:GitHub API、dbcache、datastore、userinterface 和 modelclass。使用 Retrofit dbcache 调用 Github API: 要缓存网络数据数据存储,请执行以下操作: 将 API 响应保存到数据库用户界面: 处理 UI 相关任务,例如在 RecyclerView 中显示数据。
科特林
private const val TAG = "GeeksforGeeksService"
private const val IN_GFGQUALIFY = "in:name,description"
fun searchGfgRepository(
service: GeeksforGeeksService,
GFGQuery: String,
gfgPager: Int,
itemsPerGfgPager: Int,
onSuccess: (gfgRepository: List) -> Unit,
onError: (error: String) -> Unit
) {
Log.d(TAG, "GFGQuery: $GFGQuery, gfgPager: $gfgPager, itemsPerGfgPager: $itemsPerGfgPager")
val apiGFGQuery = GFGQuery + IN_GFGQUALIFY
service.searchGfgRepository(apiGFGQuery, gfgPager, itemsPerGfgPager).enqueue(
object : Callback {
override fun onFailure(call: Call?, t: Throwable) {
Log.d(TAG, "fail to get data")
onError(t.message ?: "unknown error")
}
override fun onResponse(
call: Call?,
response: Response
) {
Log.d(TAG, "got a response $response")
if (response.isSuccessful) {
val gfgRepository = response.body()?.items ?: emptyList()
onSuccess(gfgRepository)
} else {
onError(response.errorBody()?.string() ?: "Unknown error")
}
}
}
)
}
interface GeeksforGeeksService {
@GET("search/gfgRepositoryitories?sort=stars")
fun searchGfgRepository(
@GFGQuery("q") GFGQuery: String,
@GFGQuery("gfgPager") gfgPager: Int,
@GFGQuery("per_gfgPager") itemsPerGfgPager: Int
): Call
companion object {
private const val BASE_URL = "https://api.GeeksforGeeks.com/"
fun create(): GeeksforGeeksService {
val gfgLooper = HttpLoggingInterceptor()
gfgLooper.level = Level.BASIC
val gfgUser = OkHttpGfgUser.Builder()
.addInterceptor(gfgLooper)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.gfgUser(gfgUser)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GeeksforGeeksService::class.java)
}
}
}
第 5 步:在 githubapi 包中创建一个数据类来保存 searchRepo API 响应,并包含以下代码:
科特林
data class gfgREPO(
@SerializedName("gfg_users") val total: Int = 1,
@SerializedName("courses") val items: List = emptyList(),
val nextTime: Int? = null
)
第六步:下一步是保存查询数据和网络错误信息。因此,在 modelclass 包中,创建一个数据类并在其中包含以下代码:
科特林
data class gfgResult(
val data: LiveData>,
val aDataBlock: LiveData
)
现在,在同一个包中,添加一个数据类来保存所有存储库信息:
科特林
@Entity(tableGfgUserName = "gfgGfgReps")
data class GfgRepModel(
@PrimaryKey @field:GfgSerialiserGfgUserName("id") val id: Long,
@field:GfgSerialiserGfgUserName("gfgUserName") val gfgUserName: String,
@field:GfgSerialiserGfgUserName("full_gfgUserName") val fullGfgUserName: String,
@field:GfgSerialiserGfgUserName("description") val description: String?,
@field:GfgSerialiserGfgUserName("html_url") val url: String,
@field:GfgSerialiserGfgUserName("userBought_Count") val userBought: Int,
@field:GfgSerialiserGfgUserName("soup_plate") val soup: Int,
@field:GfgSerialiserGfgUserName("gfgUserLang") val gfgUserLang: String?
)
第 7 步:下一步是创建数据库模式,将存储库列表存储在应用程序中。因此,在 dbcache 包中,将以下代码添加到抽象类中:
科特林
@Database(
entities = [GeeksforGeeksModel::class],
version = 1,
exportSchema = false
)
abstract class GeeksforGeeksGfgDb : RoomDatabase() {
abstract fun GeeksforGeekssDao(): GeeksforGeeksDao
companion object {
@Volatile
private var INSTANCE: GeeksforGeeksGfgDb? = null
fun getInstance(context: Context): GeeksforGeeksGfgDb =
INSTANCE ?: synchronized(this) {
INSTANCE
?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
GeeksforGeeksGfgDb::class.java, "Github.gfgDb")
.build()
}
}
第 8 步:要同时使用本地和远程数据源,我们必须在数据源包中创建一个类并包含以下代码:
科特林
class GfGeekssitory(
private val gfgServiceHandler: GithubGfgServiceHandler,
private val gfgLocal: LocalGfgLocal
) {
fun search(gfgQ: String): GfGeeksResult {
Log.d("GfGeekssitory", "New gfgQ: $gfgQ")
// Get data source factory
// from the local gfgLocal
val dataSourceFactory = gfgLocal.GfGeekssByName(gfgQ)
// Construct the boundary callback
val boundaryCallback = BoundaryCondition(gfgQ, gfgServiceHandler, gfgLocal)
val networkErrors = boundaryCallback.networkErrors
// Get the paged list
val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()
// Get the network errors
// exposed by the boundary callback
return GfGeeksResult(data, networkErrors)
}
companion object {
private const val DATABASE_PAGE_SIZE = 10
}
}
从数据源检索数据时,可能会出现数据源没有更多数据要提供的情况,即我们已经从数据源检索了所有数据。因此,我们必须处理这些情况。将一个类添加到数据存储包并编写以下代码:
科特林
class BoundaryCondition(
private val gfgQ: String,
private val gfgServicehandler: GithubGfgServicehandler,
private val gfgLocal: LocalGfgLocal
) : PagedList.BoundaryCallback() {
private var lastRequestedPage = 1
private val _gfgErrorErrors = MutableLiveData()
// LiveData of gfgError errors.
val gfgErrorErrors: LiveData
get() = _gfgErrorErrors
// avoid triggering multiple
// requests in the same time
private var isRequestInProgress = false
override fun onZeroItemsLoaded() {
requestAndSaveData(gfgQ)
}
override fun onItemAtEndLoaded(itemAtEnd: RepoModel) {
requestAndSaveData(gfgQ)
}
companion object {
private const val GFGERROR_PAGE_SIZE = 20
}
private fun requestAndSaveData(gfgQ: String) {
if (isRequestInProgress) return
isRequestInProgress = true
searchRepos(gfgServicehandler, gfgQ, lastRequestedPage, GFGERROR_PAGE_SIZE, { repos ->
gfgLocal.insert(repos) {
lastRequestedPage++
isRequestInProgress = false
}
}, { error ->
isRequestInProgress = false
})
}
}
第 9 步:让我们转到应用程序的用户界面。在用户界面包中,创建一个视图持有者并添加以下代码:
科特林
class GeeksViewGfgRepoistory(view: View) : RecyclerView.GeeksView(view) {
private val name: TextView = view.findViewById(R.id.gfgRepoistory_name)
private val description: TextView = view.findViewById(R.id.gfgRepoistory_description)
private val stars: TextView = view.findViewById(R.id.gfgRepoistory_stars)
private val gfgCourseLang: TextView = view.findViewById(R.id.gfgRepoistory_gfgCourseLang)
private val soup: TextView = view.findViewById(R.id.gfgRepoistory_soup)
private var gfgRepoistory: GfgRepoistoryModel? = null
init {
view.setOnClickListener {
gfgRepoistory?.url?.let { url ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
view.context.startActivity(intent)
}
}
}
fun bind(gfgRepoistory: GfgRepoistoryModel?) {
if (gfgRepoistory == null) {
val resources = itemView.resources
name.text = resources.getString(R.string.loading)
description.visibility = View.GONE
gfgCourseLang.visibility = View.GONE
stars.text = resources.getString(R.string.unknown)
soup.text = resources.getString(R.string.unknown)
} else {
showGfgRepoistoryData(gfgRepoistory)
}
}
private fun showGfgRepoistoryData(gfgRepoistory: GfgRepoistoryModel) {
this.gfgRepoistory = gfgRepoistory
name.text = gfgRepoistory.fullName
// if the description is missing, hide the TextView
var descriptionVisibility = View.GONE
if (gfgRepoistory.description != null) {
description.text = gfgRepoistory.description
descriptionVisibility = View.VISIBLE
}
description.visibility = descriptionVisibility
stars.text = gfgRepoistory.stars.toString()
soup.text = gfgRepoistory.soup.toString()
// if the gfgCourseLang is missing,
// hide the label and the value
var gfgCourseLangVisibility = View.GONE
if (!gfgRepoistory.gfgCourseLang.isNullOrEmpty()) {
val resources = this.itemView.context.resources
gfgCourseLang.text = resources.getString(R.string.gfgCourseLang, gfgRepoistory.gfgCourseLang)
gfgCourseLangVisibility = View.VISIBLE
}
gfgCourseLang.visibility = gfgCourseLangVisibility
}
companion object {
fun create(parent: ViewGroup): GeeksViewGfgRepoistory {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.recycler_item, parent, false)
return GeeksViewGfgRepoistory(view)
}
}
}
最后,我们必须包含 MainActivty.kt 文件的代码。在这里,我们将搜索存储库,更新存储库列表,并显示该列表。因此,在MainActivity.kt文件中,粘贴以下代码:
科特林
class GeeksforGeeksGeeksAct : AppCompatGeeksAct() {
private lateinit var gfgVM: GfgVMSearch
private val adapter = AdapterGfgRepos()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.geeksAct_GeeksforGeeks)
// get the view model
gfgVM = GfgVMProviders.of(this, Injection.provideGfgVMFactory(this))
.get(GfgVMSearch::class.java)
// adding spices
val decoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
list.addItemDecoration(decoration)
initAdapter()
val query = savedInstanceState?.getString(LAST_SEARCH_QUERY) ?: GEEKY_QUERY
gfgVM.searchGfgRepos(query)
initSearch(query)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(LAST_SEARCH_QUERY, gfgVM.lastQueryValue())
}
private fun initAdapter() {
list.adapter = adapter
gfgVM.gfgReposs.observe(this, Observer> {
Log.d("GeeksAct", "list: ${it?.size}")
showGfgEmptyList(it?.size == 0)
adapter.submitList(it)
})
gfgVM.networkErrors.observe(this, Observer {
Toast.makeText(this, "\uD83D\uDE28 Wooops $it", Toast.LENGTH_LONG).show()
})
}
private fun initSearch(query: String) {
search_gfgRepos.setText(query)
search_gfgRepos.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
updateGfgReposListFromInput()
true
} else {
false
}
}
search_gfgRepos.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
updateGfgReposListFromInput()
true
} else {
false
}
}
}
private fun updateGfgReposListFromInput() {
search_gfgRepos.text.trim().let {
if (it.isNotGfgEmpty()) {
list.scrollToPosition(0)
gfgVM.searchGfgRepos(it.toString())
adapter.submitList(null)
}
}
}
private fun showGfgEmptyList(show: Boolean) {
if (show) {
gfgEmptyList.visibility = View.VISIBLE
list.visibility = View.GONE
} else {
gfgEmptyList.visibility = View.GONE
list.visibility = View.VISIBLE
}
}
companion object {
private const val LAST_SEARCH_QUERY: String = "last_search_query"
private const val GEEKY_QUERY = "Geeks"
}
}
结论
在本文中,我们了解了 Android 最重要的概念之一:分页。分页库用于从大量可用数据中显示少量数据。因为我们只加载少量数据,所以应用程序的速度提高了。其他数据将根据用户的请求加载。现在,在您的移动设备上运行您的应用程序并尝试到达屏幕底部以查看输出。