Slide 1

Slide 1 text

How PayPay Flea Market Migrated from Paging 2 to Paging 3 Rei Nagahama / Yahoo! JAPAN

Slide 2

Slide 2 text

Agenda - Self Introduction - What is Paging3? - Points Noted at the Time of Migration

Slide 3

Slide 3 text

Self Introduction - Rei Nagahama Twitter: Fel1Tech - Joined Yahoo Japan Corporation in 2021 as a new graduate - In charge of Android application development for PayPay Flea Market - In the middle of migration from Paging2 to Paging3

Slide 4

Slide 4 text

PayPay Flea Market - Specialization in flea market - Payment through PayPay - Proposals and consultation for prices - Linking with Yahoo Auction!

Slide 5

Slide 5 text

Small Data Small Data Large Data What is Paging3? Official library for easy implementation of paging process

Slide 6

Slide 6 text

Why is it necessary to migrate? What is Paging3? - Coroutines Flow becomes available - Paging2 was deprecated at the same time when Paging3 was released in May 2021 - Paging3 provides loading status

Slide 7

Slide 7 text

Why the migration at this time? What is Paging3? - Unclear if Paging3 is stable in operation - No research done on the migration - No implementation experience with other services ?

Slide 8

Slide 8 text

How did we migrate? What is Paging3? 1.Migrate BoundaryCallback to RemoteMediator 2.Migrate PagedListAdapter to PagingDataAdapter 3.Migration of processing around State to loadStateFlow Official Documents:https://developer.android.com/topic/libraries/architecture/paging/v3-migration

Slide 9

Slide 9 text

Migrate BoundaryCallback to RemoteMediator How did we migrate? class ItemBoundaryCallback( … ) : PagedList.BoundaryCallback() return LivePagedListBuilder(items, PAGE_SIZE) .setBoundaryCallback(boundaryCallback) .build() class ItemRemoteMediator( … ) : RemoteMediator() return Pager(…).flow.cachedIn(scope)

Slide 10

Slide 10 text

Migrate PagedListAdapter to PagingDataAdapter How did we migrate? class ItemPagedListAdapter( … ) : PagedListAdapter(diffCallback) class ItemPagingDataAdapter( … ) : PagingDataAdapter(diffCallback)

Slide 11

Slide 11 text

Migration of processing around State to loadStateFlow How did we migrate? itemState.postValue(Loading) repository.getItems() when (itemState) { Loading -> binding.loading. visibility = VISIBLE … } lifecycleScope.launch { adapter.loadStateFlow.collectLatest { state -> if(state == LoadState.Loading) { binding.loading.visibility = VISIBLE } } }

Slide 12

Slide 12 text

Points Noted at the Time of Migration What is Paging3? - Summarized and shared the method of migration to Paging3 - Migrated from the screens having minimal impact on services - Migrated the original implementation relevant to State and performing refactoring at the same time

Slide 13

Slide 13 text

Points Noted at the Time of Migration What is Paging3? - Summarized and shared the method of migration to Paging3 - Migrated from the screens having minimal impact on services - Migrated the original implementation relevant to State and performing refactoring at the same time

Slide 14

Slide 14 text

Migrated from the screens having minimal impact on services Points Noted at the Time of Migration - Migrated from the screens that do not affect primary functions - Migration may cause malfunction - Saved the know-how through gradual migration

Slide 15

Slide 15 text

Points Noted at the Time of Migration What is Paging3? - Summarized and shared the method of migration to Paging3 - Migrated from the screens having minimal impact on services - Migrated the original implementation relevant to State and performing refactoring at the same time

Slide 16

Slide 16 text

Points Noted at the Time of Migration What is Paging3? - Summarized and shared the method of migration to Paging3 - Migrated from the screens having minimal impact on services - Migrated the original implementation relevant to State and performing refactoring at the same time

Slide 17

Slide 17 text

Migrated the original implementation of State management and DB and performed refactoring at the same time Points Noted at the Time of Migration - Efficient refactoring - Refactoring, which requires a lot of labor is difficult - Simultaneous confirmation that Paging, State, and DB behavior is correct

