Android架構組件-PagingLibrary

Paging Library

分頁庫概述

分頁庫可幫助您一次加載和顯示小塊數據。按需加載部分數據可減少網絡帶寬和系統資源的使用。

本指南提供了庫的幾個概念性示例,以及它如何工作的概述。要查看此庫如何運行的完整示例,請嘗試使用其他資源部分中的codelab和示例。additional resources

依賴

dependencies {
  def paging_version = "2.1.0"

  implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktxac
  
   implementation "androidx.paging:paging-runtime-ktx:2.1.0-rc01" // For Kotlin use paging-runtime-ktx
}

For information on using Kotlin extensions, see ktx documentation.

Library architecture

本節描述並顯示了分頁庫的主要組件。

PagedList

分頁庫的關鍵組件是PagedList類,它加載應用程序數據或頁面的塊。由於需要更多數據,因此將其分頁到現有的PagedList對像中.如果任何加載的數據發生更改,則會從LiveData或基於RxJava2的對象向可觀察數據持有者發出新的PagedList實例。生成PagedList對象,您的應用程序的UI顯示其內容,同時尊重您的UI控制器的生命週期。

以下代碼段顯示瞭如何使用PagedList對象的LiveData持有者配置應用程序的視圖模型以加載和顯示數據:
kotlin:

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

java:

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    // Creates a PagedList object with 50 items per page.
    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
                concertDao.concertsByDate(), 50).build();
    }
}

Data

PagedList的每個實例都從其相應的DataSource對象加載應用程序數據的最新快照。數據從應用程序的後端或數據庫流入PagedList對象。

以下示例使用Room持久性庫來組織應用程序的數據,但如果要使用其他方法存儲數據,還可以提供自己的數據源工廠。

kotlin:

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

java:

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a
    // PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

UI

PagedList類使用PagedListAdapter將項目加載到RecyclerView中。這些類一起工作以在加載內容時獲取和顯示內容,預取視圖內容並動畫內容更改。

支持不同的數據架構

分頁庫支持以下數據體系結構:

  • 僅從後端服務器提供。
  • 僅存儲在設備上的數據庫中。
  • 使用設備上數據庫作為緩存的其他源的組合。

圖1展示了每種架構方案中數據的流動方式。對於僅限網絡或僅限數據庫的解決方案,數據直接流向應用程序的UI模型。如果您使用的是組合方法,則數據會從後端服務器流入設備上的數據庫,然後流入應用程序的UI模型。每隔一段時間,每個數據流的端點就會耗盡要加載的數據,此時它會從提供數據的組件請求更多數據.例如,當設備上數據庫用完數據時,它會從服務器請求更多數據。圖1

本節的其餘部分提供了配置每個數據流用例的建議。

Network only

要顯示來自後端服務器的數據,請使用Retrofit API的同步版本將信息加載到您自己的自定義DataSource對象中

注意:Paging Library的DataSource對象不提供任何錯誤處理,因爲不同的應用程序以不同的方式處理和顯示錯誤UI。 如果發生錯誤,請遵循結果回調,稍後重試該請求。有關此行爲的示例,請參閱PagingWithNetwork示例。

Database only

設置RecyclerView以觀察本地存儲,最好使用Room persistence library。這樣,無論何時在應用程序的數據庫中插入或修改數據,這些更改都會自動反映在顯示此數據的RecyclerView中。

Network and database

在開始觀察數據庫之後,您可以使用PagedList.BoundaryCallback監聽數據庫何時沒有數據。然後,您可以從網絡中獲取更多項目並將其插入數據庫。如果您的UI正在觀察數據庫,那就是您需要做的。

處理網絡錯誤

當使用網絡獲取或分頁您正在使用分頁庫顯示的數據時,重要的是不要將網絡視爲“可用”或“不可用”,因爲許多連接是間歇性的或片狀的:

  • 特定服務器可能無法響應網絡請求。
  • 設備可能連接到緩慢或弱的網絡。

