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
670
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
410
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.4k
原理から完全理解するDagger Hilt Migration
kkagurazaka
1
1.9k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6.2k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
1k
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
900
CQRS Architecture on Android
kkagurazaka
7
3k
suspending functionの裏側
kkagurazaka
3
450
async/awaitで快適非同期ライフ
kkagurazaka
4
1.9k
Other Decks in Programming
See All in Programming
GitHub Actions × AWS OIDC連携の仕組みと経緯を理解する
ota1022
0
250
理論と実務のギャップを超える
eycjur
0
120
Go言語の特性を活かした公式MCP SDKの設計
hond0413
1
210
iOSエンジニア向けの英語学習アプリを作る!
yukawashouhei
0
190
Building, Deploying, and Monitoring Ruby Web Applications with Falcon (Kaigi on Rails 2025)
ioquatix
4
1.8k
Pull-Requestの内容を1クリックで動作確認可能にするワークフロー
natmark
2
480
CSC509 Lecture 02
javiergs
PRO
0
410
ポスターセッション: 「まっすぐ行って、右!」って言ってラズパイカーを動かしたい 〜生成AI × Raspberry Pi Pico × Gradioの試作メモ〜
komofr
0
1.2k
Back to the Future: Let me tell you about the ACP protocol
terhechte
0
140
CSC305 Lecture 03
javiergs
PRO
0
240
Pythonスレッドとは結局何なのか? CPython実装から見るNoGIL時代の変化
curekoshimizu
5
1.7k
デミカツ切り抜きで面倒くさいことはPythonにやらせよう
aokswork3
0
220
Featured
See All Featured
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
358
30k
The Cost Of JavaScript in 2023
addyosmani
53
9k
KATA
mclloyd
32
15k
Unsuck your backbone
ammeep
671
58k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.6k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.7k
Learning to Love Humans: Emotional Interface Design
aarron
274
41k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
2.7k
Context Engineering - Making Every Token Count
addyosmani
5
220
How GitHub (no longer) Works
holman
315
140k
Balancing Empowerment & Direction
lara
4
680
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
970
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!