Slide 18

Slide 18 text

Knowledge gained in migration - Original implementation around DataBase that caused problems in migration - Fatal code that can occur easily but generates infinite API calls - Advantages and considerations for migrating State to LoadStateFlow

Slide 19

Slide 19 text

How did we migrate? What is Paging3? 1.Migrate BoundaryCallback to RemoteMediator 2.Migrate PagedListAdapter to PagingDataAdapter 3.Migration of processing around State to loadStateFlow Official Documents:https://developer.android.com/topic/libraries/architecture/paging/v3-migration

Slide 20

Slide 20 text

Migrate BoundaryCallback to RemoteMediator How did we migrate? class ItemBoundaryCallback( … ) : PagedList.BoundaryCallback() return LivePagedListBuilder(items, PAGE_SIZE) .setBoundaryCallback(boundaryCallback) .build() class ItemRemoteMediator( … ) : RemoteMediator() return Pager(…).flow.cachedIn(scope)

Slide 21

Slide 21 text

Migrate PagedListAdapter to PagingDataAdapter How did we migrate? class ItemPagedListAdapter( … ) : PagedListAdapter(diffCallback) class ItemPagingDataAdapter( … ) : PagingDataAdapter(diffCallback)

Slide 22

Slide 22 text

Migration of processing around State to loadStateFlow How did we migrate? itemState.postValue(Loading) repository.getItems() when (itemState) { Loading -> binding.loading. visibility = VISIBLE … } lifecycleScope.launch { adapter.loadStateFlow.collectLatest { state -> if(state == LoadState.Loading) { binding.loading.visibility = VISIBLE } } }

Slide 23

Slide 23 text

Knowledge gained in migration - Original implementation around DataBase that caused problems in migration - Fatal code that can occur easily but generates infinite API calls - Advantages and considerations for migrating State to LoadStateFlow

Slide 24

Slide 24 text

Fatal code that can occur easily but generates infinite API calls - It may occur because of the load function of RemoteMediator - Service may go down due to high server load Knowledge gained in migration

Slide 25

Slide 25 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isZeroMatch MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } NG

Slide 26

Slide 26 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isEmpty() MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK

Slide 27

Slide 27 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isEmpty() MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK

Slide 28

Slide 28 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isEmpty() MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK Upward reading is Since there is no need for Success(true)

Slide 29

Slide 29 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isEmpty() MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK Upward reading is Since there is no need for Success(true) Why is it necessary? 写真:アフロ

Slide 30

Slide 30 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isEmpty() MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK Upward reading is Since there is no need for Success(true) Let’s have a look at the loading order 写真:アフロ

Slide 31

Slide 31 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state : PagingState): MediatorResult { Log.d(“RemoteMediator”, “LoadType: ${loadType.name}”) MediatorResult.Success(true) }

Slide 32

Slide 32 text

Fatal code that can occur easily but generates infinite API calls 13:41:34:046 RemoteMediator: LoadType: REFRESH ← initial loading 13:41:34:411 RemoteMediator: LoadType: PREPEND ← Upward loading 13:41:34:531 RemoteMediator: LoadType: APPEND ← Downward loading 13:41:34:652 RemoteMediator: LoadType: APPEND 13:41:34:657 RemoteMediator: LoadType: APPEND ・ ・ ・ OK

Slide 33

Slide 33 text

Fatal code that can occur easily but generates infinite API calls 13:41:34:046 RemoteMediator: LoadType: REFRESH ← initial loading 13:41:34:411 RemoteMediator: LoadType: PREPEND ← Upward loading 13:41:34:531 RemoteMediator: LoadType: PREPEND 13:41:34:652 RemoteMediator: LoadType: PREPEND 13:41:34:657 RemoteMediator: LoadType: PREPEND ・ ・ ・ Judgment of upward loading in the internal part NG

Slide 34

Slide 34 text

Fatal code that can occur easily but generates infinite API calls Judgment of initial state without scrolling is Judgment of upper end