相反,您的應用應檢查每個失敗請求,並在網絡不可用的情況下儘可能優雅地恢復。例如,如果數據刷新步驟不起作用,您可以提供“重試”按鈕供用戶選擇。如果在數據分頁步驟期間發生錯誤,則最好自動重試分頁請求。

Update your existing app

如果您的應用已經使用了數據庫或後端源中的數據,則可以直接升級到分頁庫提供的功能。本節介紹如何升級具有通用現有設計的應用程序。

定製分頁解決方案

如果您使用自定義功能從應用程序的數據源加載小的數據子集,則可以將此邏輯替換爲PagedList類中的邏輯.PagedList的實例提供與公共數據源的內置連接。這些實例還爲您可能包含在應用程序UI中的RecyclerView對象提供適配器。

使用列表而不是頁面加載數據

如果使用內存列表作爲UI適配器的後備數據結構,如果列表中的項目數可能變大,請考慮使用PagedList類觀察數據更新.PagedList的實例可以使用LiveData 或Observable 將數據更新傳遞到應用程序的UI,從而最大限度地減少加載時間和內存使用量。更好的是,在應用程序中用PagedList對象替換List對象不需要對應用程序的UI結構或數據更新邏輯進行任何更改。

使用CursorAdapter將數據光標與列表視圖相關聯

您的應用程序可能使用CursorAdapter將Cursor中的數據與ListView相關聯。在這種情況下,您通常需要從ListView遷移到RecyclerView作爲應用程序的列表UI容器,然後將Cursor組件替換爲Room或PositionalDataSource,具體取決於Cursor實例是否訪問SQLite數據庫。

在某些情況下,例如使用Spinner實例時,只提供適配器本身。然後,庫將獲取加載到該適配器中的數據併爲您顯示數據。在這些情況下,將適配器數據的類型更改爲LiveData ,然後在嘗試讓庫類在UI中對這些項進行inflate之前,將此列表包裝在ArrayAdapter對象中。

使用AsyncListUtil異步加載內容

如果您使用AsyncListUtil對象異步加載和顯示信息組,則分頁庫可讓您更輕鬆地加載數據:

  • **您的數據不需要是位置的。**分頁庫允許您使用網絡提供的密鑰直接從後端加載數據。
  • **您的數據可能非常大。**使用分頁庫,您可以將數據加載到頁面中,直到沒有剩餘數據。
  • 您可以更輕鬆地觀察數據。 Paging庫可以顯示應用程序的ViewModel在可觀察數據結構中保存的數據。

數據庫示例

以下代碼片段顯示了使所有部分協同工作的幾種可能方法。

使用LiveData觀察分頁數據

以下代碼段顯示了一起工作的所有部分。隨着在數據庫中添加,刪除或更改Concert事件,RecyclerView中的內容將自動且高效地更新:

kotlin:

	@Dao
	interface ConcertDao {
	    // The Int type parameter tells Room to use a PositionalDataSource
	    // object, with position-based loading under the hood.
	    @Query("SELECT * FROM concerts ORDER BY date DESC")
	    fun concertsByDate(): DataSource.Factory<Int, Concert>
	}
	
	class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
	    val concertList: LiveData<PagedList<Concert>> =
	            concertDao.concertsByDate().toLiveData(pageSize = 50)
	}
	
	class ConcertActivity : AppCompatActivity() {
	    public override fun onCreate(savedInstanceState: Bundle?) {
	        super.onCreate(savedInstanceState)
	        val viewModel = ViewModelProviders.of(this)
	                .get<ConcertViewModel>()
	        val recyclerView = findViewById(R.id.concert_list)
	        val adapter = ConcertAdapter()
	        viewModel.livePagedList.observe(this, PagedList(adapter::submitList))
	        recyclerView.setAdapter(adapter)
	    }
	}
	
	class ConcertAdapter() :
	        PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
	    fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
	        val concert: Concert? = getItem(position)
	
	        // Note that "concert" is a placeholder if it's null.
	        holder.bindTo(concert)
	    }
	
	    companion object {
	        private val DIFF_CALLBACK = object :
	                DiffUtil.ItemCallback<Concert>() {
	            // Concert details may have changed if reloaded from the database,
	            // but ID is fixed.
	            override fun areItemsTheSame(oldConcert: Concert,
	                    newConcert: Concert) = oldConcert.id == newConcert.id
	
	            override fun areContentsTheSame(oldConcert: Concert,
	                    newConcert: Concert) = oldConcert == newConcert
	        }
	    }
	}

