Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
coroutinesで非同期ページネーション
Search
Keita Kagurazaka
June 29, 2017
Programming
1
650
coroutinesで非同期ページネーション
2017/06/29に開催された第6回Kotlin勉強会 @ Sansanの発表スライドです。
Keita Kagurazaka
June 29, 2017
Tweet
Share
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
SELECT FOR UPDATEの話
kkagurazaka
0
390
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.4k
原理から完全理解するDagger Hilt Migration
kkagurazaka
1
1.9k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6.1k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
980
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
890
CQRS Architecture on Android
kkagurazaka
7
3k
suspending functionの裏側
kkagurazaka
3
440
async/awaitで快適非同期ライフ
kkagurazaka
4
1.8k
Other Decks in Programming
See All in Programming
Advanced Micro Frontends: Multi Version/ Framework Scenarios
manfredsteyer
PRO
0
130
顧客の画像データをテラバイト単位で配信する 画像サーバを WebP にした際に起こった課題と その対応策 ~継続的な取り組みを添えて~
takutakahashi
4
1.4k
Jakarta EE Meets AI
ivargrimstad
0
530
ご注文の差分はこちらですか? 〜 AWS CDK のいろいろな差分検出と安全なデプロイ
konokenj
4
730
JetBrainsのAI機能の紹介 #jjug
yusuke
0
160
What's new in Adaptive Android development
fornewid
0
130
PHPUnitの限界をPlaywrightで補完するテストアプローチ
yuzneri
0
360
0から始めるモジュラーモノリス-クリーンなモノリスを目指して
sushi0120
0
250
Amazon Q CLI開発で学んだAIコーディングツールの使い方
licux
3
140
DatadogのArchived LogsをSnowflakeで高速に検索する方法(Archive Searchでオワコンにならないことを祈って) / How to search Datadog Archived Logs quickly with Snowflake (hoping Datadog Archive Search doesn’t make this obsolete)
civitaspo
0
100
PHPカンファレンス関西2025 基調講演
sugimotokei
6
1k
202507_ADKで始めるエージェント開発の基本 〜デモを通じて紹介〜(奥田りさ)The Basics of Agent Development with ADK — A Demo-Focused Introduction
risatube
PRO
6
1.3k
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
40
1.9k
The Power of CSS Pseudo Elements
geoffreycrofte
77
5.9k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
Why You Should Never Use an ORM
jnunemaker
PRO
58
9.5k
We Have a Design System, Now What?
morganepeng
53
7.7k
Optimizing for Happiness
mojombo
379
70k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
A Modern Web Designer's Workflow
chriscoyier
695
190k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
1k
Facilitating Awesome Meetings
lara
54
6.5k
Transcript
coroutinesで 非同期ページネーション 2017/06/29 第6回 Kotlin勉強会@Sansan Keita Kagurazaka
こんな実装、経験ありませんか?
1. APIやDBからLIMIT分だけデータを読み込み 2. 読み込んだデータをUIに一覧表示 3. 下までスクロールしていくと次のページを読み込み
いわゆる無限スクロール 1. APIやDBからLIMIT分だけデータを読み込み 2. 読み込んだデータをUIに一覧表示 3. 下までスクロールしていくと次のページを読み込み
無限スクロールの面倒Point • 現在のページ番号などの管理を呼び出し側が行わなければ ならない • 下までスクロールしたら次を呼ぶというボイラーテンプレートな ScrollListenerが必要 次のアイテムを要求したらいい感じに 読み込んで返してくれないものか
それ、coroutinesでできるよ
自己紹介 • アカウント ◦ Twitter: @kkagurazaka ◦ Github: k-kagurazaka •
Sansan Android developer • Kotlin Love! RxProperty for Android https://github.com/k-kagurazaka/rx-property-android AsyncPermissions https://github.com/k-kagurazaka/async-permissions
本題の前にcoroutinesの復習
coroutines (コルーチン) とは • 中断・再開可能な関数のようなもの ◦ Threadクラスのように作成して、実行する • 中断している間はスレッドをブロックしない ◦
non-blocking • 特定のスレッドに紐付かない ◦ 中断前と再開後で別スレッドで動かせる • 中断時・完了時に値を返せる
コルーチンの作成 ビルダー launch async<T> 戻り値 Job Deferred<T> 使い方 val job
= launch(CommonPool) { // 処理 } job.join() val deferred = async(CommonPool) { // T型を返す処理 } val result = deferred.await() 説明 値を返さないコルーチンを作成するビル ダー。 Jobをjoinすると完了まで中断、 cancelで キャンセルできる。 T型の値を返すコルーチンを作成するビル ダー。 Deferred<T>をawaitすると完了まで中断、 cancelでキャンセルできる。
中断点 = suspending function / lambda • suspending function ◦
suspend fun hoge() { } • suspending lambda ◦ val lambda: suspend () -> Unit = { } • suspending function / lambda は suspending function / lambda からしか呼べない
スレッドの切り替え suspend fun <T> run(context: CoroutineContext, block: suspend () ->
T): T • 使い方 suspend fun loadItems() { val items = run(CommonPool) { getItems() } run(UI) { itemList.addAll(items) } } suspend fun taskAonUI() = run(UI) { // 処理 }
コルーチンの中断と再開例 // コルーチン作成&開始 launch(CommonPool) { val hoge = ... taskAonUI()
// UIスレッドでtaskAを実行中はこのコルーチンは中断 val huga = … // taskAが完了するとここからコルーチンを再開 } 中断している間は、コルーチンはスレッドをブロックしない • taskAの実施中に別のコルーチンがスレッドを使える • taskA完了後もまだ使われてたら別のスレッドで再開する
複数の値を扱いたいときは?
Channel
Channelとは • ブロックの代わりに中断するBlockingQueue • suspend fun send(value: T) ◦ Channelのcapacityに空きがあれば送信
◦ なければ空くまで中断 • suspend fun receive(): T ◦ Channelに値があれば受信 ◦ なければ値が来るまで中断 capacityが0の場合はsendとreceiveが揃ったら送受信
コルーチン間でのデータ受け渡し val channel = Channel<Int>() // デフォルトではcapacity = 0のチャンネル launch(CommonPool)
{ repeat(5) { println(channel.receive()) } // 5回受信 } launch(CommonPool) { repeat(10) { channel.send(it) } // 0から4まで送信、5の送信時に中断 }
ようやく本題
Channelを使って ページネーションを実現する
suspend fun getBooks(page: Int, limit: Int): List<Book> // APIやらDBやらから取得
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ // 受信 (receive) 専用 Channel val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() // capacity = 0 の Channel 作成 launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { // スレッドプールで動くコルーチン起動 var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel // 作ったチャンネルを返す }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { // 無限ループで val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) // ページ数をincrementしながら取得 if (books.isEmpty()) break books.forEach { channel.send(it) } if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } // 取得したものを Channel に送信 if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break // 空か books.forEach { channel.send(it) } if (books.size < LIMIT) break // LIMIT より少なかったら無限ループから抜ける } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
{ val channel = Channel<Book>() launch(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { channel.send(it) } // 受信されないかぎりここで中断 if (books.size < LIMIT) break } channel.close() } return channel }
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
= produce(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { send(it) } if (books.size < LIMIT) break } } 簡単に書けるメソッドもあるよ!
suspend fun getBooks(page: Int, limit: Int): List<Book> fun loadBooks(): ReceiveChannel<Book>
= produce(CommonPool) { var page = 0 while (true) { val books = getBooks(page++, LIMIT) if (books.isEmpty()) break books.forEach { send(it) } if (books.size < LIMIT) break } } // 20個読み込む (LIMIT < 20でも問題なし) repeat(20) { val book = channel.receiveOrNull() ?: return@repeat }
ページネーションを意識せずに 必要な数だけ要求できる!
まとめ • coroutineは中断可能なThreadのようなもの • 中断中はスレッドをブロックしない • coroutine間で値をやりとりするときはChannelを使う • ページネーションを抽象化できたり、応用は様々 ◦
RxJavaのdebounceとかも書けるよ!
Thanks!