Slide 35

Slide 35 text

API Call Scroll position is maintained as superscript Judgment of upper end Internal part has upward loading Data is added below

Slide 36

Slide 36 text

API Call Scroll position is maintained as superscript Judgment of upper end Internal part has upward loading Data is added below

Slide 37

Slide 37 text

API Call Scroll position is maintained as superscript Judgment of upper end Internal part has upward loading Data is added below

Slide 38

Slide 38 text

API Call Scroll position is maintained as superscript Judgment of upper end Internal part has upward loading Data is added below

Slide 39

Slide 39 text

API Call Scroll position is maintained as superscript Judgment of upper end Internal part has upward loading Data is added below Until all the data is collected 写真:アフロ

Slide 40

Slide 40 text

Fatal code that can occur easily but generates infinite API calls override suspend fun load(loadType: LoadType, state : PagingState): MediatorResult { return try { withContext(Dispatchers.IO) { if (loadType == LoadType.PREPEND) return MediatorResult.Success(true) val response = fetchAndRegisterData() val endOfPaginationReached = response.data.isZeroMatch MediatorResult.Success(endOfPaginationReached) } } catch (e: Exception) { MediatorResult.Error(e) } } OK As PREPEND is not required at the time of downward loading, Success(true) is returned

Slide 41

Slide 41 text

Knowledge gained in migration - Original implementation around DataBase that caused problems in migration - Fatal code that can occur easily but generates infinite API calls - Advantages and considerations for migrating State to LoadStateFlow

Slide 42

Slide 42 text

Original Implementation around the DataBase that Caused Problems in Migration - Information relevant to State can be acquired using loadStateListener - Leaving implementation of State for both DataBase and loadStateListener aside is not a good option as the implementation will be reviewed again in the future - Information about State and information about products were saved together in DataBase

Slide 43

Slide 43 text

Original implementation around DataBase that caused problems in migration @Entity data class Item( @PrimaryKey(autoGenerate = true) val id: Int, val title: String, val empty: Boolean = false ) Flag whether or not to display an indication of missing data

Slide 44

Slide 44 text

Original implementation around DataBase that caused problems in migration @Entity data class Item( @PrimaryKey(autoGenerate = true) val id: Int, val title: String, val empty: Boolean = false ) Converted to an object indicating that there was no data and Item and State were separated out.

Slide 45

Slide 45 text

Original implementation around DataBase that caused problems in migration 1.Separating the processes related to display such as State from DB 2.Aggregate State to Fragment 3.Migrate BoundaryCallback and other data acquisition processes 4.Managing the State on the basis of the loadStateFlow of PagingDataAdapter

Slide 46

Slide 46 text

Original implementation around DataBase that caused problems in migration 1.Separating the processes related to display such as State from DB 2.Aggregate State to Fragment 3.Migrate BoundaryCallback and other data acquisition processes 4.Managing the State on the basis of the loadStateFlow of PagingDataAdapter

Slide 47

Slide 47 text