java :

	@Dao
	public interface ConcertDao {
	    // The Integer type parameter tells Room to use a PositionalDataSource
	    // object, with position-based loading under the hood.
	    @Query("SELECT * FROM concerts ORDER BY date DESC")
	    DataSource.Factory<Integer, Concert> concertsByDate();
	}
	
	public class ConcertViewModel extends ViewModel {
	    private ConcertDao concertDao;
	    public final LiveData<PagedList<Concert>> concertList;
	
	    public ConcertViewModel(ConcertDao concertDao) {
	        this.concertDao = concertDao;
	        concertList = new LivePagedListBuilder<>(
	            concertDao.concertsByDate(), /* page size */ 50).build();
	    }
	}
	
	public class ConcertActivity extends AppCompatActivity {
	    @Override
	    public void onCreate(@Nullable Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        ConcertViewModel viewModel =
	                ViewModelProviders.of(this).get(ConcertViewModel.class);
	        RecyclerView recyclerView = findViewById(R.id.concert_list);
	        ConcertAdapter adapter = new ConcertAdapter();
	        viewModel.concertList.observe(this, adapter::submitList);
	        recyclerView.setAdapter(adapter);
	    }
	}
	
	public class ConcertAdapter
	        extends PagedListAdapter<Concert, ConcertViewHolder> {
	    protected ConcertAdapter() {
	        super(DIFF_CALLBACK);
	    }
	
	    @Override
	    public void onBindViewHolder(@NonNull ConcertViewHolder holder,
	            int position) {
	        Concert concert = getItem(position);
	        if (concert != null) {
	            holder.bindTo(concert);
	        } else {
	            // Null defines a placeholder item - PagedListAdapter automatically
	            // invalidates this row when the actual object is loaded from the
	            // database.
	            holder.clear();
	        }
	    }
	
	    private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
	            new DiffUtil.ItemCallback<Concert>() {
	        // Concert details may have changed if reloaded from the database,
	        // but ID is fixed.
	        @Override
	        public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
	            return oldConcert.getId() == newConcert.getId();
	        }
	
	        @Override
	        public boolean areContentsTheSame(Concert oldConcert,
	                Concert newConcert) {
	            return oldConcert.equals(newConcert);
	        }
	    };
	}

使用RxJava2觀察分頁數據

如果您更喜歡使用RxJava2而不是LiveData,則可以創建一個Observable或Flowable對象:

kotlin:

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: Observable<PagedList<Concert>> =
            concertDao.concertsByDate().toObservable(pageSize = 50)
}

java:

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final Observable<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;

        concertList = new RxPagedListBuilder<>(
                concertDao.concertsByDate(), /* page size */ 50)
                        .buildObservable();
    }
}

然後,您可以使用以下代碼段中的代碼開始和停止觀察數據:

kotlin:

	class ConcertActivity : AppCompatActivity() {
	    private val adapter: ConcertAdapter()
	    private lateinit var viewModel: ConcertViewModel
	
	    private val disposable = CompositeDisposable()
	
	    public override fun onCreate(savedInstanceState: Bundle?) {
	        super.onCreate(savedInstanceState)
	        val recyclerView = findViewById(R.id.concert_list)
	        viewModel = ViewModelProviders.of(this)
	                .get<ConcertViewModel>()
	        recyclerView.setAdapter(adapter)
	    }
	
	    override fun onStart() {
	        super.onStart()
	        disposable.add(viewModel.concertList
	                .subscribe(adapter::submitList)))
	    }
	
	    override fun onStop() {
	        super.onStop()
	        disposable.clear()
	    }
	}

