Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Androidx Paging Library with GraphQL
Search
Leon
September 04, 2019
Programming
10
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Androidx Paging Library with GraphQL
lightning talk about the new paging library
Leon
September 04, 2019
Other Decks in Programming
See All in Programming
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
240
Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)
sylph01
0
310
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
2
330
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
430
Next.js: The final boss of Web Performance
guaca
0
260
sbt 2.0: takeoff
eed3si9n
0
160
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
500
カツオ、ご期待ください
suneo3476
0
190
Agents on Rails - Rails at Scale 2026
irinanazarova
0
560
ChromeでローカルLLM
ryu1013
0
140
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
790
Featured
See All Featured
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
750
Chasing Engaging Ingredients in Design
codingconduct
0
360
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
630
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
520
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
330
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
The SEO identity crisis: Don't let AI make you average
varn
0
590
BBQ
matthewcrist
90
10k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
630
Ruling the World: When Life Gets Gamed
codingconduct
0
400
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Transcript
How we implemented the new paging library in Spontacts Leon
Füßner @leon_fuessner
What is paging? Loading data in small chunks
What we already had: - RecyclerView with ListAdapter - one
initial request when starting the fragment - MVVM Architecture with databinding enabled What we needed: - Paging functionality
Getting started Dependencies: "com.apollographql.apollo:apollo-runtime:$apollo_version" "com.apollographql.apollo:apollo-android-support:$apollo_version" "com.apollographql.apollo:apollo-coroutines-support:$apollo_version" "androidx.paging:paging-runtime:2.1.0" Paging: Apollo Client
for GraphQL Requests: apply plugin: 'com.apollographql.android'
Different ways of implementing
Components ‣ PagedList ‣ DataSource + DataSource.Factory ‣ PagedListAdapter ‣
LiveListPageBuilder ‣ ApolloClient for GraphQL (not required)
None
GraphQL Schema query GetAllGroups($cursor: String) { getAll(cursor: $cursor) { page
{ total nextCursor hasNextPage } groups { ...GroupField } } } fragment GroupField on Group { id title description category { id title } location { title } owner { id } }
DataSource abstract class GroupDataSource(val repository: GroupsRepository) : PageKeyedDataSource<String, Group>() {
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, Group>) { } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Group>) { } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Group>) { } companion object { const val PAGE_SIZE = 10 } }
DataSource abstract class GroupDataSource(val repository: GroupsRepository) : PageKeyedDataSource<String, Group>() {
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, Group>) { GlobalScope.launch(Dispatchers.IO) { try { val groups = fetchNetwork() callback.onResult(groups.first, null, groups.second) } catch(e: Exception) { Timber.e(e) } } } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Group>) { GlobalScope.launch(Dispatchers.IO) { try { val groups = fetchNetwork(params.key) callback.onResult(groups.first, groups.second) } catch (e: Exception) { Timber.e(e) } } } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Group>) { } abstract suspend fun fetchNetwork(cursor: String? = null): Pair<List<Group>, String?> companion object { const val PAGE_SIZE = 10 } }
DataSource class AllGroupsDataSource(repository: GroupsRepository) : GroupDataSource(repository) { override suspend fun
fetchNetwork(cursor: String?): Pair<List<Group>, String?> = repository.getAllGroups(cursor) } class GroupsRepository(private val apolloClient: ApolloClient) { suspend fun getAllGroups(cursor: String? = null): Pair<List<Group>, String?> { val query: GetAllGroupsQuery = GetAllGroupsQuery.builder().cursor(cursor).build() val response = apolloClient.query(query).toDeferred().await() return Pair(response.data()?.all?.groups()?.map { it.toGroup() }.orEmpty(), response.data()?.all?.page()?.nextCursor()) } } query GetAllGroups($cursor: String) { getAll(cursor: $cursor) { page { total nextCursor hasNextPage } groups { ...GroupField } } }
DataSource abstract class GroupDataSource(val repository: GroupsRepository) : PageKeyedDataSource<String, Group>() {
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, Group>) { GlobalScope.launch(Dispatchers.IO) { try { val groups = fetchNetwork() callback.onResult(groups.first, null, groups.second) } catch(e: Exception) { Timber.e(e) } } } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Group>) { GlobalScope.launch(Dispatchers.IO) { try { val groups = fetchNetwork(params.key) callback.onResult(groups.first, groups.second) } catch (e: Exception) { Timber.e(e) } } } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Group>) { } abstract suspend fun fetchNetwork(cursor: String? = null): Pair<List<Group>, String?> companion object { const val PAGE_SIZE = 10 } } List of Groups Cursor
DataSource.Factory class AllGroupDataSourceFactory(private val repository: GroupsRepository) : DataSource.Factory<String, Group>() {
override fun create(): DataSource<String, Group> = AllGroupsDataSource(repository) }
Groups ViewModel class GroupsViewModel(val repository: GroupsRepository) : ViewModel() { val
allGroupsDataSourceFactory = AllGroupDataSourceFactory(repository) private val pagedListConfig: PagedList.Config = PagedList.Config.Builder() .setEnablePlaceholders(false) .setPageSize(GroupDataSource.PAGE_SIZE).build() val groups: LiveData<PagedList<Group>> = LivePagedListBuilder(allGroupDataSourceFactory, pagedListConfig).build() }
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel"
type="com.conceptworks.spontacts.groups.overview.GroupsViewModel" /> <import type="com.conceptworks.spontacts.groups.Status" /> <import type="android.view.View" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/swipe_container" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/list" android:layout_width="0dp" android:layout_height="0dp" app:groupItems="@{viewModel.groups}" app:spanCount="2" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" tools:listitem="@layout/group_list_item" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Adapter: class GroupsAdapter : PagedListAdapter<Group, GroupsAdapter.GroupViewholder>(DIFF_UTIL) { override fun onCreateViewHolder(parent:
ViewGroup, viewType: Int): GroupViewholder = GroupViewholder( GroupListItemBinding.inflate( LayoutInflater.from(parent.context), parent, false ) ) override fun onBindViewHolder(holder: GroupViewholder, position: Int) { getItem(position)?.let { holder.bind(it) } } class GroupViewholder(private val binding: GroupListItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Group) { binding.group = item binding.executePendingBindings() } } companion object { val DIFF_UTIL = object : DiffUtil.ItemCallback<Group>() { override fun areItemsTheSame(oldItem: Group, newItem: Group) = oldItem == newItem override fun areContentsTheSame(oldItem: Group, newItem: Group) = oldItem == newItem } @JvmStatic @BindingAdapter("groupItems") fun RecyclerView.bindItems(items: PagedList<Group>?) { (adapter as GroupsAdapter).submitList(items) } }
Fragment: private val viewModel by sharedViewModel<GroupsViewModel>() private val adapter =
GroupsAdapter() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = OverviewGroupsFragmentBinding .inflate(inflater) .apply { lifecycleOwner = this@OverviewFragment viewModel =
[email protected]
{ groups.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) } setHasOptionsMenu(true) list.adapter = adapter } .root MVP
What to display while you’re waiting for the response?
DataSource.Factory class AllGroupDataSourceFactory(private val repository: GroupsRepository) : DataSource.Factory<String, Group>() {
val allGroupsSource = MutableLiveData<AllGroupsDataSource>() override fun create(): DataSource<String, Group> = AllGroupsDataSource(repository).apply { allGroupsSource.postValue(this) } } abstract class GroupDataSource(val repository: GroupsRepository) : PageKeyedDataSource<String, Group>() { val loadingStatus = MutableLiveData<Status>() override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, Group>) { GlobalScope.launch(Dispatchers.IO) { try { loadingStatus.postValue(Status.Loading) val groups = fetchNetwork() callback.onResult(groups.first, null, groups.second) loadingStatus.postValue(Status.Success) } catch(e: Exception) { Timber.e(e) loadingStatus.postValue(Status.Error) } } } DataSource
<ProgressBar android:layout_width="0dp" android:layout_height="wrap_content" android:indeterminate="true" android:visibility="@{viewModel.status instanceof Status.Loading ? View.VISIBLE :
View.GONE}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> XML val status: LiveData<Status> = Transformations.switchMap(allGroupDataSourceFactory.allGroupsSource) { it.loadingStatus } ViewModel
What if the data is being changed? fun refresh() {
allGroupDataSourceFactory.allGroupsSource.value?.invalidate() }
Resources that I used: https://www.raywenderlich.com/6948-paging-library-for-android-with-kotlin-creating-infinite-lists https://codelabs.developers.google.com/codelabs/android-paging https://developer.android.com/topic/libraries/architecture/paging
Thanks for listening! Questions?