Slide 1

Slide 1 text

@kobaken DataStoreΛಋೖͯ͠Έͨ

Slide 2

Slide 2 text

ࣗݾ঺հ • kobakenʢ@koba_dog_ʣ • pixivࣄۀຊ෦ϚϯΨ෦ • AndroidΞϓϦΛͭͬͯ͘·͢ • ࠷ۙ͸ϙέΧͱϫϯϐʔεΧʔυ͕೤͍ʂ

Slide 3

Slide 3 text

ͱ͜ΖͰΈͳ͞Μ

Slide 4

Slide 4 text

pixivίϛοΫͬͯ͝ଘ஌Ͱ͔͢ʁ

Slide 5

Slide 5 text

ίϛοΫΛઆ໌Ͱ͖Δ ͳΜ͔͍͍ײ͡ͷը૾

Slide 6

Slide 6 text

ΈͯͶʂ https://inside.pixiv.blog/2023/01/25/120000

Slide 7

Slide 7 text

ؓ࿩ٳ୊

Slide 8

Slide 8 text

DataStoreͱ͸

Slide 9

Slide 9 text

DataStoreͱ͸ • σʔλΛϩʔΧϧʹӬଓԽͰ͖ΔσʔλετϨʔδιϦϡʔγϣϯ • Android Jetpackʹؚ·Ε͍ͯΔϥΠϒϥϦ • 2020೥9݄ʹ1.0.0-alpha1͕ެ։͞Εͨ • ݱࡏ͸҆ఆ൛ͱͯ͠1.0.0͕ϦϦʔε͞Ε͍ͯΔ

Slide 10

Slide 10 text

DataStoreͷಛ௃ • Kotlin CoroutinesʹରԠ͍ͯ͠Δ • খن໛Ͱ୯७ͳσʔληοτʹద͍ͯ͠Δ • Preferences DataStoreͱProto DataStoreͷ2छྨ͕ଘࡏ͍ͯ͠Δ • SharedPreferences͔ΒͷҠߦ͕Մೳ

Slide 11

Slide 11 text

Kotlin CoroutinesʹରԠ͍ͯ͠Δ • σʔλͷऔಘʹ͸ fl ow • σʔλͷอଘʹ͸suspend

Slide 12

Slide 12 text

CoroutinesରԠ͞Ε͍ͯΔ༷ࢠ public interface DataStore { public val data: Flow public suspend fun updateData(transform: suspend (t: T) -> T): T }

Slide 13

Slide 13 text

খن໛Ͱ୯७ͳσʔληοτʹ࠷దԽ • ୯७ͳσʔλͷΈΛӬଓԽ͢Δ͜ͱ • ෦෼ߋ৽΍ࢀর੔߹ੑΛαϙʔτ͍ͯ͠ͳ͍ • େن໛ɾෳࡶͳσʔληοτ΍෦෼ߋ৽ɺࢀর੔߹ੑΛαϙʔτ͍ͨ͠ ৔߹͸RoomΛ࢖͍·͠ΐ͏

Slide 14

Slide 14 text

2छྨͷDataStore • Key-ValueϖΞΛѻ͏Preferences DataStore • SharedPreferencesͷ୅ସ • ܕ෇͖ΦϒδΣΫτΛѻ͏Proto DataStore • Protocol bufferΛ࢖༻ͯ͠εΩʔϚఆٛΛ͢Δ • ܕ҆શ

Slide 15

Slide 15 text

SharedPreferences

Slide 16

Slide 16 text

SharedPreferences vs DataStore 
 https://medium.com/androiddevelopers/introduction-to-jetpack-datastore-3dc8d74139e7

Slide 17

Slide 17 text

SharedPreferences͔ΒҠߦ͢ΔϝϦοτ • ඇಉظAPI͕αϙʔτ͞Ε͍ͯΔͨΊɺUIϒϩοΩϯάʹΑΔδϟϯΫ΍ ANRͷൃੜϦεΫ͕ܰݮ͢Δ • σʔλͷҰ؏ੑɾ੔߹ੑ͕औΕ͍ͯΔ • ϚΠάϨʔγϣϯʹରԠ͍ͯ͠Δ

Slide 18

Slide 18 text

Google→։ൃऀ΁ • ʮSharedPreferencesΛ࢖༻ͯ͠σʔλΛอଘ͍ͯ͠Δ৔߹͸ɺ DataStoreʹҠߦ͢Δ͜ͱΛݕ౼͍ͯͩ͘͠͞ɻʯ https://developer.android.com/topic/libraries/architecture/datastore

Slide 19

Slide 19 text

ಋೖͯ͠ΈΔ

Slide 20

Slide 20 text

Preferences DataStore

