{"id":"b56141cd-b44b-4496-a025-781c11dbd076","shortId":"RfmaGB","kind":"skill","title":"datastore","tagline":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","description":"# DataStore\r\n\r\nDataStore is the modern replacement for SharedPreferences. Coroutine-based, type-safe, safe for Main thread.\r\n\r\n## Preferences DataStore — for simple key-value pairs\r\n\r\n```kotlin\r\n// Setup\r\nimplementation(libs.androidx.datastore.preferences)\r\n```\r\n\r\n```kotlin\r\n// ✅ Define all preference keys in one place\r\nobject PreferenceKeys {\r\n    val IS_DARK_THEME = booleanPreferencesKey(\"is_dark_theme\")\r\n    val ONBOARDING_COMPLETE = booleanPreferencesKey(\"onboarding_complete\")\r\n    val USER_ID = stringPreferencesKey(\"user_id\")\r\n    val NOTIFICATION_ENABLED = booleanPreferencesKey(\"notifications_enabled\")\r\n    val LANGUAGE = stringPreferencesKey(\"language\")\r\n    val FONT_SIZE = intPreferencesKey(\"font_size\")\r\n}\r\n\r\n// ✅ Hilt module — create DataStore singleton\r\n@Module\r\n@InstallIn(SingletonComponent::class)\r\nobject DataStoreModule {\r\n    @Provides\r\n    @Singleton\r\n    fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> =\r\n        PreferenceDataStoreFactory.create(\r\n            corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() },\r\n            migrations = listOf(SharedPreferencesMigration(context, \"legacy_prefs\")),\r\n            scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),\r\n            produceFile = { context.preferencesDataStoreFile(\"app_preferences\") }\r\n        )\r\n}\r\n\r\n// ✅ Repository pattern for DataStore\r\nclass UserPreferencesRepository @Inject constructor(\r\n    private val dataStore: DataStore<Preferences>\r\n) {\r\n    // Read — returns Flow, auto-updates on change\r\n    val isDarkTheme: Flow<Boolean> = dataStore.data\r\n        .catch { exception ->\r\n            if (exception is IOException) emit(emptyPreferences())\r\n            else throw exception\r\n        }\r\n        .map { preferences -> preferences[PreferenceKeys.IS_DARK_THEME] ?: false }\r\n\r\n    val onboardingComplete: Flow<Boolean> = dataStore.data\r\n        .catch { if (it is IOException) emit(emptyPreferences()) else throw it }\r\n        .map { it[PreferenceKeys.ONBOARDING_COMPLETE] ?: false }\r\n\r\n    // Read all preferences at once\r\n    val userPreferences: Flow<UserPreferences> = dataStore.data\r\n        .catch { if (it is IOException) emit(emptyPreferences()) else throw it }\r\n        .map { preferences ->\r\n            UserPreferences(\r\n                isDarkTheme = preferences[PreferenceKeys.IS_DARK_THEME] ?: false,\r\n                notificationsEnabled = preferences[PreferenceKeys.NOTIFICATION_ENABLED] ?: true,\r\n                language = preferences[PreferenceKeys.LANGUAGE] ?: \"en\",\r\n                fontSize = preferences[PreferenceKeys.FONT_SIZE] ?: 16\r\n            )\r\n        }\r\n\r\n    // Write — always suspend, safe on any coroutine\r\n    suspend fun setDarkTheme(enabled: Boolean) {\r\n        dataStore.edit { preferences ->\r\n            preferences[PreferenceKeys.IS_DARK_THEME] = enabled\r\n        }\r\n    }\r\n\r\n    suspend fun setOnboardingComplete() {\r\n        dataStore.edit { preferences ->\r\n            preferences[PreferenceKeys.ONBOARDING_COMPLETE] = true\r\n        }\r\n    }\r\n\r\n    suspend fun updateLanguage(language: String) {\r\n        dataStore.edit { preferences ->\r\n            preferences[PreferenceKeys.LANGUAGE] = language\r\n        }\r\n    }\r\n\r\n    suspend fun clearAll() {\r\n        dataStore.edit { it.clear() }\r\n    }\r\n}\r\n\r\ndata class UserPreferences(\r\n    val isDarkTheme: Boolean = false,\r\n    val notificationsEnabled: Boolean = true,\r\n    val language: String = \"en\",\r\n    val fontSize: Int = 16\r\n)\r\n```\r\n\r\n## Proto DataStore — for complex structured data\r\n\r\n```kotlin\r\n// Setup\r\nimplementation(libs.androidx.datastore)\r\nimplementation(libs.protobuf.kotlin.lite)\r\n```\r\n\r\n```proto\r\n// src/main/proto/user_settings.proto\r\nsyntax = \"proto3\";\r\noption java_package = \"com.company.app\";\r\noption java_multiple_files = true;\r\n\r\nmessage UserSettings {\r\n  bool dark_mode = 1;\r\n  string language = 2;\r\n  NotificationSettings notifications = 3;\r\n  repeated string recent_searches = 4;\r\n}\r\n\r\nmessage NotificationSettings {\r\n  bool enabled = 1;\r\n  bool marketing = 2;\r\n  bool updates = 3;\r\n}\r\n```\r\n\r\n```kotlin\r\n// ✅ Serializer for Proto DataStore\r\nobject UserSettingsSerializer : Serializer<UserSettings> {\r\n    override val defaultValue: UserSettings = UserSettings.getDefaultInstance()\r\n\r\n    override suspend fun readFrom(input: InputStream): UserSettings = try {\r\n        UserSettings.parseFrom(input)\r\n    } catch (e: InvalidProtocolBufferException) {\r\n        throw CorruptionException(\"Cannot read proto\", e)\r\n    }\r\n\r\n    override suspend fun writeTo(t: UserSettings, output: OutputStream) {\r\n        t.writeTo(output)\r\n    }\r\n}\r\n\r\n// ✅ Proto DataStore module\r\n@Module\r\n@InstallIn(SingletonComponent::class)\r\nobject ProtoDataStoreModule {\r\n    @Provides\r\n    @Singleton\r\n    fun provideUserSettingsDataStore(@ApplicationContext context: Context): DataStore<UserSettings> =\r\n        DataStoreFactory.create(\r\n            serializer = UserSettingsSerializer,\r\n            produceFile = { context.dataStoreFile(\"user_settings.pb\") }\r\n        )\r\n}\r\n```\r\n\r\n## ViewModel — read DataStore in ViewModel\r\n\r\n```kotlin\r\n// ✅ Map DataStore Flow to UiState in ViewModel\r\n@HiltViewModel\r\nclass SettingsViewModel @Inject constructor(\r\n    private val prefsRepository: UserPreferencesRepository\r\n) : ViewModel() {\r\n\r\n    val uiState: StateFlow<SettingsUiState> = prefsRepository.userPreferences\r\n        .map { prefs ->\r\n            SettingsUiState.Success(\r\n                isDarkTheme = prefs.isDarkTheme,\r\n                notificationsEnabled = prefs.notificationsEnabled,\r\n                language = prefs.language\r\n            )\r\n        }\r\n        .stateIn(\r\n            scope = viewModelScope,\r\n            started = SharingStarted.WhileSubscribed(5_000),\r\n            initialValue = SettingsUiState.Loading\r\n        )\r\n\r\n    fun onDarkThemeToggled(enabled: Boolean) {\r\n        viewModelScope.launch {\r\n            prefsRepository.setDarkTheme(enabled)\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Migration from SharedPreferences\r\n\r\n```kotlin\r\n// ✅ Migrate automatically on first DataStore read\r\nPreferenceDataStoreFactory.create(\r\n    migrations = listOf(\r\n        SharedPreferencesMigration(\r\n            context = context,\r\n            sharedPreferencesName = \"legacy_prefs\",\r\n            keysToMigrate = setOf(\"is_dark_theme\", \"user_id\")  // migrate specific keys only\r\n        )\r\n    ),\r\n    produceFile = { context.preferencesDataStoreFile(\"app_preferences\") }\r\n)\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ Using `SharedPreferences` — always use DataStore in new code\r\n❌ Reading DataStore with `.first()` on Main thread — observe as Flow in ViewModel\r\n❌ Creating multiple DataStore instances for same file — always `@Singleton`\r\n❌ No corruption handler — add `ReplaceFileCorruptionHandler { emptyPreferences() }`\r\n❌ Storing large data in DataStore — use Room for lists/complex objects\r\n❌ Calling `dataStore.edit {}` from Main thread — always from a coroutine","tags":["datastore","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-datastore","topic-agent-skills","topic-ai-agent","topic-android","topic-antigravity","topic-claude-code","topic-codex","topic-cursor","topic-gemini-cli","topic-hilt","topic-jetpack-compose","topic-kotlin","topic-material3"],"categories":["android-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/piyushverma0/android-agent-skills/datastore","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add piyushverma0/android-agent-skills","source_repo":"https://github.com/piyushverma0/android-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (6,913 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:09.462Z","embedding":null,"createdAt":"2026-05-18T13:14:49.033Z","updatedAt":"2026-05-18T19:09:09.462Z","lastSeenAt":"2026-05-18T19:09:09.462Z","tsv":"'000':473 '1':343,359 '16':250,312 '18':33 '2':346,362 '27':2 '3':349,365 '4':354 '5':28,472 'add':551 'agent':7 'ai':6,30 'alway':252,521,546,569 'android':3 'app':146,515 'applicationcontext':126,421 'auth':14 'auto':164 'auto-upd':163 'automat':488 'base':45 'bill':27 'bool':340,357,360,363 'boolean':262,299,303,479 'booleanpreferenceskey':79,86,98 'call':564 'cannot':394 'catch':172,194,218,389 'chang':167 'class':119,152,295,414,445 'claud':8 'clearal':291 'code':9,526 'codex':10 'com.company.app':332 'common':517 'complet':85,88,207,277 'complex':316 'constructor':155,448 'context':127,128,137,422,423,497,498 'context.datastorefile':429 'context.preferencesdatastorefile':145,514 'coroutin':44,257,572 'coroutine-bas':43 'coroutinescop':141 'corrupt':549 'corruptionexcept':393 'corruptionhandl':131 'creat':113,539 'cursor':11 'dark':77,81,187,234,267,341,505 'data':294,318,556 'datastor':1,35,36,54,114,129,151,158,159,314,370,409,424,433,438,491,523,528,541,558 'datastore.data':171,193,217 'datastore.edit':263,273,284,292,565 'datastorefactory.create':425 'datastoremodul':121 'day':34 'defaultvalu':376 'defin':66 'design':17 'dispatchers.io':142 'e':390,397 'els':180,201,225 'emit':178,199,223 'emptyprefer':133,179,200,224,553 'en':245,308 'enabl':97,100,240,261,269,358,478,482 'error':16 'except':173,175,182 'fals':189,208,236,300 'file':336,545 'first':490,530 'fitgenz':29 'fix':12 'flow':162,170,192,216,439,536 'font':106,109 'fontsiz':246,310 'fun':124,259,271,280,290,381,400,419,476 'handler':550 'hilt':15,111 'hiltviewmodel':444 'id':91,94,508 'implement':63,321,323 'inconsist':18 'initialvalu':474 'inject':154,447 'input':383,388 'inputstream':384 'installin':117,412 'instanc':542 'int':311 'intpreferenceskey':108 'invalidprotocolbufferexcept':391 'ioexcept':177,198,222 'isdarkthem':169,231,298,461 'it.clear':293 'java':330,334 'kapt':19 'key':58,69,511 'key-valu':57 'keystomigr':502 'kotlin':61,65,319,366,436,486 'ksp':20 'languag':102,104,242,282,288,306,345,465 'larg':555 'legaci':138,500 'libs.androidx.datastore':322 'libs.androidx.datastore.preferences':64 'libs.protobuf.kotlin.lite':324 'listof':135,495 'lists/complex':562 'main':51,532,567 'map':183,204,228,437,458 'market':361 'messag':338,355 'migrat':134,483,487,494,509 'miss':21 'mistak':518 'mode':342 'modern':39 'modul':112,116,410,411 'multipl':335,540 'new':525 'notif':96,99,348 'notificationsen':237,302,463 'notificationset':347,356 'object':73,120,371,415,563 'observ':534 'onboard':84,87 'onboardingcomplet':191 'ondarkthemetoggl':477 'one':71 'option':329,333 'output':404,407 'outputstream':405 'overrid':374,379,398 'packag':331 'pair':60 'pattern':149 'place':72 'pref':139,459,501 'prefer':53,68,147,184,185,211,229,232,238,243,247,264,265,274,275,285,286,516 'preferencedatastorefactory.create':130,493 'preferencekey':74 'preferencekeys.font':248 'preferencekeys.is':186,233,266 'preferencekeys.language':244,287 'preferencekeys.notification':239 'preferencekeys.onboarding':206,276 'prefs.isdarktheme':462 'prefs.language':466 'prefs.notificationsenabled':464 'prefsrepositori':451 'prefsrepository.setdarktheme':481 'prefsrepository.userpreferences':457 'privat':156,449 'producefil':144,428,513 'proto':313,325,369,396,408 'proto3':328 'protodatastoremodul':416 'provid':122,417 'providedatastor':125 'provideusersettingsdatastor':420 'read':160,209,395,432,492,527 'readfrom':382 'recent':352 'reduc':24 'repeat':350 'replac':40 'replacefilecorruptionhandl':132,552 'repositori':148 'return':161 'room':560 'safe':48,49,254 'scope':140,468 'search':353 'serial':367,373,426 'setdarkthem':260 'setof':503 'setonboardingcomplet':272 'settingsuistate.loading':475 'settingsuistate.success':460 'settingsviewmodel':446 'setup':62,320 'sharedprefer':42,485,520 'sharedpreferencesmigr':136,496 'sharedpreferencesnam':499 'sharingstarted.whilesubscribed':471 'ship':31 'simpl':56 'singleton':115,123,418,547 'singletoncompon':118,413 'size':107,110,249 'skill':4 'skill-datastore' 'source-piyushverma0' 'specif':510 'src/main/proto/user_settings.proto':326 'start':470 'state':23 'stateflow':456 'statein':467 'store':554 'string':283,307,344,351 'stringpreferenceskey':92,103 'structur':317 'supabas':13 'supervisorjob':143 'suspend':253,258,270,279,289,380,399 'syntax':327 't.writeto':406 'theme':78,82,188,235,268,506 'thread':52,533,568 'throw':181,202,226,392 'token':26 'topic-agent-skills' 'topic-ai-agent' 'topic-android' 'topic-antigravity' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-gemini-cli' 'topic-hilt' 'topic-jetpack-compose' 'topic-kotlin' 'topic-material3' 'tri':386 'true':241,278,304,337 'type':47 'type-saf':46 'uistat':22,441,455 'updat':165,364 'updatelanguag':281 'use':519,522,559 'user':90,93,507 'user_settings.pb':430 'userprefer':215,230,296 'userpreferencesrepositori':153,452 'userset':339,377,385,403 'usersettings.getdefaultinstance':378 'usersettings.parsefrom':387 'usersettingsseri':372,427 'val':75,83,89,95,101,105,157,168,190,214,297,301,305,309,375,450,454 'valu':59 'viewmodel':431,435,443,453,538 'viewmodelscop':469 'viewmodelscope.launch':480 'write':251 'writeto':401","prices":[{"id":"98e93cc0-5021-492d-b7af-3681f474a29d","listingId":"b56141cd-b44b-4496-a025-781c11dbd076","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"piyushverma0","category":"android-agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:49.033Z"}],"sources":[{"listingId":"b56141cd-b44b-4496-a025-781c11dbd076","source":"github","sourceId":"piyushverma0/android-agent-skills/datastore","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/datastore","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:49.033Z","lastSeenAt":"2026-05-18T19:09:09.462Z"}],"details":{"listingId":"b56141cd-b44b-4496-a025-781c11dbd076","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"datastore","github":{"repo":"piyushverma0/android-agent-skills","stars":8,"topics":["agent-skills","ai-agent","android","antigravity","claude-code","codex","cursor","gemini-cli","hilt","jetpack-compose","kotlin","material3","open-source","skills","supabase"],"license":"mit","html_url":"https://github.com/piyushverma0/android-agent-skills","pushed_at":"2026-04-27T09:15:31Z","description":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","skill_md_sha":"bd3132897fa5b0c2c5609d4dd06ea7993f593477","skill_md_path":"skills/datastore/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/datastore"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/datastore"},"updatedAt":"2026-05-18T19:09:09.462Z"}}