Sicherlich hat jeder Android-Entwickler mit RecyclerView mit Listen gearbeitet. Außerdem gelang es vielen, mithilfe der Paging-Bibliothek von Android Architecture Components zu sehen, wie die Paginierung in der Liste organisiert wird.
Es ist ganz einfach: Legen Sie die PositionalDataSource fest, legen Sie die Konfigurationen fest, erstellen Sie die PagedList und geben Sie alles mit dem Adapter und DiffUtilCallback an unsere RecyclerView weiter.
Was aber, wenn wir mehrere Datenquellen haben? Zum Beispiel möchten wir einen Cache in Room haben und Daten vom Netzwerk empfangen.
Der Fall stellt sich als sehr benutzerdefiniert heraus und es gibt nicht viele Informationen zu diesem Thema im Internet. Ich werde versuchen, das Problem zu beheben und zu zeigen, wie ein solcher Fall gelöst werden kann.

Wenn Sie mit der Implementierung der Paginierung mit einer einzigen Datenquelle noch nicht vertraut sind, empfehle ich Ihnen, sich vor dem Lesen des Artikels damit vertraut zu machen.
Wie eine Lösung ohne Paginierung aussehen würde:
- Zugriff auf den Cache (in unserem Fall handelt es sich um eine Datenbank)
- Wenn der Cache leer ist, senden Sie eine Anfrage an den Server
- Wir erhalten Daten vom Server
- Wir zeigen sie in einem Blatt
- In den Cache schreiben
- Wenn es einen Cache gibt - zeigen Sie ihn in der Liste an
- Wir erhalten die neuesten Daten vom Server
- Wir zeigen sie in der Liste ○ an
- In den Cache schreiben

Eine so bequeme Sache wie die Paginierung, die das Leben der Benutzer vereinfacht, erschwert uns hier. Versuchen wir uns vorzustellen, welche Probleme bei der Implementierung einer paginierten Liste mit mehreren Datenquellen auftreten können.
Der Algorithmus ist ungefähr der folgende:
- Cache-Daten für die erste Seite abrufen
- Wenn der Cache leer ist, erhalten wir die Serverdaten, zeigen sie in der Liste an und schreiben in die Datenbank
- Wenn ein Cache vorhanden ist, laden Sie ihn in die Liste
- Wenn wir am Ende der Datenbank angelangt sind, fordern wir Daten vom Server an und zeigen sie an
- in die Liste und schreiben Sie in die Datenbank
Von den Funktionen dieses Ansatzes können Sie sehen, dass zum Anzeigen der Liste zuerst der Cache abgefragt wird und das Signal zum Laden neuer Daten das Ende des Caches ist.

Google hat darüber nachgedacht und eine Lösung entwickelt, die aus der PagingLibrary-Box kommt - BoundaryCallback.
BoundaryCallback meldet, wenn die lokale Datenquelle "endet", und benachrichtigt das Repository, um neue Daten herunterzuladen.