Slide 21

Slide 21 text

ґଘؔ܎ͷ௥Ճ dependencies { implementation "androidx.datastore:datastore-preferences:1.0.0" }

Slide 22

Slide 22 text

Preferences DataStoreΛ࡞੒ val Context.dataStore: DataStore by preferencesDataStore(name = “settings")

Slide 23

Slide 23 text

Preferences DataStoreΛ࡞੒ val Context.dataStore: DataStore by preferencesDataStore(name = “settings") ⚠1ϑΝΠϧʹରͯ͠DataStoreͷΠϯελϯε͸γϯάϧτϯʹ͢Δ͜ͱ⚠ →ಉϓϩηε্ʹॴఆϑΝΠϧʹରͯ͠ΞΫςΟϒͳDataStore͕ෳ਺͋Δͱ σʔλʹ৮ΕΔࡍʹIllegalStateExceptionΛεϩʔ͢Δ

Slide 24

Slide 24 text

Preferences DataStoreΛ࡞੒ val Context.dataStore: DataStore by preferencesDataStore(name = “settings")

Slide 25

Slide 25 text

DataStoreͷinterfaceʢ࠶ܝʣ public interface DataStore { public val data: Flow public suspend fun updateData(transform: suspend (t: T) -> T): T }

Slide 26

Slide 26 text

ಡΈऔΓ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") val comicUserIdsFlow: Flow> = context.dataStore.data .map { preferences -> preferences[COMIC_USER_ID] ?: emptySet() }

Slide 27

Slide 27 text

ಡΈऔΓ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") val comicUserIdsFlow: Flow> = context.dataStore.data .map { preferences -> preferences[COMIC_USER_ID] ?: emptySet() } booleanPreferencesKey 
 intPreferencesKey longPreferencesKey fl oatPreferencesKey 
 stringPreferencesKey

Slide 28

Slide 28 text

ॻ͖ࠐΈ val COMIC_USER_IDS = stringSetPreferencesKey("comic_user_ids") context.dataStore.edit { preferences -> preferences[COMIC_USER_IDS] = newUserIds }

Slide 29

Slide 29 text

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) ) } )

Slide 30

Slide 30 text

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) ) } )

Slide 31

Slide 31 text

pixivίϛοΫͰͷӡ༻ํ๏

Slide 32

Slide 32 text

pixivίϛοΫͰͷํ਑ • ৽ن࣮૷Ͱ͸ੵۃతʹPreferences DataStoreΛ࠾༻͢Δ • طଘͷSharedPreferences͸ख͕ۭ͍ͨͱ͖ʹҠߦΛݕ౼ • 1िؒͷ10%Λ಺෦඼࣭޲্ʹࣗ༝ʹ౰ͯΒΕΔʢ10%ϧʔϧʣ

Slide 33

Slide 33 text

pixivίϛοΫͰͷӡ༻ • Preferences DataStoreΛѻ͏interfaceΛఆٛ • ӬଓԽ͍ͨ͠σʔλ͕͋Ε͹ͦͷinterfaceΛܧঝ࣮ͯ͠૷͢Δ

Slide 34

Slide 34 text

DataStoreͷΠϯελϯεΛऔಘͰ͖ΔΑ͏ʹ͢Δ private const val PREFERENCES_NAME = "......" val Context.dataStore by preferencesDataStore( name = PREFERENCES_NAME )

Slide 35

Slide 35 text

γϯάϧτϯͳDataStoreΛѻ͑ΔinterfaceΛఆٛ interface Preference { val context: Context val dataStore: DataStore get() = context.dataStore }

Slide 36

Slide 36 text

ѻ͏σʔλʹԠͯ͡DataStoreͷ࣮૷Λ͢Δ interface ComicUserIdsPreference : Preference> { val userIds: Flow> suspend fun add(id: String) suspend fun remove(id: String) }

Slide 37

Slide 37 text