java

	public class ConcertActivity extends AppCompatActivity {
	    private ConcertAdapter adapter = new ConcertAdapter();
	    private ConcertViewModel viewModel;
	
	    private CompositeDisposable disposable = new CompositeDisposable();
	
	    @Override
	    public void onCreate(@Nullable Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        RecyclerView recyclerView = findViewById(R.id.concert_list);
	
	        viewModel = ViewModelProviders.of(this).get(ConcertViewModel.class);
	        recyclerView.setAdapter(adapter);
	    }
	
	    @Override
	    protected void onStart() {
	        super.onStart();
	        disposable.add(viewModel.concertList
	                .subscribe(adapter.submitList(flowableList)
	        ));
	    }
	
	    @Override
	    protected void onStop() {
	        super.onStop();
	        disposable.clear();
	    }
	}

對於基於RxJava2的解決方案,ConcertDaoConcertAdapter的代碼與基於LiveData的解決方案的代碼相同。

顯示分頁列表

本指南以分頁庫概述爲基礎,描述瞭如何在應用程序UI中向用戶顯示信息列表,尤其是在此信息發生變化時。

##將UI連接到View Model

您可以將LiveData 的實例連接到PagedListAdapter,如以下代碼段所示:

kotlin:

private val adapter = ConcertAdapter()
private lateinit var viewModel: ConcertViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java)
    viewModel.concerts.observe(this, Observer { adapter.submitList(it) })
}

java:

public class ConcertActivity extends AppCompatActivity {
    private ConcertAdapter adapter = new ConcertAdapter();
    private ConcertViewModel viewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = ViewModelProviders.of(this).get(ConcertViewModel.class);
        viewModel.concertList.observe(this, adapter::submitList);
    }
}

當DataSource提供PagedList的新實例時,活動會將這些對象發送到適配器。PagedListAdapter實現定義瞭如何計算更新,並自動處理分頁和列表差異。因此,您的ViewHolder只需要綁定到特定提供的項目:

kotlin:

class ConcertAdapter() :
        PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
    override fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
        val concert: Concert? = getItem(position)

        // Note that "concert" is a placeholder if it's null.
        holder.bindTo(concert)
    }

    companion object {
        private val DIFF_CALLBACK = ... // See Implement the diffing callback section.
    }
}

java:

public class ConcertAdapter
        extends PagedListAdapter<Concert, ConcertViewHolder> {
    protected ConcertAdapter() {
        super(DIFF_CALLBACK);
    }

    @Override
    public void onBindViewHolder(@NonNull ConcertViewHolder holder,
            int position) {
        Concert concert = getItem(position);

        // Note that "concert" can be null if it's a placeholder.
        holder.bindTo(concert);
    }

    private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK
            = ... // See Implement the diffing callback section.
}

PagedListAdapter使用PagedList.Callback對象處理頁面加載事件。當用戶滾動時,PagedListAdapter調用PagedList.loadAround()以向底層的PagedList提供關於它應該從DataSource獲取哪些項的提示。

實現差異回調

以下示例顯示了areContentsTheSame()的手動實現,它比較了相關的對象字段:

kotlin:

private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() {
    // The ID property identifies when items are the same.
    override fun areItemsTheSame(oldItem: Concert, newItem: Concert) =
            oldItem.id == newItem.id

    // If you use the "==" operator, make sure that the object implements
    // .equals(). Alternatively, write custom data comparison logic here.
    override fun areContentsTheSame(
            oldItem: Concert, newItem: Concert) = oldItem == newItem
}

java:

private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
        new DiffUtil.ItemCallback<Concert>() {

    @Override
    public boolean areItemsTheSame(Concert oldItem, Concert newItem) {
        // The ID property identifies when items are the same.
        return oldItem.getId() == newItem.getId();
    }

    @Override
    public boolean areContentsTheSame(Concert oldItem, Concert newItem) {
        // Don't use the "==" operator here. Either implement and use .equals(),
        // or write custom data comparison logic here.
        return oldItem.equals(newItem);
    }
};