Die offizielle Android Dev-Website enthält einen Link zu einem Repository mit einem Beispielprojekt, das eine Paginierungsliste mit zwei Datenquellen verwendet: Netzwerk (Nachrüstung 2) + Datenbank (Raum). Um besser zu verstehen, wie ein solches System funktioniert, versuchen wir, dieses Beispiel zu analysieren und es ein wenig zu vereinfachen.
Beginnen wir mit der Datenschicht. Erstellen Sie zwei DataSource.
Schnittstelle RedditApi.ktimport com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query interface RedditApi { @GET("/r/{subreddit}/hot.json") fun getTop( @Path("subreddit") subreddit: String, @Query("limit") limit: Int): Call<ListingResponse>
Diese Schnittstelle beschreibt die Anforderungen an die Reddit-API und die Modellklassen (ListingResponse, ListingData, RedditChildrenResponse), in welche Objekte die API-Antworten reduziert werden.
Und machen Sie sofort ein Modell für Retrofit und Room
RedditPost.kt import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import com.google.gson.annotations.SerializedName @Entity(tableName = "posts", indices = [Index(value = ["subreddit"], unique = false)]) data class RedditPost( @PrimaryKey @SerializedName("name") val name: String, @SerializedName("title") val title: String, @SerializedName("score") val score: Int, @SerializedName("author") val author: String, @SerializedName("subreddit")
Die RedditDb.kt-Klasse, die RoomDatabase erbt.
Redditdb.kt import androidx.room.Database import androidx.room.RoomDatabase import com.memebattle.pagingwithrepository.domain.model.RedditPost @Database( entities = [RedditPost::class], version = 1, exportSchema = false ) abstract class RedditDb : RoomDatabase() { abstract fun posts(): RedditPostDao }
Denken Sie daran, dass das Erstellen der RoomDatabase-Klasse jedes Mal zum Ausführen einer Abfrage an die Datenbank sehr teuer ist. Erstellen Sie sie in einem realen Fall einmal für die gesamte Lebensdauer der Anwendung!
Und die Dao-Klasse mit Datenbankabfragen RedditPostDao.kt
RedditPostDao.kt import androidx.paging.DataSource import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.memebattle.pagingwithrepository.domain.model.RedditPost @Dao interface RedditPostDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(posts : List<RedditPost>) @Query("SELECT * FROM posts WHERE subreddit = :subreddit ORDER BY indexInResponse ASC") fun postsBySubreddit(subreddit : String) : DataSource.Factory<Int, RedditPost> @Query("DELETE FROM posts WHERE subreddit = :subreddit") fun deleteBySubreddit(subreddit: String) @Query("SELECT MAX(indexInResponse) + 1 FROM posts WHERE subreddit = :subreddit") fun getNextIndexInSubreddit(subreddit: String) : Int }
Sie haben wahrscheinlich bemerkt, dass die PostsBySubreddit-Methode zum Abrufen von Posts zurückgegeben wird
DataSource.Factory. Dies ist erforderlich, um unsere PagedList mit zu erstellen
LivePagedListBuilder im Hintergrundthread. Mehr dazu lesen Sie in
Lektion .
Großartig, die Datenschicht ist fertig. Wir wenden uns der Geschäftslogikschicht zu. Um das Repository-Muster zu implementieren, ist es üblich, eine Repository-Schnittstelle getrennt von ihrer Implementierung zu erstellen. Daher erstellen wir die Schnittstelle RedditPostRepository.kt
RedditPostRepository.kt interface RedditPostRepository { fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost> }
Und sofort die Frage - welche Art von Listing? Dies ist die Datumsklasse, die zum Anzeigen der Liste erforderlich ist.
Listing.kt import androidx.lifecycle.LiveData import androidx.paging.PagedList import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState data class Listing<T>( // the LiveData of paged lists for the UI to observe val pagedList: LiveData<PagedList<T>>, // represents the network request status to show to the user val networkState: LiveData<NetworkState>, // represents the refresh status to show to the user. Separate from networkState, this // value is importantly only when refresh is requested. val refreshState: LiveData<NetworkState>, // refreshes the whole data and fetches it from scratch. val refresh: () -> Unit, // retries any failed requests. val retry: () -> Unit)
Erstellen Sie eine Implementierung des MainRepository.kt-Repositorys
MainRepository.kt import android.content.Context import androidx.annotation.MainThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import com.android.example.paging.pagingwithnetwork.reddit.api.RedditApi import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import androidx.room.Room import com.android.example.paging.pagingwithnetwork.reddit.db.RedditDb import com.android.example.paging.pagingwithnetwork.reddit.db.RedditPostDao import com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.concurrent.Executors import androidx.paging.LivePagedListBuilder import com.memebattle.pagingwithrepository.domain.repository.core.Listing import com.memebattle.pagingwithrepository.domain.repository.boundary.SubredditBoundaryCallback import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.domain.repository.core.RedditPostRepository class MainRepository(context: Context) : RedditPostRepository { private var retrofit: Retrofit = Retrofit.Builder() .baseUrl("https://www.reddit.com/")
Mal sehen, was in unserem Repository passiert.
Erstellen Sie Instanzen unserer Datenquellen- und Datenzugriffsschnittstellen. Für die Datenbank:
RoomDatabase und Dao für das Netzwerk: Retrofit- und API-Schnittstelle.
Als nächstes implementieren wir die erforderliche Repository-Methode
fun postsOfSubreddit(subReddit: String, pageSize: Int): Listing<RedditPost>
was die Paginierung einrichtet:
- Erstellen Sie einen SubRedditBoundaryCallback, der PagedList.BoundaryCallback <> erbt
- Wir verwenden den Konstruktor mit Parametern und übergeben alles, was für die Funktion von BoundaryCallback erforderlich ist
- Erstellen Sie einen refreshTrigger-Trigger, um das Repository über die Notwendigkeit zu informieren, Daten zu aktualisieren
- Erstellen Sie ein Listing-Objekt und geben Sie es zurück
Im Listing-Objekt:
- livePagedList
- networkState - Netzwerkstatus
- retry - Rückruf zum Aufrufen des Abrufs von Daten vom Server
- Aktualisieren - Auslöser zum Aktualisieren von Daten
- refreshState - Status des Aktualisierungsprozesses
Wir implementieren eine Hilfsmethode
private fun insertResultIntoDb(subredditName: String, body: RedditApi.ListingResponse?)
um die Netzwerkantwort in der Datenbank aufzuzeichnen. Es wird verwendet, wenn Sie die Liste aktualisieren oder neue Daten schreiben müssen.
Wir implementieren eine Hilfsmethode
private fun refresh(subredditName: String): LiveData<NetworkState>
für einen Datenaktualisierungsauslöser. Hier ist alles ganz einfach: Wir erhalten Daten vom Server, bereinigen die Datenbank und schreiben neue Daten in die Datenbank.
Wir haben das Repository herausgefunden. Schauen wir uns nun den SubredditBoundaryCallback genauer an.
SubredditBoundaryCallback.kt import androidx.paging.PagedList import androidx.annotation.MainThread import com.android.example.paging.pagingwithnetwork.reddit.api.RedditApi import com.memebattle.pagingwithrepository.domain.model.RedditPost import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.util.concurrent.Executor import com.memebattle.pagingwithrepository.domain.util.PagingRequestHelper import com.memebattle.pagingwithrepository.domain.repository.network.createStatusLiveData class SubredditBoundaryCallback( private val subredditName: String, private val webservice: RedditApi, private val handleResponse: (String, RedditApi.ListingResponse?) -> Unit, private val ioExecutor: Executor, private val networkPageSize: Int) : PagedList.BoundaryCallback<RedditPost>() { val helper = PagingRequestHelper(ioExecutor) val networkState = helper.createStatusLiveData() @MainThread override fun onZeroItemsLoaded() { helper.runIfNotRunning(PagingRequestHelper.RequestType.INITIAL) { webservice.getTop( subreddit = subredditName, limit = networkPageSize) .enqueue(createWebserviceCallback(it)) } } @MainThread override fun onItemAtEndLoaded(itemAtEnd: RedditPost) { helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { webservice.getTopAfter( subreddit = subredditName, after = itemAtEnd.name, limit = networkPageSize) .enqueue(createWebserviceCallback(it)) } } private fun insertItemsIntoDb( response: Response<RedditApi.ListingResponse>, it: PagingRequestHelper.Request.Callback) { ioExecutor.execute { handleResponse(subredditName, response.body()) it.recordSuccess() } } override fun onItemAtFrontLoaded(itemAtFront: RedditPost) {
In der Klasse, die BoundaryCallback erbt, sind mehrere Methoden erforderlich:
override fun onZeroItemsLoaded()
Die Methode wird aufgerufen, wenn die Datenbank leer ist. Hier müssen wir eine Anfrage an den Server erfüllen, um die erste Seite zu erhalten.
override fun onItemAtEndLoaded(itemAtEnd: RedditPost)
Die Methode wird aufgerufen, wenn der „Iterator“ den „unteren Rand“ der Datenbank erreicht hat. Hier müssen wir den Server abfragen, um die nächste Seite zu erhalten, und den Schlüssel übergeben, mit dem der Server die Daten unmittelbar nach dem letzten Datensatz des lokalen Speichers ausgibt.
override fun onItemAtFrontLoaded(itemAtFront: RedditPost)
Die Methode wird aufgerufen, wenn der „Iterator“ das erste Element unseres Geschäfts erreicht hat. Um unseren Fall zu implementieren, können wir die Implementierung dieser Methode ignorieren.
Fügen Sie einen Rückruf hinzu, um Daten zu empfangen und weiter zu übertragen
fun createWebserviceCallback(it: PagingRequestHelper.Request.Callback) : Callback<RedditApi.ListingResponse>
Wir fügen die Methode zum Aufzeichnen der empfangenen Daten in der Datenbank hinzu
insertItemsIntoDb( response: Response<RedditApi.ListingResponse>, it: PagingRequestHelper.Request.Callback)
Was ist der PagingRequestHelper-Helfer? Dies ist eine GESUNDE Klasse, die Google uns freundlicherweise zur Verfügung gestellt hat und die anbietet, sie in die Bibliothek aufzunehmen, aber wir kopieren sie einfach in das Logikschichtpaket.
PagingRequestHelper.kt package com.memebattle.pagingwithrepository.domain.util; import java.util.Arrays; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.AnyThread; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.paging.DataSource;
PagingRequestHelperExt.kt import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.memebattle.pagingwithrepository.domain.util.PagingRequestHelper private fun getErrorMessage(report: PagingRequestHelper.StatusReport): String { return PagingRequestHelper.RequestType.values().mapNotNull { report.getErrorFor(it)?.message }.first() } fun PagingRequestHelper.createStatusLiveData(): LiveData<NetworkState> { val liveData = MutableLiveData<NetworkState>() addListener { report -> when { report.hasRunning() -> liveData.postValue(NetworkState.LOADING) report.hasError() -> liveData.postValue( NetworkState.error(getErrorMessage(report))) else -> liveData.postValue(NetworkState.LOADED) } } return liveData }
, .
MVVM Google ViewModel LiveData.
MainActivity.kt import android.os.Bundle import android.view.KeyEvent import android.view.inputmethod.EditorInfo import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.paging.PagedList import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost import com.memebattle.pagingwithrepository.domain.repository.MainRepository import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.presentation.recycler.PostsAdapter import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { companion object { const val KEY_SUBREDDIT = "subreddit" const val DEFAULT_SUBREDDIT = "androiddev" } lateinit var model: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) model = getViewModel() initAdapter() initSwipeToRefresh() initSearch() val subreddit = savedInstanceState?.getString(KEY_SUBREDDIT) ?: DEFAULT_SUBREDDIT model.showSubReddit(subreddit) } private fun getViewModel(): MainViewModel { return ViewModelProviders.of(this, object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { val repo = MainRepository(this@MainActivity) @Suppress("UNCHECKED_CAST") return MainViewModel(repo) as T } })[MainViewModel::class.java] } private fun initAdapter() { val adapter = PostsAdapter { model.retry() } list.adapter = adapter model.posts.observe(this, Observer<PagedList<RedditPost>> { adapter.submitList(it) }) model.networkState.observe(this, Observer { adapter.setNetworkState(it) }) } private fun initSwipeToRefresh() { model.refreshState.observe(this, Observer { swipe_refresh.isRefreshing = it == NetworkState.LOADING }) swipe_refresh.setOnRefreshListener { model.refresh() } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(KEY_SUBREDDIT, model.currentSubreddit()) } private fun initSearch() { input.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_GO) { updatedSubredditFromInput() true } else { false } } input.setOnKeyListener { _, keyCode, event -> if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { updatedSubredditFromInput() true } else { false } } } private fun updatedSubredditFromInput() { input.text.trim().toString().let { if (it.isNotEmpty()) { if (model.showSubReddit(it)) { list.scrollToPosition(0) (list.adapter as? PostsAdapter)?.submitList(null) } } } } }
onCreate ViewModel, , .
LiveData ViewModel, .
MainViewModel.kt import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import com.memebattle.pagingwithrepository.domain.repository.core.RedditPostRepository class MainViewModel(private val repository: RedditPostRepository) : ViewModel() { private val subredditName = MutableLiveData<String>() private val repoResult = Transformations.map(subredditName) { repository.postsOfSubreddit(it, 10) } val posts = Transformations.switchMap(repoResult) { it.pagedList }!! val networkState = Transformations.switchMap(repoResult) { it.networkState }!! val refreshState = Transformations.switchMap(repoResult) { it.refreshState }!! fun refresh() { repoResult.value?.refresh?.invoke() } fun showSubReddit(subreddit: String): Boolean { if (subredditName.value == subreddit) { return false } subredditName.value = subreddit return true } fun retry() { val listing = repoResult?.value listing?.retry?.invoke() } fun currentSubreddit(): String? = subredditName.value }
, : retry refesh.
PagedListAdapter. .
PostAdapter.kt import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import android.view.ViewGroup import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.presentation.recycler.viewholder.NetworkStateItemViewHolder import com.memebattle.pagingwithrepository.presentation.recycler.viewholder.RedditPostViewHolder class PostsAdapter( private val retryCallback: () -> Unit) : PagedListAdapter<RedditPost, RecyclerView.ViewHolder>(POST_COMPARATOR) { private var networkState: NetworkState? = null override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (getItemViewType(position)) { R.layout.reddit_post_item -> (holder as RedditPostViewHolder).bind(getItem(position)) R.layout.network_state_item -> (holder as NetworkStateItemViewHolder).bindTo( networkState) } } override fun onBindViewHolder( holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isNotEmpty()) { val item = getItem(position) (holder as RedditPostViewHolder).updateScore(item) } else { onBindViewHolder(holder, position) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { R.layout.reddit_post_item -> RedditPostViewHolder.create(parent) R.layout.network_state_item -> NetworkStateItemViewHolder.create(parent, retryCallback) else -> throw IllegalArgumentException("unknown view type $viewType") } } private fun hasExtraRow() = networkState != null && networkState != NetworkState.LOADED override fun getItemViewType(position: Int): Int { return if (hasExtraRow() && position == itemCount - 1) { R.layout.network_state_item } else { R.layout.reddit_post_item } } override fun getItemCount(): Int { return super.getItemCount() + if (hasExtraRow()) 1 else 0 } fun setNetworkState(newNetworkState: NetworkState?) { val previousState = this.networkState val hadExtraRow = hasExtraRow() this.networkState = newNetworkState val hasExtraRow = hasExtraRow() if (hadExtraRow != hasExtraRow) { if (hadExtraRow) { notifyItemRemoved(super.getItemCount()) } else { notifyItemInserted(super.getItemCount()) } } else if (hasExtraRow && previousState != newNetworkState) { notifyItemChanged(itemCount - 1) } } companion object { private val PAYLOAD_SCORE = Any() val POST_COMPARATOR = object : DiffUtil.ItemCallback<RedditPost>() { override fun areContentsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean = oldItem == newItem override fun areItemsTheSame(oldItem: RedditPost, newItem: RedditPost): Boolean = oldItem.name == newItem.name override fun getChangePayload(oldItem: RedditPost, newItem: RedditPost): Any? { return if (sameExceptScore(oldItem, newItem)) { PAYLOAD_SCORE } else { null } } } private fun sameExceptScore(oldItem: RedditPost, newItem: RedditPost): Boolean {
ViewHolder .
RedditPostViewHolder.kt import android.content.Intent import android.net.Uri import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.model.RedditPost class RedditPostViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val title: TextView = view.findViewById(R.id.title) private val subtitle: TextView = view.findViewById(R.id.subtitle) private val score: TextView = view.findViewById(R.id.score) private var post : RedditPost? = null init { view.setOnClickListener { post?.url?.let { url -> val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) view.context.startActivity(intent) } } } fun bind(post: RedditPost?) { this.post = post title.text = post?.title ?: "loading" subtitle.text = itemView.context.resources.getString(R.string.post_subtitle, post?.author ?: "unknown") score.text = "${post?.score ?: 0}" } companion object { fun create(parent: ViewGroup): RedditPostViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.reddit_post_item, parent, false) return RedditPostViewHolder(view) } } fun updateScore(item: RedditPost?) { post = item score.text = "${item?.score ?: 0}" } }
NetworkStateItemViewHolder.kt import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar import android.widget.TextView import com.memebattle.pagingwithrepository.R import com.memebattle.pagingwithrepository.domain.repository.network.NetworkState import com.memebattle.pagingwithrepository.domain.repository.network.Status class NetworkStateItemViewHolder(view: View, private val retryCallback: () -> Unit) : RecyclerView.ViewHolder(view) { private val progressBar = view.findViewById<ProgressBar>(R.id.progress_bar) private val retry = view.findViewById<Button>(R.id.retry_button) private val errorMsg = view.findViewById<TextView>(R.id.error_msg) init { retry.setOnClickListener { retryCallback() } } fun bindTo(networkState: NetworkState?) { progressBar.visibility = toVisibility(networkState?.status == Status.RUNNING) retry.visibility = toVisibility(networkState?.status == Status.FAILED) errorMsg.visibility = toVisibility(networkState?.msg != null) errorMsg.text = networkState?.msg } companion object { fun create(parent: ViewGroup, retryCallback: () -> Unit): NetworkStateItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.network_state_item, parent, false) return NetworkStateItemViewHolder(view, retryCallback) } fun toVisibility(constraint : Boolean): Int { return if (constraint) { View.VISIBLE } else { View.GONE } } } }
, , Reddit androiddev. , .

, !
, Google.
Das ist alles. “” , .
!