Original implementation around DataBase that caused problems in migration @Entity data class Item( @PrimaryKey(autoGenerate = true) val id: Int, val title: String // val empty: Boolean = false )

Slide 48

Slide 48 text

Original implementation around DataBase that caused problems in migration 1.Separating the processes related to display such as State from DB 2.Aggregate State to Fragment 3.Migrate BoundaryCallback and other data acquisition processes 4.Managing the State on the basis of the loadStateFlow of PagingDataAdapter

Slide 49

Slide 49 text

sealed class VH (view: View) : RecyclerView.ViewHolder(view) { class ItemViewHolder(view: View) : VH(view) class ZeroMatchViewHolder(view: View) :VH(view) }

Slide 50

Slide 50 text

Original implementation around DataBase that caused problems in migration 1.Separating the processes related to display such as State from DB 2.Aggregate State to Fragment 3.Migrate BoundaryCallback and other data acquisition processes 4.Managing the State on the basis of the loadStateFlow of PagingDataAdapter

Slide 51

Slide 51 text

class ItemAdapter() : PagedListAdapter(diffCallback) class ItemAdapter() : PagingDataAdapter(diffCallback) Original implementation around DataBase that caused problems in migration

Slide 52

Slide 52 text

Original implementation around DataBase that caused problems in migration 1.Separating the processes related to display such as State from DB 2.Aggregate State to Fragment 3.Migrate BoundaryCallback and other data acquisition processes 4.Managing the State on the basis of the loadStateFlow of PagingDataAdapter

Slide 53

Slide 53 text

itemAdapter.loadStateFlow.collectLatest { state -> when { state == LoadState.Loading -> binding.loading.visibility = VISIBLE … } } Original implementation around DataBase that caused problems in migration

Slide 54

Slide 54 text

itemAdapter.loadStateFlow.collectLatest { state -> when { state == LoadState.Loading -> binding.loading.visibility = VISIBLE … } } Original implementation around DataBase that caused problems in migration Perform corresponding processing based on the LoadState status

Slide 55

Slide 55 text

Knowledge gained in migration - Original implementation around DataBase that caused problems in migration - Fatal code that can occur easily but generates infinite API calls - Advantages and considerations for migrating State to LoadStateFlow

Slide 56

Slide 56 text

Advantages and Considerations for Migrating State to LoadStateFlow - Logic around State can be gathered into Fragment - As the loading information is provided by the officials, self-implementation is not required

Slide 57

Slide 57 text

Advantages and Considerations for Migrating State to LoadStateFlow - gathered into FragmentaIt becomes complicated when display is split on the basis of loading information of multiple APIs - LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 58

Slide 58 text

It becomes complicated when display is split on the basis of loading information of multiple APIs. NotLoading Loading Error CombinedLoadStates Refresh Append Prepend LoadState

Slide 59

Slide 59 text

It becomes complicated when display is split on the basis of loading information of multiple APIs. NotLoading Loading Error Refresh Append Prepend itemAdapter.loadStateFlow.first().refresh == LoadState.Loading CombinedLoadStates LoadState

Slide 60

Slide 60 text

It becomes complicated when display is split on the basis of loading information of multiple APIs. lifecycleScope.launch { itemAdapter.loadStateFlow.combine(otherAdapter.loadStateFlow) { item, other -> if (item.refresh == LoadState.Loading && other.refresh == LoadState.Loading) { binding.loading.visibility = VISIBLE } } }

Slide 61

Slide 61 text

itemAdapter.loadStateFlow.collectLatest { state -> Log.d(”LoadState”, ”${ state.toString() }” ) } LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 62

Slide 62 text

LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down 13:41:34:046 LoadState: LoadType.Loading 13:41:34:411 LoadState: LoadType.Loading 13:41:34:531 LoadState: LoadType.Loading 13:41:34:652 LoadState: LoadType.Loading 13:41:34:657 LoadState: LoadType.Loading ・ ・ ・

Slide 63

Slide 63 text

itemAdapter.loadStateFlow.map { it.refresh }.distinctUntilChanged().collectLatest { state -> Log.d(”LoadState”, ”${ state.toString() }” ) } LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 64

Slide 64 text

itemAdapter.loadStateFlow.map { it.refresh }.distinctUntilChanged().collectLatest { state -> Log.d(”LoadState”, ”${ state.toString() }” ) } LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 65

Slide 65 text

itemAdapter.loadStateFlow.map { it.refresh }.distinctUntilChanged().collectLatest { state -> Log.d(”LoadState”, ”${ state.toString() }” ) } LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 66

Slide 66 text

LoadState: LoadType.Loading LoadState: LoadType.NotLoading ・ ・ ・ ・ LoadState is flowing in large quantities, so it is necessary to devise a way to narrow it down

Slide 67

Slide 67 text

Summary - By migrating, you will obtain various benefits such as the benefits from new features, affinity with Coroutines Flow, provision of LoadState - We hope it will be useful in your projects! - I have introduced the knowledge gained in the current migration in PayPay Flea Market

Slide 68

Slide 68 text

Thank you