$30 off During Our Annual Pro Sale. View Details »

Paging Library ~ アイテムの更新 ~

Paging Library ~ アイテムの更新 ~

Bonfire Android #5での発表資料です

Shinnosuke Kugimiya

August 19, 2019
Tweet

More Decks by Shinnosuke Kugimiya

Other Decks in Programming

Transcript

  1. !LHNZTIJO
    #POpSF"OESPJE
    ΞΠςϜͷߋ৽
    1BHJOH-JCSBSZ

    View Slide

  2. X
    © DMM.com
    ࣗݾ঺հ

    View Slide

  3. X
    © DMM.com
    ࣗݾ঺հ
    w LHNZTIJOఝٶʢ͗͘Έ΍ʣ
    w "OESPJEΤϯδχΞ
    w ߹ಉձࣾ%..DPN
    w $50ࣨॴଐ

    View Slide

  4. X
    © DMM.com
    ΰʔϧ

    View Slide

  5. X
    © DMM.com
    ΰʔϧ
    w ϖʔδϯάͷͭΒΈϙΠϯτ͕Θ͔Δ
    w ϖʔδϯάͷͭΒΈϙΠϯτͷճආํ๏͕Θ͔Δ
    ˞લఏͱͯ͠"1*࿈ܞͷ࿩Ͱ͢ɻ

    View Slide

  6. X
    © DMM.com
    BHFOEB

    View Slide

  7. X
    © DMM.com
    BHFOEB
    w ඪ४తͳ࢖͍ํ
    w ΞΠςϜͷΞοϓσʔτ͕Ͱ͖ͳ͍ʁ
    w 3PPNΛ࢖͏
    w 3PPNΛ࢖Θͣʹ΍Δ
    w ͓ΘΓʹ
    ˞લఏͱͯ͠"1*࿈ܞͷ࿩Ͱ͢ɻ

    View Slide

  8. X
    © DMM.com
    ඪ४తͳ࢖͍ํ

    View Slide

  9. X
    © DMM.com
    ϖʔδϯά

    View Slide

  10. X
    © DMM.com
    ࣮૷ͷྲྀΕ
    w %BUB4PVSDFΛ࣮૷͢Δ
    w %BUB4PVSDF'BDUPSZΛ࣮૷͢Δ
    w -JWF1BHFE-JTU#VJMEFSͰ-JWF%BUB1BHFE-JTU5ΛCVJME͢Δ
    w ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ

    View Slide

  11. X
    © DMM.com
    %BUB4PVSDFΛ࣮૷͢Δ
    internal class ItemDataSource() : PageKeyedDataSource() {
    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
    }
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
    }
    override fun loadBefore(params: LoadParams, callback: LoadCallback) {
    }
    }

    View Slide

  12. X
    © DMM.com
    %BUB4PVSDFΛ࣮૷͢Δ
    internal class ItemDataSource(
    private val itemRepository: ItemRepository
    ) : PageKeyedDataSource() {
    private val dataSourceScope = CoroutineScope(Main + Job())
    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = 0),
    null,
    1
    )
    }
    }
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = params.key),
    params.key + 1
    )
    }
    }

    View Slide

  13. X
    © DMM.com
    %BUB4PVSDFΛ࣮૷͢Δ
    internal class ItemDataSource(
    private val itemRepository: ItemRepository
    ) : PageKeyedDataSource() {
    private val dataSourceScope = CoroutineScope(Main + Job())
    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = 0),
    null,
    1
    )
    }
    }
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = params.key),
    params.key + 1
    )
    }
    }

    View Slide

  14. X
    © DMM.com
    %BUB4PVSDFΛ࣮૷͢Δ
    internal class ItemDataSource(
    private val itemRepository: ItemRepository
    ) : PageKeyedDataSource() {
    private val dataSourceScope = CoroutineScope(Main + Job())
    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = 0),
    null,
    1
    )
    }
    }
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
    dataSourceScope.launch {
    callback.onResult(
    itemRepository.findAll(page = params.key),
    params.key + 1
    )
    }
    }

    View Slide

  15. X
    © DMM.com
    %BUB4PVSDF'BDUPSZΛ࣮૷͢Δ
    internal class ItemDataSourceFactory(
    private val itemRepository: ItemRepository
    ) : DataSource.Factory() {
    override fun create(): DataSource = ItemDataSource(itemRepository)
    }

    View Slide

  16. X
    © DMM.com
    -JWF1BHFE-JTU#VJMEFSͰ-JWF%BUB1BHFE-JTU5ΛCVJME͢Δ
    internal class ItemViewModel(
    itemRepository: ItemRepository
    ) : ViewModel() {
    val itemPagedList = LivePagedListBuilder(
    ItemDataSourceFactory(itemRepository),
    PagedList.Config.Builder()
    .setEnablePlaceholders(false)
    .setPageSize(30)
    .build()
    ).build()
    }

    View Slide

  17. X
    © DMM.com
    ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ
    class ReadOnlyActivity : AppCompatActivity() {
    private val viewModel: ItemViewModel by viewModels {
    ItemViewModelFactory(ItemRepositoryImpl())
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = DataBindingUtil.setContentView(
    this,
    R.layout.activity_item_list
    )
    val adapter = ItemAdapter(this)
    binding.recyclerView.adapter = adapter
    viewModel.itemPagedList.observe(
    this,
    Observer {
    adapter.submitList(it)
    }
    )
    }
    }

    View Slide

  18. X
    © DMM.com
    ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ
    class ReadOnlyActivity : AppCompatActivity() {
    private val viewModel: ItemViewModel by viewModels {
    ItemViewModelFactory(ItemRepositoryImpl())
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = DataBindingUtil.setContentView(
    this,
    R.layout.activity_item_list
    )
    val adapter = ItemAdapter(this)
    binding.recyclerView.adapter = adapter
    viewModel.itemPagedList.observe(
    this,
    Observer {
    adapter.submitList(it)
    }
    )
    }
    }

    View Slide

  19. X
    © DMM.com
    1BHFE-JTU"EBQUFS
    internal class ItemAdapter(context: Context) : PagedListAdapter(ITEM_CALLBACK) {
    companion object {
    private val ITEM_CALLBACK = object : DiffUtil.ItemCallback() {
    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem == newItem
    }
    }
    var onItemClickListener: OnItemClickListener? = null
    private val inflater = LayoutInflater.from(context)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
    ItemViewHolder.create(inflater, parent, false)
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) =
    holder.bind(getItem(position), onItemClickListener)
    }

    View Slide

  20. X
    © DMM.com
    1BHFE-JTU"EBQUFS
    internal class ItemAdapter(context: Context) : PagedListAdapter(ITEM_CALLBACK) {
    companion object {
    private val ITEM_CALLBACK = object : DiffUtil.ItemCallback() {
    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem == newItem
    }
    }
    var onItemClickListener: OnItemClickListener? = null
    private val inflater = LayoutInflater.from(context)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
    ItemViewHolder.create(inflater, parent, false)
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) =
    holder.bind(getItem(position), onItemClickListener)
    }
    ීஈ-JTU"EBQUFSΛ࢖ͬͯΔਓ͸ɺ
    -JTU"EBQUFSΛ1BHFE-JTU"EBQUFSʹ͢Δ͚ͩ

    View Slide

  21. X
    © DMM.com
    ͜ΕͰ׬੒

    View Slide

  22. X
    © DMM.com
    ΞΠςϜͷΞοϓσʔτ͕Ͱ͖ͳ͍ʁ

    View Slide

  23. X
    © DMM.com
    internal class ItemViewModel(
    private val itemRepository: ItemRepository
    ) : ViewModel() {
    private val dataSourceFactory = ItemDataSourceFactory(itemRepository)
    val itemPagedList = LivePagedListBuilder(
    dataSourceFactory,
    PagedList.Config.Builder().setEnablePlaceholders(false).setPageSize(30).build()
    ).build()
    fun check(item: Item): Job = viewModelScope.launch {
    itemRepository.store(item.copy(checked = true))
    dataSourceFactory.sourceLiveData.value?.invalidate()
    }
    fun uncheck(item: Item): Job = viewModelScope.launch {
    itemRepository.store(item.copy(checked = false))
    dataSourceFactory.sourceLiveData.value?.invalidate()
    }
    } %BUB4PVSDFJOWBMJEBUF͢Δͱσʔλͷ࠶औಘ͕૸Δ
    ߋ৽Ͱ͖ΔΑ͏ʹͯ͠ΈΔ
    JUFN3FQPTJUPSZʹ৽͍͠σʔλΛอଘͯ͠ɺߋ৽Λ͔͚Δ

    View Slide

  24. X
    © DMM.com
    ߋ৽Ͱ͖ΔΑ͏ʹͯ͠ΈΔ
    class NgActivity : AppCompatActivity() {
    private val viewModel: ItemViewModel by viewModels {
    ItemViewModelFactory(ItemRepositoryImpl())
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = DataBindingUtil.setContentView(
    this,
    R.layout.activity_item_list
    )
    val adapter = ItemAdapter(this).apply {
    onItemClickListener = object : OnItemClickListener {
    override fun onClick(item: Item) {
    if (item.checked) {
    viewModel.uncheck(item)
    } else {
    viewModel.check(item)
    }
    }
    }
    }
    binding.recyclerView.adapter = adapter

    View Slide

  25. X
    © DMM.com
    ͜ΕͰ׬੒ʁ

    View Slide

  26. X
    © DMM.com
    ݁Ռ
    w ߋ৽͸Ͱ͖͍ͯΔ
    w ͕ɺϦετ͕ઌ಄ʹ໭ͬͯ͠·͏

    View Slide

  27. X
    © DMM.com
    ͳ͔ͥ
    dataSource?.invalidate()

    View Slide

  28. X
    © DMM.com
    ͳ͔ͥ
    dataSource?.invalidate()
    ८Γ८ͬͯ͜͜ʹ
    @Override
    protected PagedList compute() {
    @Nullable Key initializeKey = initialLoadKey;
    if (mList != null) {
    initializeKey = (Key) mList.getLastKey();
    }
    do {
    if (mDataSource != null) {
    mDataSource.removeInvalidatedCallback(mCallback);
    }
    mDataSource = dataSourceFactory.create();
    mDataSource.addInvalidatedCallback(mCallback);
    mList = new PagedList.Builder<>(mDataSource, config)
    .setNotifyExecutor(notifyExecutor)
    .setFetchExecutor(fetchExecutor)
    .setBoundaryCallback(boundaryCallback)
    .setInitialKey(initializeKey)
    .build();
    } while (mList.isDetached());
    return mList;
    }

    View Slide

  29. X
    © DMM.com
    @Override
    protected PagedList compute() {
    @Nullable Key initializeKey = initialLoadKey;
    if (mList != null) {
    initializeKey = (Key) mList.getLastKey();
    }
    do {
    if (mDataSource != null) {
    mDataSource.removeInvalidatedCallback(mCallback);
    }
    mDataSource = dataSourceFactory.create();
    mDataSource.addInvalidatedCallback(mCallback);
    mList = new PagedList.Builder<>(mDataSource, config)
    .setNotifyExecutor(notifyExecutor)
    .setFetchExecutor(fetchExecutor)
    .setBoundaryCallback(boundaryCallback)
    .setInitialKey(initializeKey)
    .build();
    } while (mList.isDetached());
    return mList;
    }
    ͳ͔ͥ
    dataSource?.invalidate()
    ͕͜͜QPJOU
    JOJUJBMJ[F,FZΛऔಘͯͦ͠ΕΛݩʹ࡞Γ௚͍ͯ͠Δ

    View Slide

  30. X
    © DMM.com
    @Nullable Key initializeKey = initialLoadKey;
    if (mList != null) {
    initializeKey = (Key) mList.getLastKey();
    }
    ͳ͔ͥ
    @Nullable
    @Override
    public Object getLastKey() {
    return mDataSource.getKey(mLastLoad, mLastItem);
    }
    ContiguousPagedList

    View Slide

  31. X
    © DMM.com
    @Nullable Key initializeKey = initialLoadKey;
    if (mList != null) {
    initializeKey = (Key) mList.getLastKey();
    }
    ͳ͔ͥ
    public Object getLastKey() {
    return mDataSource.getKey(mLastLoad, mLastItem);
    }
    ContiguousPagedList
    PageKeyedDataSource
    final Key getKey(int position, Value item) {
    // don't attempt to persist keys, since we currently don't pass them to initial load
    return null;
    }

    View Slide

  32. X
    © DMM.com
    @Nullable Key initializeKey = initialLoadKey;
    if (mList != null) {
    initializeKey = (Key) mList.getLastKey();
    }
    ͳ͔ͥ
    public Object getLastKey() {
    return mDataSource.getKey(mLastLoad, mLastItem);
    }
    ContiguousPagedList
    PageKeyedDataSource
    final Key getKey(int position, Value item) {
    // don't attempt to persist keys, since we currently don't pass them to initial load
    return null;
    }
    OVMMͳͷͰඞͣ࠷ॳͷϖʔδ͔ΒʹͳΔ

    View Slide

  33. X
    © DMM.com
    3PPNΛ࢖͏

    View Slide

  34. X
    © DMM.com
    3PPNΛ࢖͏
    w 3PPN͸1BHJOHʹରԠ͍ͯ͠Δ
    w #PVOEBSZ$BMMCBDLͷதͰσʔλͷऔಘ3PPN΁ͷJOTFSUΛ͢Δ
    w ʢ*OWBMJEBUJPO5SBDLFSͱ͍͏ͷ͕͋ͬͯɺJOTFSU͢Δͱউखʹ
    %BUB4PVSDFJOWBMJEBUF͕૸Δʣ
    w ߋ৽͞ΕΔ

    View Slide

  35. X
    © DMM.com
    ࣮૷ͷྲྀΕ
    w %BUB4PVSDFΛ࣮૷͢Δ
    w %BUB4PVSDF'BDUPSZΛ࣮૷͢Δ
    w %BUBCBTF %BPΛ࣮૷͢Δ
    w #PVOEBSZ$BMMCBDLͰσʔλͷऔಘΛͯ͠%#ʹJOTFSU͢Δ
    w -JWF1BHFE-JTU#VJMEFSͰ-JWF%BUB1BHFE-JTU5ΛCVJME͢Δ
    w ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ

    View Slide

  36. X
    © DMM.com
    %BUBCBTFɺ%BPΛ࣮૷͢Δ
    @Dao
    internal interface ItemDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(items: ItemRecord)
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun bulkUpsert(items: List)
    @Query("DELETE FROM item")
    suspend fun deleteAll()
    @Query("SELECT * FROM item")
    fun selectAll(): DataSource.Factory
    }
    @Database(
    entities = [ItemRecord::class],
    version = 1,
    exportSchema = false
    )
    internal abstract class InMemDatabase : RoomDatabase() {
    companion object {
    fun create(context: Context): InMemDatabase =
    Room.inMemoryDatabaseBuilder(
    context,
    InMemDatabase::class.java
    ).fallbackToDestructiveMigration().build()
    }
    abstract fun itemDao(): ItemDao
    }
    %BP %BUBCBTF

    View Slide

  37. X
    © DMM.com
    %BUBCBTFɺ%BPΛ࣮૷͢Δ
    @Dao
    internal interface ItemDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(items: ItemRecord)
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun bulkUpsert(items: List)
    @Query("DELETE FROM item")
    suspend fun deleteAll()
    @Query("SELECT * FROM item")
    fun selectAll(): DataSource.Factory
    }
    @Database(
    entities = [ItemRecord::class],
    version = 1,
    exportSchema = false
    )
    internal abstract class InMemDatabase : RoomDatabase() {
    companion object {
    fun create(context: Context): InMemDatabase =
    Room.inMemoryDatabaseBuilder(
    context,
    InMemDatabase::class.java
    ).fallbackToDestructiveMigration().build()
    }
    abstract fun itemDao(): ItemDao
    }
    %BP %BUBCBTF
    ϚΠάϨʔγϣϯͱ͔େมͳͷͰJO.FNPSZͰ0,

    View Slide

  38. X
    © DMM.com
    #PVOEBSZ$BMMCBDLͰσʔλͷऔಘΛͯ͠%#ʹJOTFSU͢Δ
    internal class ItemBoundaryCallback(
    private val itemRepository: ItemRepository,
    private val inMemDatabase: InMemDatabase
    ) : PagedList.BoundaryCallback() {
    private val boundaryCallbackScope = CoroutineScope(Main + Job())
    override fun onZeroItemsLoaded() {
    super.onZeroItemsLoaded()
    if (pageList.isNotEmpty() && pageList[0].isEndPage) return
    boundaryCallbackScope.launch {
    val itemList = itemRepository.findAll(page = 0)
    if (itemList.isNotEmpty()) {
    bulkInsertToDatabase(itemList)
    }

    }
    }

    View Slide

  39. X
    © DMM.com
    -JWF1BHFE-JTU#VJMEFSͰ-JWF%BUB1BHFE-JTU5ΛCVJME͢Δ
    internal class ItemViewModel(
    private val itemRepository: ItemRepository,
    private val inMemDatabase: InMemDatabase
    ) : ViewModel() {
    val itemPagedList = LivePagedListBuilder(
    inMemDatabase.itemDao().selectAll().map { ItemConverter.convertToItem(it) },
    PagedList.Config.Builder()
    .setEnablePlaceholders(false)
    .setPageSize(30)
    .build()
    ).setBoundaryCallback(
    ItemBoundaryCallback(
    itemRepository,
    inMemDatabase
    )
    ).build()
    }

    View Slide

  40. X
    © DMM.com
    ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ
    class RoomActivity : AppCompatActivity() {
    private val viewModel: ItemViewModel by viewModels {
    ItemViewModelFactory(
    ItemRepositoryImpl(),
    InMemDatabase.create(this)
    )
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = DataBindingUtil.setContentView(
    this,
    R.layout.activity_item_list
    )
    val adapter = ItemAdapter(this)
    binding.recyclerView.adapter = adapter
    viewModel.itemPagedList.observe(
    this,
    Observer {
    adapter.submitList(it)
    }
    )
    }
    }

    View Slide

  41. X
    © DMM.com
    ߋ৽࣌͸%BPͷVQEBUFϝιουΛݺͿ
    internal class ItemViewModel(
    private val itemRepository: ItemRepository,
    private val inMemDatabase: InMemDatabase
    ) : ViewModel() {

    fun check(item: Item): Job = viewModelScope.launch {
    val newItem = item.copy(checked = true)
    itemRepository.store(newItem)
    inMemDatabase.itemDao().upsert(ItemConverter.convertToRecord(newItem))
    }
    fun uncheck(item: Item): Job = viewModelScope.launch {
    val newItem = item.copy(checked = false)
    itemRepository.store(newItem)
    inMemDatabase.itemDao().upsert(ItemConverter.convertToRecord(newItem))
    }
    }

    View Slide

  42. X
    © DMM.com
    ׬੒

    View Slide

  43. X
    © DMM.com
    1BHJOHͷͨΊʹ%BUBCBTFΛಋೖʜʁ
    w ӬଓԽ͢ΔΘ͚Ͱ΋ͳ͍ͷʹӬଓԽͷͨΊͷ΋ͷΛ࢖ͬͯྑ͍ͷ͔ʜ
    w *O.FNPSZ͔ͩΒϚΠάϨʔγϣϯ͕େมͱ͔͸ͳ͍
    w 3PPNΛ࢖Θͳ͍ํ๏΋͋Δ

    View Slide

  44. X
    © DMM.com
    3PPNΛ࢖Θͣʹ΍Δ

    View Slide

  45. X
    © DMM.com
    *O.FNPSZͰσʔλΛ؅ཧ͢Δ΋ͷΛ࡞Δ
    *UFN%BUB1SPWJEFS
    *UFN
    *UFN
    *UFN
    *UFN
    *UFN
    *UFN
    QBHF
    QBHF
    3FQPTJUPSZ
    1BHF୯Ґ
    *UFN,FZFE
    %BUB4PVSDF
    *UFN୯Ґ

    View Slide

  46. X
    © DMM.com
    ࣮૷ͷྲྀΕ
    w *UFN%BUB1SPWJEFSΛ࣮૷͢Δ
    w %BUB4PVSDFΛ࣮૷͢Δ
    w %BUB4PVSDF'BDUPSZΛ࣮૷͢Δ
    w #PVOEBSZ$BMMCBDLͰσʔλͷऔಘΛͯ͠*UFN%BUB1SPWJEFSʹBEE͢
    Δ
    w -JWF1BHFE-JTU#VJMEFSͰ-JWF%BUB1BHFE-JTU5ΛCVJME͢Δ
    w ͦΕΛPCTFSWFͯ͠1BHFE-JTU"EBQUFSʹTVCNJU-JTU͢Δ

    View Slide

  47. X
    © DMM.com
    *UFN%BUB1SPWJEFSΛ࣮૷͢Δ
    internal data class Page(
    val itemList: List,
    val isEndPage: Boolean
    )
    internal class ItemDataProvider {
    val sourceLiveData = MutableLiveData>()
    private val _pageList = mutableListOf()
    val pageList: List
    get() = _pageList
    fun addPage(page: Page) {
    _pageList.add(page)
    sourceLiveData.value?.invalidate()
    }
    fun update(item: Item) {

    sourceLiveData.value?.invalidate()
    }
    }

    View Slide

  48. X
    © DMM.com
    *UFN#PVOEBSZ$BMMCBDLΛ࣮૷͢Δ
    internal class ItemBoundaryCallback(
    private val itemRepository: ItemRepository,
    private val itemDataProvider: ItemDataProvider
    ) : PagedList.BoundaryCallback() {
    private val boundaryCallbackScope = CoroutineScope(Dispatchers.Main + Job())
    override fun onZeroItemsLoaded() {
    super.onZeroItemsLoaded()
    if (itemDataProvider.pageList.isNotEmpty() && itemDataProvider.pageList[0].isEndPage) return
    boundaryCallbackScope.launch {
    val itemList = itemRepository.findAll(page = 0)
    itemDataProvider.addPage(
    page = Page(
    itemList = itemList,
    isEndPage = itemList.isEmpty()
    )
    )
    }
    }
    override fun onItemAtEndLoaded(itemAtEnd: Item) { … }

    View Slide

  49. X
    © DMM.com
    *UFN%BUB1SPWJEFSͷVQEBUFϝιουΛݺͿ
    internal class ItemViewModel(
    private val itemRepository: ItemRepository,
    private val itemDataProvider: ItemDataProvider
    ) : ViewModel() {

    fun check(item: Item): Job = viewModelScope.launch {
    val newItem = item.copy(checked = true)
    itemRepository.store(newItem)
    itemDataProvider.update(newItem)
    }
    fun uncheck(item: Item): Job = viewModelScope.launch {
    val newItem = item.copy(checked = false)
    itemRepository.store(newItem)
    itemDataProvider.update(newItem)
    }
    }

    View Slide

  50. X
    © DMM.com
    ׬੒

    View Slide

  51. X
    © DMM.com
    ͓ΘΓʹ

    View Slide

  52. X
    © DMM.com
    ͓ΘΓʹ
    w 1BHJOH͸ߋ৽͕ೖΓ࢝ΊΔͱਏ͍
    w 3PPNͰ*O.FNPSZͳΒϚΠάϨʔγϣϯͱ͔΋ͳ͍ͷͰ͋Γ
    w ͜Ε͚ͩͷͨΊʹ3PPNΛೖΕΔͷ͸໨త͔Β֎ΕͯΔͷͰɺ3PPNͳ
    ͠Ͱ΍Δͷ΋͋Γ
    αϯϓϧίʔυ https://github.com/kgmyshin/paging-sample

    View Slide

  53. X
    © DMM.com
    αϯϓϧίʔυ͋Γ㽂
    w ϦʔυΦϯϦʔύλʔϯʢVQEBUFͳ͠ʣ
    w ୯७ʹVQEBUF͚ͩͰ͖ΔΑ͏ʹͯ͠Έͨύλʔϯʢઌ಄ʹ͍͘Α͏ʹ
    ͳͬͯ͠·͏ࣦഊύλʔϯʣ
    w 3PPN࢖༻ύλʔϯ
    w 3PPNෆ࢖༻ύλʔϯ
    https://github.com/kgmyshin/paging-sample

    View Slide