由於適配器包含比較項的定義,因此適配器會在加載新的PagedList對象時自動檢測對這些項的更改。因此,適配器會在RecyclerView對象中觸發高效的item動畫。

使用不同的適配器類型進行區分

如果您選擇不從PagedListAdapter繼承 - 例如當您使用提供自己的適配器的庫時 - 您仍然可以通過直接使用AsyncPagedListDiffer對象來使用Paging Library適配器的diffing功能。

在UI中提供佔位符

如果您希望UI在應用程序完成獲取數據之前顯示列表,您可以向用戶顯示佔位符列表項。PagedList通過將列表項數據顯示爲null來處理這種情況,直到加載數據。

注意:默認情況下,分頁庫啓用此佔位符行爲。

佔位符具有以下好處:

  • **支持滾動條:**PagedList向PagedListAdapter提供列表項的數量。此信息允許適配器繪製一個滾動條,傳達列表的完整大小。加載新頁面時,滾動條不會跳轉,因爲列表不會更改大小。

  • **無需加載微調器:**由於列表大小已知,因此無需提醒用戶正在加載更多項目。佔位符本身傳達了這些信息。

但是,在添加對佔位符的支持之前,請記住以下前提條件:

  • **需要可數數據集:**Room持久性庫中的DataSource實例可以有效地計算其項目。但是,如果您使用的是自定義本地存儲解決方案或僅限網絡的數據體系結構,則確定數據集中包含的項目數量可能很昂貴甚至無法實現。

  • **需要適配器來考慮卸載的項目:**用於爲通脹準備列表的適配器或表示機制需要處理空列表項。例如,將數據綁定到ViewHolder時,需要提供默認值來表示卸載的數據。

  • **需要相同大小的項目視圖:**如果列表項大小可以根據其內容(例如社交網絡更新)而更改,則項之間的交叉淡化看起來不太好。我們強烈建議在這種情況下禁用佔位符。

#收集分頁數據

本指南以Paging Library概述爲基礎,討論如何自定義應用程序的數據加載解決方案以滿足應用程序的架構需求。

構造一個可觀察的列表

通常,您的UI代碼會觀察LiveData 對象(或者,如果您使用的是RxJava2,則爲Flowable 或Observable 對象),該對象位於應用程序的ViewModel中。此可觀察對象在應用程序列表數據的表示和內容之間形成連接。

爲了創建這些可觀察的PagedList對象之一,將DataSource.Factory的實例傳遞給LivePagedListBuilderRxPagedListBuilder對象。DataSource對象加載單個PagedList的頁面。工廠類創建PagedList的新實例以響應內容更新,例如數據庫表失效和網絡刷新。Room持久性庫可以爲您提供DataSource.Factory對象,或者您可以構建自己的對象

以下代碼段顯示瞭如何使用Room的DataSource.Factory構建功能在應用程序的ViewModel類中創建LiveData 的新實例:

ConcertDao:

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}
@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

ConcertViewModel:

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
       concertDao.concertsByDate()

val concertList = myConcertDataSource.toLiveData(pageSize = 50)
// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
       concertDao.concertsByDate();

LiveData<PagedList<Concert>> concertList =
        LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();

定義您自己的分頁配置

要爲高級案例進一步配置LiveData ,您還可以定義自己的分頁配置。特別是,您可以定義以下屬性:

  • Page size: 每頁中的項目數。
  • Prefetch distance:給定應用程序UI中的最後一個可見項目,該頁面庫應該嘗試提前獲取的最後一項之外的項目數。此值應該是頁面大小的幾倍。
  • Placeholder presence:確定UI是否顯示尚未完成加載的列表項的佔位符。有關使用佔位符的優缺點的討論,請了解如何在UI中提供佔位符

