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
DataStoreを導入してみた
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
kobaken
April 20, 2023
Technology
390
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
DataStoreを導入してみた
kobaken
April 20, 2023
More Decks by kobaken
See All by kobaken
複数行のTextで中間省略(…)を実現する
kobaken0029
0
71
Jetpack Compose Preview実践ガイド
kobaken0029
0
150
Serializable / Parcelableとの上手な付き合い方
kobaken0029
0
190
Kotlinの好きなところ
kobaken0029
0
1.4k
Compose駆動開発のためのマルチモジュール化
kobaken0029
0
260
Epoxyを用いたレイアウト構築術
kobaken0029
1
260
Androidエンジニアが1週間でiOSアプリ開発を学び、1ヶ月で大規模アプリ開発にJOINした話
kobaken0029
0
3.9k
Modern REST Communicate for Android
kobaken0029
0
1.6k
AndroidでモダンREST通信してみたった
kobaken0029
0
280
Other Decks in Technology
See All in Technology
「休む」重要さ
smt7174
6
1.6k
そのドキュメント、自動化しませんか?
yuksew
1
410
どこまでAIに任せるか 〜確率論と決定論の境界決定〜
shukob
0
450
Webの技術とガジェットで子どもも大人も楽しめるワクワク体験を提供する / Qiita Tech Festa Day 2026
you
PRO
1
130
PHPで作って学ぶリアルタイム音声対話AIとWebSocket入門 by ムナカタ
munakata
0
130
Type-safe IaC for Dart
coborinai
0
180
20260720_クラウド女子会×PyLadiesTokyoコラボ Amazon Bedrock ハンズオン用資料
yuuka51
1
110
AmplifyHostingConstructからSSRフレームワークのためのホスティング設計を考察する/amplify-hosting-construct
fossamagna
1
300
なぜ、あなたのエージェントは言うことを聞かないのか
segavvy
1
370
クラウドを使う側から、作る側へ / 大吉祥寺.pm 2026前夜祭
fujiwara3
5
1.2k
「顧客の声を聞かなければ何も始まらない」 ── 顧客の声から生まれた『AI返信補助機能』の開発プロセス / AICon2026_shikata_imai
rakus_dev
1
270
探索・可視化・自動化を一本化 Amazon Quickでデータ活用スピードを上げる方法
koheiyoshikawa
0
170
Featured
See All Featured
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.4k
How to Think Like a Performance Engineer
csswizardry
28
2.7k
Faster Mobile Websites
deanohume
310
32k
The SEO Collaboration Effect
kristinabergwall1
1
510
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
280
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
330
Marketing to machines
jonoalderson
1
5.6k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
590
How GitHub (no longer) Works
holman
316
150k
Transcript
@kobaken DataStoreΛಋೖͯ͠Έͨ
ࣗݾհ • kobakenʢ@koba_dog_ʣ • pixivࣄۀຊ෦ϚϯΨ෦ • AndroidΞϓϦΛͭͬͯ͘·͢ • ࠷ۙϙέΧͱϫϯϐʔεΧʔυ͕͍ʂ
ͱ͜ΖͰΈͳ͞Μ
pixivίϛοΫͬͯ͝ଘͰ͔͢ʁ
ίϛοΫΛઆ໌Ͱ͖Δ ͳΜ͔͍͍ײ͡ͷը૾
ΈͯͶʂ https://inside.pixiv.blog/2023/01/25/120000
ؓٳ
DataStoreͱ
DataStoreͱ • σʔλΛϩʔΧϧʹӬଓԽͰ͖ΔσʔλετϨʔδιϦϡʔγϣϯ • Android Jetpackʹؚ·Ε͍ͯΔϥΠϒϥϦ • 20209݄ʹ1.0.0-alpha1͕ެ։͞Εͨ • ݱࡏ҆ఆ൛ͱͯ͠1.0.0͕ϦϦʔε͞Ε͍ͯΔ
DataStoreͷಛ • Kotlin CoroutinesʹରԠ͍ͯ͠Δ • খنͰ୯७ͳσʔληοτʹద͍ͯ͠Δ • Preferences DataStoreͱProto DataStoreͷ2छྨ͕ଘࡏ͍ͯ͠Δ
• SharedPreferences͔ΒͷҠߦ͕Մೳ
Kotlin CoroutinesʹରԠ͍ͯ͠Δ • σʔλͷऔಘʹ fl ow • σʔλͷอଘʹsuspend
CoroutinesରԠ͞Ε͍ͯΔ༷ࢠ public interface DataStore<T> { public val data: Flow<T> public
suspend fun updateData(transform: suspend (t: T) -> T): T }
খنͰ୯७ͳσʔληοτʹ࠷దԽ • ୯७ͳσʔλͷΈΛӬଓԽ͢Δ͜ͱ • ෦ߋ৽ࢀর߹ੑΛαϙʔτ͍ͯ͠ͳ͍ • େنɾෳࡶͳσʔληοτ෦ߋ৽ɺࢀর߹ੑΛαϙʔτ͍ͨ͠ ߹RoomΛ͍·͠ΐ͏
2छྨͷDataStore • Key-ValueϖΞΛѻ͏Preferences DataStore • SharedPreferencesͷସ • ܕ͖ΦϒδΣΫτΛѻ͏Proto DataStore •
Protocol bufferΛ༻ͯ͠εΩʔϚఆٛΛ͢Δ • ܕ҆શ
SharedPreferences
SharedPreferences vs DataStore https://medium.com/androiddevelopers/introduction-to-jetpack-datastore-3dc8d74139e7
SharedPreferences͔ΒҠߦ͢ΔϝϦοτ • ඇಉظAPI͕αϙʔτ͞Ε͍ͯΔͨΊɺUIϒϩοΩϯάʹΑΔδϟϯΫ ANRͷൃੜϦεΫ͕ܰݮ͢Δ • σʔλͷҰ؏ੑɾ߹ੑ͕औΕ͍ͯΔ • ϚΠάϨʔγϣϯʹରԠ͍ͯ͠Δ
Google→։ൃऀ • ʮSharedPreferencesΛ༻ͯ͠σʔλΛอଘ͍ͯ͠Δ߹ɺ DataStoreʹҠߦ͢Δ͜ͱΛݕ౼͍ͯͩ͘͠͞ɻʯ https://developer.android.com/topic/libraries/architecture/datastore
ಋೖͯ͠ΈΔ
Preferences DataStore
ґଘؔͷՃ dependencies { implementation "androidx.datastore:datastore-preferences:1.0.0" }
Preferences DataStoreΛ࡞ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = “settings")
Preferences DataStoreΛ࡞ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = “settings") ⚠1ϑΝΠϧʹରͯ͠DataStoreͷΠϯελϯεγϯάϧτϯʹ͢Δ͜ͱ⚠
→ಉϓϩηε্ʹॴఆϑΝΠϧʹରͯ͠ΞΫςΟϒͳDataStore͕ෳ͋Δͱ σʔλʹ৮ΕΔࡍʹIllegalStateExceptionΛεϩʔ͢Δ
Preferences DataStoreΛ࡞ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = “settings")
DataStoreͷinterfaceʢ࠶ܝʣ public interface DataStore<T> { public val data: Flow<T> public
suspend fun updateData(transform: suspend (t: T) -> T): T }
ಡΈऔΓ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") val comicUserIdsFlow: Flow<Set<String>> = context.dataStore.data
.map { preferences -> preferences[COMIC_USER_ID] ?: emptySet() }
ಡΈऔΓ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") val comicUserIdsFlow: Flow<Set<String>> = context.dataStore.data
.map { preferences -> preferences[COMIC_USER_ID] ?: emptySet() } booleanPreferencesKey intPreferencesKey longPreferencesKey fl oatPreferencesKey stringPreferencesKey
ॻ͖ࠐΈ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") context.dataStore.edit { preferences -> preferences[COMIC_USER_IDS]
= newUserIds }
SharedPreferences→DataStoreͷҠߦ const val PREFERENCES_NAME = "..." val Context.dataStore by preferencesDataStore(
name = PREFERENCES_NAME, produceMigrations = { context -> SharedPreferencesMigration( context = context, sharedPreferencesName = PREFERENCES_NAME, // ࢦఆͨ͠keyͷΈҠߦͰ͖ΔʂʢσϑΥϧτҾMIGRATE_ALL_KEYSʣ keysToMigrate = setOf(Key.COMIC_USER_IDS) ) } )
SharedPreferences→DataStoreͷҠߦ const val PREFERENCES_NAME = "..." val Context.dataStore by preferencesDataStore(
name = PREFERENCES_NAME, produceMigrations = { context -> SharedPreferencesMigration( context = context, sharedPreferencesName = PREFERENCES_NAME, // ࢦఆͨ͠keyͷΈҠߦͰ͖ΔʂʢσϑΥϧτҾMIGRATE_ALL_KEYSʣ keysToMigrate = setOf(Key.COMIC_USER_IDS) ) } )
pixivίϛοΫͰͷӡ༻ํ๏
pixivίϛοΫͰͷํ • ৽ن࣮ͰੵۃతʹPreferences DataStoreΛ࠾༻͢Δ • طଘͷSharedPreferencesख͕ۭ͍ͨͱ͖ʹҠߦΛݕ౼ • 1िؒͷ10%Λ෦্࣭ʹࣗ༝ʹͯΒΕΔʢ10%ϧʔϧʣ
pixivίϛοΫͰͷӡ༻ • Preferences DataStoreΛѻ͏interfaceΛఆٛ • ӬଓԽ͍ͨ͠σʔλ͕͋ΕͦͷinterfaceΛܧঝ࣮ͯ͢͠Δ
DataStoreͷΠϯελϯεΛऔಘͰ͖ΔΑ͏ʹ͢Δ private const val PREFERENCES_NAME = "......" val Context.dataStore by
preferencesDataStore( name = PREFERENCES_NAME )
γϯάϧτϯͳDataStoreΛѻ͑ΔinterfaceΛఆٛ interface Preference { val context: Context val dataStore: DataStore<Preferences>
get() = context.dataStore }
ѻ͏σʔλʹԠͯ͡DataStoreͷ࣮Λ͢Δ interface ComicUserIdsPreference : Preference<Set<String>> { val userIds: Flow<Set<String>> suspend
fun add(id: String) suspend fun remove(id: String) }
ѻ͏σʔλʹ Ԡͯ͡DataStore ͷ࣮Λॻ͘ class ComicUserIdsPreferenceImpl @Inject( override val context: Context
) : ComicUserIdsPreference { override val userIds: Flow<Set<String>> get() = dataStore.data .catch { if (exception is IOException) { emit(emptyPreferences()) } else { throw exception } } .map { it[PreferencesKeys.ComicUserIds.key] ?: emptySet() } override suspend fun add(id: String) { dataStore.edit { it[PreferencesKeys.ComicUserIds.key] = it[PreferencesKeys.ComicUserIds.key].plus(id).toSet() } } override suspend fun remove(id: String) { dataStore.edit { it[PreferencesKeys.ComicUserIds.key] = it[PreferencesKeys.ComicUserIds.key].minus(id).toSet() } } } Dagger2ͰDIͯ͠·͢ ʢhilitҠߦ͍ͨ͠……ʣ
KeyΛཧ͢Δ sealed class PreferencesKeys<T>(val key: Preferences.Key<T>) { object ComicUserIds :
PreferencesKeys<Set<String>>(stringSetPreferencesKey("comic_user_ids")) ... } class PreferenceTest { @Test fun checkDuplicatedKeys() { val subClasses = PreferencesKeys::class.sealedSubclasses val nonDuplicatedKeys = subClasses.map { it.objectInstance?.key?.name }.toSet() Truth.assertThat(nonDuplicatedKeys.count()).isEqualTo(subClasses.count()) } } KeyͷॏෳΛ͙ͨΊʹsealed classͰఆٛ ॏෳνΣοΫͷςετ͔ͬ͠Γ࣮ࢪ
ಉظͰΛऔಘͨ͘͠ͳͬͨέʔε
ΞϓϦىಈ࣌ͷςʔϚө
ઃఆ͕ө͞ΕΔલʹભҠͯ͠͠·͍ ҙਤ͠ͳ͍ςʔϚ͕ͪΒ͍ͭͯ͠·͏
Կ͕ى͍ͬͯ͜Δͷ͔ • ΞϓϦىಈ࣌ʹApplicationΫϥεͰςʔϚઃఆΛऔಘͯ͠ద༻͍ͯ͠Δ • ςʔϚઃఆPreferences DataStoreͰӬଓԽ͍ͯ͠Δ • DataStoreಉظॲཧʹରԠ͍ͯ͠ͳ͍ • ΞϓϦىಈˠϗʔϜը໘ͷը໘ભҠʹςʔϚͷө͕ؒʹ߹Θͣɺσ
ϑΥϧτͷςʔϚ͕ө͞ΕΔ • ͦͷޙɺඇಉظʹө͞ΕͨςʔϚઃఆ͕ద༻͞Εͨ
Կ͕ى͍ͬͯ͜Δͷ͔ • ΞϓϦىಈ࣌ʹApplicationΫϥεͰςʔϚઃఆΛऔಘͯ͠ద༻͍ͯ͠Δ • ςʔϚઃఆPreferences DataStoreͰӬଓԽ͍ͯ͠Δ • DataStoreಉظॲཧʹରԠ͍ͯ͠ͳ͍ • ΞϓϦىಈˠϗʔϜը໘ͷը໘ભҠʹςʔϚͷө͕ؒʹ߹Θͣɺσ
ϑΥϧτͷςʔϚ͕ө͞ΕΔ • ͦͷޙɺඇಉظʹө͞ΕͨςʔϚઃఆ͕ద༻͞Εͨ
ղܾࡦ • DataStore͔ΒΛऔಘ͢ΔͷΛಉظॲཧʹ͢Δ • runBlockingͱFlow# fi rstΛۦ͢Δ • SharedPreferencesΛ͏
DataStoreͰಉظॲཧ͢Δ // ςʔϚͷద༻͕ྃ͢ΔͷΛͭͨΊʹrunBlockingͰॲཧ͢Δ runBlocking { initTheme() } private suspend fun
initTheme() { themeSettingPreference.currentThemeStatus.first().let { AppCompatDelegate.setDefaultNightMode( it.getAppTheme().convertNightModeParameter() ) } }
runBlocking͢Δͱ͖ͷҙࣄ߲ • લఏͱͯ͠ਪ͞ΕΔํ๏Ͱͳ͍ • ຖճϑΝΠϧͷI/O͕Δͱ͍͏Θ͚Ͱͳ͍ͷͰɺͦΕΛཧղͯ͠ར༻͢ΔͷΞϦ • UIεϨουΛϒϩοΩϯά͢ΔͷͰɺδϟϯΫANRͷՄೳੑ͋Δ • ैདྷͷSharedPreferencesͱಉ͡ •
ෳՕॴͰݺͼग़͢߹ࣄલʹϓϦϩʔυΛ͓ͯ͘͠ͷ͕ྑ͍ • 2ճҎ߱ͷݺͼग़͠ͰϝϞϦΩϟογϡʹώοτ͢Δ • Activity#onCreateͱ͔ͰݺͿͱΑ͍
·ͱΊ
·ͱΊ • DataStoreͱ৽͍͠ϩʔΧϧετϨʔδιϦϡʔγϣϯͰ͋Δ • Kotlin Coroutines/FlowʹରԠ͍ͯ͠Δ • SharedPreferences͔ΒͷϚΠάϨʔγϣϯՄೳ • ಉظॲཧ͕ඞཁʹͳͬͨΒrunBlocking͢Δ
͋Γ͕ͱ͏͍͟͝·ͨ͠🙇
ࢀߟจݙ • ެࣜυΩϡϝϯτΨΠυ • Introduction to Jetpack DataStore • ϓϩμΫτͰ҆શʹDataStoreҠߦ͢Δ
• SharedPreferencesɺDataStoreҠߦͯ͠ΈΑ͏