用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UitbreidenOS/UitKit --skill android命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Guidelines and instructions for Agent execution state rollback rules
Guidelines and instructions for Agent execution step counters limits
Guidelines and instructions for Agent execution timeout limits setups
基于 SOC 职业分类
正在显示 SKILL.md
| name | android |
| description | Build Android applications with Kotlin, Jetpack Compose, and modern Android architecture |
Recomposition reruns composable functions when their inputs change. Avoid unnecessary work:
Use remember to cache computed values and objects across recompositions:
@Composable
fun ExpensiveList(items: List<Item>) {
// Without remember — sorted on every recomposition
val sorted = items.sortedBy { it.name }
// With remember — re-sorts only when items reference changes
val sorted = remember(items) { items.sortedBy { it.name } }
LazyColumn {
items(sorted, key = { it.id }) { item ->
ItemRow(item)
}
}
}
Use derivedStateOf for values derived from observable state to reduce recomposition frequency:
@Composable
fun ScrollToTopButton(listState: LazyListState) {
// Without derivedStateOf — recomposes on every scroll pixel
val showButton = listState.firstVisibleItemIndex > 0
// With derivedStateOf — recomposes only when the boolean flips
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
AnimatedVisibility(visible = showButton) {
FloatingActionButton(onClick = { /* scroll to top */ }) {
Icon(Icons.Default.KeyboardArrowUp, contentDescription = "Scroll to top")
}
}
}
Stability: Compose skips recomposition of composables whose parameters have not changed — but only if all parameters are considered "stable" (primitives, @Stable/@Immutable annotated classes, or classes Compose infers as stable). Data classes with only stable fields are inferred as stable automatically.
// Unstable — List is not stable in Compose
@Composable
fun ItemList(items: List<Item>) { ... }
// Stable alternative — wrap in a stable holder
@Immutable
data class ItemsState(val items: ImmutableList<Item>)
@Composable
fun ItemList(state: ItemsState) { ... }
Use kotlinx.collections.immutable (ImmutableList, PersistentList) for stable collections in Compose.
data class ProductUiState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
)
sealed interface ProductEvent {
data object NavigateToCart : ProductEvent
data class ShowSnackbar(val message: String) : ProductEvent
}
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repo: ProductRepository,
) : ViewModel() {
// StateFlow for UI state — replayed on collection, represents current state
private val _uiState = MutableStateFlow(ProductUiState())
val uiState: StateFlow<ProductUiState> = _uiState.asStateFlow()
// SharedFlow for one-time events — not replayed, no initial value
private val _events = MutableSharedFlow<ProductEvent>()
val events: SharedFlow<ProductEvent> = _events.asSharedFlow()
init {
loadProducts()
}
fun loadProducts() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
repo.getProducts()
.onSuccess { products ->
_uiState.update { it.copy(products = products, isLoading = false) }
}
.onFailure { e ->
_uiState.update { it.copy(isLoading = , error = e.message) }
}
}
}
{
viewModelScope.launch {
repo.addToCart(product)
_events.emit(ProductEvent.ShowSnackbar())
}
}
}
) {
uiState vm.uiState.collectAsStateWithLifecycle()
snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect() {
vm.events.collect { event ->
(event) {
ProductEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.message)
ProductEvent.NavigateToCart -> { }
}
}
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
}
}
// Entity
@Entity(tableName = "products")
data class ProductEntity(
@PrimaryKey val id: Int,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "price_cents") val priceCents: Int,
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(),
)
// DAO
@Dao
interface ProductDao {
@Query("SELECT * FROM products ORDER BY name ASC")
fun observeAll(): Flow<List<ProductEntity>>
@Query("SELECT * FROM products WHERE id = :id")
suspend fun findById(id: Int): ProductEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertAll(products: List<ProductEntity>)
@Delete
suspend fun delete(product: ProductEntity)
}
: () {
: ProductDao
{
INSTANCE: AppDatabase? =
: AppDatabase =
INSTANCE ?: synchronized() {
Room.databaseBuilder(context, AppDatabase::.java, )
.addMigrations(MIGRATION_1_2)
.build()
.also { INSTANCE = it }
}
}
}
MIGRATION_1_2 = : Migration(, ) {
{
db.execSQL()
}
}
Provide via Hilt (see module section below). Always use Flow<T> in DAOs for reactive data; use suspend for one-shot queries.
// API model
data class ProductResponse(
@Json(name = "id") val id: Int,
@Json(name = "name") val name: String,
@Json(name = "price") val price: Double,
)
// Retrofit service interface
interface ProductService {
@GET("products")
suspend fun getProducts(
@Query("category") category: String? = null,
@Query("page") page: Int = 1,
): List<ProductResponse>
@GET("products/{id}")
suspend fun getProduct(@Path("id") id: Int): ProductResponse
@POST("products")
suspend fun createProduct(@Body body: CreateProductRequest): ProductResponse
}
// Build Retrofit instance
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/v1/")
.addConverterFactory(
MoshiConverterFactory.create(
Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
)
)
.client(
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
HttpLoggingInterceptor.Level.NONE
})
.addInterceptor { chain ->
request = chain.request().newBuilder()
.addHeader(, )
.build()
chain.proceed(request)
}
.build()
)
.build()
// di/NetworkModule.kt
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor())
.build()
@Provides @Singleton
fun provideMoshi(): Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
@Provides @Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit =
Retrofit.Builder()
.baseUrl("https://api.example.com/v1/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
@Provides @Singleton
fun provideProductService(retrofit: Retrofit): ProductService =
retrofit.create(ProductService::class.java)
}
// di/DatabaseModule.kt
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides @Singleton
fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase =
AppDatabase.getInstance(ctx)
@Provides
: ProductDao = db.productDao()
}
: ()
Annotate Activity, Fragment, ViewModel, Service with @AndroidEntryPoint to enable field injection. Use @HiltViewModel on ViewModels.
Before submitting a release:
Technical:
targetSdkVersion at least the current requirement (Google updates this annually).aab) required for new apps — not APKAndroidManifest.xml must be justified in the data safety formStore listing assets:
Data safety section:
Pre-launch report:
A product list screen backed by Room + Retrofit with offline-first caching:
// Repository
class ProductRepository @Inject constructor(
private val service: ProductService,
private val dao: ProductDao,
) {
fun observeProducts(): Flow<List<Product>> =
dao.observeAll().map { entities -> entities.map { it.toProduct() } }
suspend fun syncProducts() {
val remote = service.getProducts()
dao.deleteAll()
dao.upsertAll(remote.map { it.toEntity() })
}
}
// Screen
@Composable
fun ProductListScreen(vm: ProductViewModel = hiltViewModel()) {
val state by vm.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) { vm.sync() }
LazyColumn {
items(state.products, key = { it.id }) { product ->
ProductCard(
product = product,
onAddToCart = { vm.onAddToCartClick(product) },
)
}
}
}