如果您想更好地控制Paging Library何時從應用程序的數據庫加載列表,請將自定義Executor對象傳遞給LivePagedListBuilder,如以下代碼段所示:

ConcertViewModel:

val myPagingConfig = Config(
        pageSize = 50,
        prefetchDistance = 150,
        enablePlaceholders = true
)

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
        concertDao.concertsByDate()

val concertList = myConcertDataSource.toLiveData(
        pagingConfig = myPagingConfig,
        fetchExecutor = myExecutor
)
PagedList.Config myPagingConfig = new PagedList.Config.Builder()
        .setPageSize(50)
        .setPrefetchDistance(150)
        .setEnablePlaceholders(true)
        .build();

// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
        concertDao.concertsByDate();

LiveData<PagedList<Concert>> concertList =
        new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig)
            .setFetchExecutor(myExecutor)
            .build();

選擇正確的數據源類型

連接到最能處理源數據結構的數據源非常重要:

  • 如果您加載的頁面嵌入了下一個/上一個鍵,請使用PageKeyedDataSource。例如,如果您從網絡中獲取社交媒體帖子,則可能需要將nextPage令牌從一個加載傳遞到後續加載。
  • 如果需要使用項目N中的數據來獲取項目N + 1,請使用ItemKeyedDataSource。例如,如果您要爲討論應用程序提取線程評論,則可能需要傳遞最後一條評論的ID以獲取下一條評論的內容。
  • 如果需要從數據存儲中選擇的任何位置獲取數據頁,請使用PositionalDataSource。此類支持從您選擇的任何位置開始請求一組數據項。例如,請求可能返回以位置1500開頭的50個數據項。

數據無效時通知

使用分頁庫時,數據層當表或行變得陳舊時通知應用程序的其他層。爲此,請從您爲應用程序選擇的DataSource類中調用invalidate()。

注意:您的應用的UI可以使用[滑動刷新(swip to refresh)](https://developer.android.google.cn/training/swipe)模型觸發此數據失效功能。

構建自己的數據源

如果使用自定義本地數據解決方案,或者直接從網絡加載數據,則可以實現其中一個DataSource子類。以下代碼段顯示了一個數據源,該數據源與給定Concert的start time相關聯:

kotlin:

class ConcertTimeDataSource() :
        ItemKeyedDataSource<Date, Concert>() {
    override fun getKey(item: Concert) = item.startTime

    override fun loadInitial(
            params: LoadInitialParams<Date>,
            callback: LoadInitialCallback<Concert>) {
        val items = fetchItems(params.requestedInitialKey,
                params.requestedLoadSize)
        callback.onResult(items)
    }

    override fun loadAfter(
            params: LoadParams<Date>,
            callback: LoadCallback<Concert>) {
        val items = fetchItemsAfter(
            date = params.key,
            limit = params.requestedLoadSize)
        callback.onResult(items)
    }
}

java:

public class ConcertTimeDataSource
        extends ItemKeyedDataSource<Date, Concert> {
    @NonNull
    @Override
    public Date getKey(@NonNull Concert item) {
        return item.getStartTime();
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Date> params,
            @NonNull LoadInitialCallback<Concert> callback) {
        List<Concert> items =
            fetchItems(params.key, params.requestedLoadSize);
        callback.onResult(items);
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Date> params,
            @NonNull LoadCallback<Concert> callback) {
        List<Concert> items =
            fetchItemsAfter(params.key, params.requestedLoadSize);
        callback.onResult(items);
    }

然後,您可以通過創建DataSource.Factory的具體子類將此自定義數據加載到PagedList對象中。以下代碼段顯示瞭如何生成前面代碼段中定義的自定義數據源的新實例:

kotlin:

class ConcertTimeDataSourceFactory :
        DataSource.Factory<Date, Concert>() {
    val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
    var latestSource: ConcertDataSource?
    override fun create(): DataSource<Date, Concert> {
        latestSource = ConcertTimeDataSource()
        sourceLiveData.postValue(latestSource)
        return latestSource
    }
}

java

public class ConcertTimeDataSourceFactory
        extends DataSource.Factory<Date, Concert> {
    private MutableLiveData<ConcertTimeDataSource> sourceLiveData =
            new MutableLiveData<>();

    private ConcertDataSource latestSource;

    @Override
    public DataSource<Date, Concert> create() {
        latestSource = new ConcertTimeDataSource();
        sourceLiveData.postValue(latestSource);
        return latestSource;
    }
}

考慮內容更新的工作原理

在構造可觀察的PagedList對象時,請考慮內容更新的工作方式。如果您直接從Room數據庫加載數據,則會自動將更新推送到您應用的UI。

使用分頁網絡API時,您通常會進行用戶交互,例如“滑動以刷新”,作爲使您最近使用的數據源無效的信號。然後,您請求該數據源的新實例。以下代碼段演示了此行爲:

kotlin

class ConcertActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        concertTimeViewModel.refreshState.observe(this, Observer {
            // Shows one possible way of triggering a refresh operation.
            swipeRefreshLayout.isRefreshing =
                    it == MyNetworkState.LOADING
        })
        swipeRefreshLayout.setOnRefreshListener {
            concertTimeViewModel.invalidateDataSource()
        }
    }
}