ѻ͏σʔλʹ Ԡͯ͡DataStore ͷ࣮૷Λॻ͘ class ComicUserIdsPreferenceImpl @Inject( override val context: Context ) : ComicUserIdsPreference { override val userIds: Flow> 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Ҡߦ͍ͨ͠……ʣ

Slide 38

Slide 38 text

KeyΛ؅ཧ͢Δ sealed class PreferencesKeys(val key: Preferences.Key) { object ComicUserIds : PreferencesKeys>(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Ͱఆٛ ॏෳνΣοΫͷςετ΋͔ͬ͠Γ࣮ࢪ

Slide 39

Slide 39 text

ಉظͰ஋Λऔಘͨ͘͠ͳͬͨέʔε

Slide 40

Slide 40 text

ΞϓϦىಈ࣌ͷςʔϚ൓ө

Slide 41

Slide 41 text

ઃఆ͕൓ө͞ΕΔલʹભҠͯ͠͠·͍ ҙਤ͠ͳ͍ςʔϚ͕ͪΒ͍ͭͯ͠·͏

Slide 42

Slide 42 text

Կ͕ى͍ͬͯ͜Δͷ͔ • ΞϓϦىಈ࣌ʹApplicationΫϥεͰςʔϚઃఆΛऔಘͯ͠ద༻͍ͯ͠Δ • ςʔϚઃఆ͸Preferences DataStoreͰӬଓԽ͍ͯ͠Δ • DataStore͸ಉظॲཧʹରԠ͍ͯ͠ͳ͍ • ΞϓϦىಈˠϗʔϜը໘΁ͷը໘ભҠʹςʔϚͷ൓ө͕ؒʹ߹Θͣɺσ ϑΥϧτͷςʔϚ͕൓ө͞ΕΔ • ͦͷޙɺඇಉظʹ൓ө͞ΕͨςʔϚઃఆ͕ద༻͞Εͨ

Slide 43

Slide 43 text

Կ͕ى͍ͬͯ͜Δͷ͔ • ΞϓϦىಈ࣌ʹApplicationΫϥεͰςʔϚઃఆΛऔಘͯ͠ద༻͍ͯ͠Δ • ςʔϚઃఆ͸Preferences DataStoreͰӬଓԽ͍ͯ͠Δ • DataStore͸ಉظॲཧʹରԠ͍ͯ͠ͳ͍ • ΞϓϦىಈˠϗʔϜը໘΁ͷը໘ભҠʹςʔϚͷ൓ө͕ؒʹ߹Θͣɺσ ϑΥϧτͷςʔϚ͕൓ө͞ΕΔ • ͦͷޙɺඇಉظʹ൓ө͞ΕͨςʔϚઃఆ͕ద༻͞Εͨ

Slide 44

Slide 44 text

ղܾࡦ • DataStore͔Β஋Λऔಘ͢ΔͷΛಉظॲཧʹ͢Δ • runBlockingͱFlow# fi rstΛۦ࢖͢Δ • SharedPreferencesΛ࢖͏

Slide 45

Slide 45 text

DataStoreͰಉظॲཧ͢Δ // ςʔϚͷద༻͕׬ྃ͢ΔͷΛ଴ͭͨΊʹrunBlockingͰॲཧ͢Δ runBlocking { initTheme() } private suspend fun initTheme() { themeSettingPreference.currentThemeStatus.first().let { AppCompatDelegate.setDefaultNightMode( it.getAppTheme().convertNightModeParameter() ) } }

Slide 46

Slide 46 text

runBlocking͢Δͱ͖ͷ஫ҙࣄ߲ • લఏͱͯ͠ਪ঑͞ΕΔํ๏Ͱ͸ͳ͍ • ຖճϑΝΠϧͷI/O͕૸Δͱ͍͏Θ͚Ͱ͸ͳ͍ͷͰɺͦΕΛཧղͯ͠ར༻͢Δͷ͸ΞϦ • UIεϨουΛϒϩοΩϯά͢ΔͷͰɺδϟϯΫ΍ANRͷՄೳੑ͸͋Δ • ैདྷͷSharedPreferencesͱಉ͡ • ෳ਺ՕॴͰݺͼग़͢৔߹͸ࣄલʹϓϦϩʔυΛ͓ͯ͘͠ͷ͕ྑ͍ • 2ճ໨Ҏ߱ͷݺͼग़͠Ͱ͸ϝϞϦΩϟογϡʹώοτ͢Δ • Activity#onCreateͱ͔ͰݺͿͱΑ͍

Slide 47

Slide 47 text

·ͱΊ

Slide 48

Slide 48 text

·ͱΊ • DataStoreͱ͸৽͍͠ϩʔΧϧετϨʔδιϦϡʔγϣϯͰ͋Δ • Kotlin Coroutines/FlowʹରԠ͍ͯ͠Δ • SharedPreferences͔ΒͷϚΠάϨʔγϣϯ΋Մೳ • ಉظॲཧ͕ඞཁʹͳͬͨΒrunBlocking͢Δ

Slide 49

Slide 49 text

͋Γ͕ͱ͏͍͟͝·ͨ͠🙇

Slide 50

Slide 50 text

ࢀߟจݙ • ެࣜυΩϡϝϯτΨΠυ • Introduction to Jetpack DataStore • ϓϩμΫτͰ҆શʹDataStoreҠߦ͢Δ • ୤SharedPreferencesɺDataStoreҠߦͯ͠ΈΑ͏