data layer mastery

Data Layer Mastery (数据层专精)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "data layer mastery" with this command: npx skills add fwrite0920/android-skills/fwrite0920-android-skills-data-layer-mastery

Data Layer Mastery (数据层专精)

Instructions

  • 确认需求属于数据层(Room、网络、脱机策略)

  • 先填写 Required Inputs(数据源、一致性目标、回退策略)

  • 依照下方章节顺序套用

  • 一次只调整一个数据流或责任边界

  • 完成后对照 Quick Checklist

When to Use

  • Scenario A:新项目数据层创建

  • Scenario D:性能问题的数据层瓶颈

  • Scenario F:KMP 共享数据层设计

Example Prompts

  • "请参考 Room Advanced,帮我设计 Migration 策略"

  • "依照 Network Layer 章节,创建统一的错误处理"

  • "请用 Offline-First 章节查看目前 Repository 是否符合 SSOT"

Workflow

  • 先确认 Required Inputs(数据源、同步策略、失败处理)

  • 检查 Room / Network 的基础设计

  • 确立 Offline-First 与数据同步策略

  • 执行 Data Gate(迁移/错误/回归),再用 Quick Checklist 验收

Practical Notes (2026)

  • Offline-first 只在不稳网络或高一致性需求时激活

  • Repository 必须是 SSOT,避免多处来源竞争

  • 错误处理统一化,避免每层自行判断

  • 每个关键查询必须有索引与量测基线

  • 同步失败要有重试与幂等策略,避免脏写

Minimal Template

目标: 数据源: 一致性要求: 同步触发策略: 缓存策略: 错误处理: 验收: Quick Checklist

Required Inputs (执行前输入)

  • 数据源清单 (本地 DB / 远端 API / 缓存)

  • 一致性目标 (最终一致 / 强一致)

  • 同步触发 (前台/后台/手动)

  • 错误策略 (重试、降级、熔断)

  • 回退策略 (迁移失败或线上异常时)

Deliverables (完成后交付物)

  • Repository SSOT 结构与责任边界

  • Room migration 脚本与测试

  • Network error 统一封装

  • Offline-first 同步流程说明

  • Data Gate 验收记录

Data Gate (验收门槛)

./gradlew test ./gradlew connectedDebugAndroidTest

至少包含:Migration 测试、Repository 行为测试、错误路径测试。

Room Advanced

Migration 策略

val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE users ADD COLUMN avatar_url TEXT") } }

val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { // 复杂迁移:创建新表、复制数据、删除旧表 database.execSQL("CREATE TABLE users_new (...)") database.execSQL("INSERT INTO users_new SELECT ... FROM users") database.execSQL("DROP TABLE users") database.execSQL("ALTER TABLE users_new RENAME TO users") } }

Room.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build()

Paging 3 集成

@Dao interface UserDao { @Query("SELECT * FROM users ORDER BY name") fun pagingSource(): PagingSource<Int, User> }

// Repository class UserRepository(private val dao: UserDao) { fun getUsers(): Flow<PagingData<User>> = Pager( config = PagingConfig(pageSize = 20, prefetchDistance = 5), pagingSourceFactory = { dao.pagingSource() } ).flow }

// ViewModel val users = repository.getUsers().cachedIn(viewModelScope)

Full-Text Search (FTS)

@Fts4(contentEntity = Article::class) @Entity(tableName = "articles_fts") data class ArticleFts( @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "content") val content: String )

@Dao interface ArticleDao { @Query("SELECT * FROM articles WHERE rowid IN (SELECT rowid FROM articles_fts WHERE articles_fts MATCH :query)") fun search(query: String): Flow<List<Article>> }

Network Layer (Retrofit + OkHttp)

Error Handling Strategy

sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error(val code: Int, val message: String) : NetworkResult<Nothing>() data object NetworkError : NetworkResult<Nothing>() }

suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): NetworkResult<T> { return try { val response = apiCall() if (response.isSuccessful) { val body = response.body() if (body != null) { NetworkResult.Success(body) } else { NetworkResult.Error(response.code(), "Empty response body") } } else { NetworkResult.Error(response.code(), response.message()) } } catch (e: IOException) { NetworkResult.NetworkError } }

Interceptors

// Logging val loggingInterceptor = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) BODY else NONE }

// Auth Token class AuthInterceptor(private val tokenProvider: TokenProvider) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().newBuilder() .addHeader("Authorization", "Bearer ${tokenProvider.token}") .build() return chain.proceed(request) } }

// Retry class RetryInterceptor( private val maxRetries: Int = 3, private val baseDelayMs: Long = 200 ) : Interceptor { private val retryableMethods = setOf("GET", "HEAD", "OPTIONS") private val retryableStatusCodes = setOf(408, 429, 500, 502, 503, 504)

override fun intercept(chain: Interceptor.Chain): Response {
    val request = chain.request()

    // 仅重试幂等请求,避免 POST/PUT 重复提交副作用
    if (request.method !in retryableMethods) {
        return chain.proceed(request)
    }

    var attempt = 0
    while (true) {
        try {
            val response = chain.proceed(request)
            val shouldRetry = response.code in retryableStatusCodes &#x26;&#x26; attempt &#x3C; maxRetries - 1
            if (!shouldRetry) {
                return response
            }
            response.close()
        } catch (e: IOException) {
            if (attempt >= maxRetries - 1) throw e
        }

        attempt++
        Thread.sleep(baseDelayMs * attempt) // 简单 backoff,降低瞬时失败抖动
    }
}

}

Offline-First Architecture

Repository Pattern (SSOT)

class UserRepository( private val remoteDataSource: UserRemoteDataSource, private val localDataSource: UserLocalDataSource ) { fun getUser(id: String): Flow<User> = flow { // 1. 先从 Local 发射 localDataSource.getUser(id)?.let { emit(it) }

    // 2. 从 Remote 取得最新
    val remote = remoteDataSource.fetchUser(id)
    
    // 3. 存入 Local
    localDataSource.saveUser(remote)
    
    // 4. 发射更新后的数据
    emit(remote)
}

// 或使用 NetworkBoundResource pattern
fun getUserWithCache(id: String): Flow&#x3C;Resource&#x3C;User>> = networkBoundResource(
    query = { localDataSource.getUserFlow(id) },
    fetch = { remoteDataSource.fetchUser(id) },
    saveFetchResult = { localDataSource.saveUser(it) },
    shouldFetch = { it == null || it.isStale() }
)

}

DataStore Migration

SharedPreferences → Preferences DataStore

val Context.dataStore by preferencesDataStore( name = "settings", produceMigrations = { context -> listOf(SharedPreferencesMigration(context, "old_prefs")) } )

// 使用 val themeKey = booleanPreferencesKey("dark_theme")

suspend fun setDarkTheme(enabled: Boolean) { context.dataStore.edit { prefs -> prefs[themeKey] = enabled } }

val darkThemeFlow: Flow<Boolean> = context.dataStore.data .map { it[themeKey] ?: false }

Quick Checklist

  • Required Inputs 已填写并冻结(数据源/一致性/回退)

  • Room Migration 测试通过

  • Network Error 统一处理

  • Repository 实作 SSOT

  • DataStore 取代 SharedPreferences

  • Paging 用于大量数据列表

  • Data Gate 已执行并记录结果

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

crash monitoring

No summary provided by upstream source.

Repository SourceNeeds Review
General

android skill index

No summary provided by upstream source.

Repository SourceNeeds Review
General

navigation patterns

No summary provided by upstream source.

Repository SourceNeeds Review