class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {
    val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)
    val concertList: LiveData<PagedList<Concert>> =
            dataSourceFactory.toLiveData(
                pageSize = 50,
                fetchExecutor = myExecutor
            )

    fun invalidateDataSource() =
            dataSourceFactory.sourceLiveData.value?.invalidate()
}

java:

public class ConcertActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        // ...
        viewModel.getRefreshState()
                .observe(this, new Observer<NetworkState>() {
            // Shows one possible way of triggering a refresh operation.
            @Override
            public void onChanged(@Nullable MyNetworkState networkState) {
                swipeRefreshLayout.isRefreshing =
                        networkState == MyNetworkState.LOADING;
            }
        };

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() {
            @Override
            public void onRefresh() {
                viewModel.invalidateDataSource();
            }
        });
    }
}

public class ConcertTimeViewModel extends ViewModel {
    private LiveData<PagedList<Concert>> concertList;
    private DataSource<Date, Concert> mostRecentDataSource;

    public ConcertTimeViewModel(Date firstConcertStartTime) {
        ConcertTimeDataSourceFactory dataSourceFactory =
                new ConcertTimeDataSourceFactory(firstConcertStartTime);
        mostRecentDataSource = dataSourceFactory.create();
        concertList = new LivePagedListBuilder<>(dataSourceFactory, 50)
                .setFetchExecutor(myExecutor)
                .build();
    }

    public void invalidateDataSource() {
        mostRecentDataSource.invalidate();
    }
}

提供數據映射

分頁庫支持由DataSource加載的項目的基於項目和基於頁面的轉換。

在以下代碼段中,Concert名稱和Concert日期的組合映射到包含名稱和日期的單個字符串:

kotlin:

class ConcertViewModel : ViewModel() {
    val concertDescriptions : LiveData<PagedList<String>>
        init {
            val concerts = database.allConcertsFactory()
                    .map "${it.name} - ${it.date}" }
                    .toLiveData(pageSize = 50)
        }
    }
}

java:

public class ConcertViewModel extends ViewModel {
    private LiveData<PagedList<String>> concertDescriptions;

    public ConcertViewModel(MyDatabase database) {
        DataSource.Factory<Integer, Concert> factory =
                database.allConcertsFactory().map(concert ->
                    concert.getName() + "-" + concert.getDate());
        concertDescriptions = new LivePagedListBuilder<>(
            factory, /* page size */ 50).build();
    }
}

如果要在項目加載後進行換行,轉換或準備,這可能很有用。由於此工作是在獲取執行程序上完成的,因此您可以執行可能昂貴的工作,例如從磁盤讀取或查詢單獨的數據庫。

注意:JOIN查詢總是更有效,可以作爲map()的一部分進行重新查詢。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章