用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill android-dev-core命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Token-efficient tracking for AI orchestration. CLI-first for status updates (~50 tokens), agent fallback for complex ops (~1KB). Use when: updating task status, querying blockers, creating progress files, validating phases.
AshAi extension guidelines for integrating AI capabilities with Ash Framework. Use when implementing vectorization/embeddings, exposing Ash actions as LLM tools, creating prompt-backed actions, or setting up MCP servers. Covers semantic search, LangChain integration, and structured outputs.
This skill should be used when solving hard questions, complex architectural problems, or debugging issues that benefit from GPT-5 Pro or GPT-5.1 thinking models with large file context. Use when standard Claude analysis needs deeper reasoning or extended context windows.
基于 SOC 职业分类
正在显示 SKILL.md
| name | android-dev-core |
| description | Android 功能开发核心规则。包含项目架构、MVI模式、Base类、Adapter、网络请求等基础规范。开发任何 Android 功能时都应加载此 skill。 |
| metadata | {"category":"android","platform":"android"} |
本 Skill 包含 Android 功能开发的核心规范,适用于所有功能开发场景。
You are an Android Developer that provides expert-level insights and solutions. Your responses should include examples of code snippets (where applicable), best practices, and explanations of underlying concepts.
Here are some rules:
Coroutine and suspend functions for asynchronous operations, and prioritize the use of suspendCancellableCoroutine or callbackFlow instead of the callback parameter. Improve code readability and maintainability.NOTICE: When using encoding, prioritize encoding and importing packages according to the following format, never put a full package name in a code block(e.g.,com.package.VLog.d()), always put an import statement at the beginning of the file. adhere to these project standards to maintain code consistency:
import com.androidtool.common.extension.onClick
// Standard usage
button.onClick { /* handle click */ }
// With custom debounce delay
button.onClick(delay = 600) { /* handle click */ }
import com.androidtool.common.extension.dp
// Usage
val padding = 16.dp
import com.androidtool.common.utils.ScreenUtil
// Available methods
// Get StatusBar height
ScreenUtil.getStatusBarHeight()
// Get screen width
ScreenUtil.getScreenWidth()
// Get screen height
ScreenUtil.getScreenHeight()
import com.androidtool.common.log.VLog
// Always use "lilili" as primary tag with descriptive messages
VLog.d("lilili", "Retrieved user ID: $userId")
import com.androidtool.common.utils.TranslateResource
binding.title = TranslateResource.getStringResources("title")
import com.androidtool.common.utils.collectWhenStarted
viewModel.sampleLoadMoreUiState.collectWhenStarted { state ->
handleUiState(state)
}
import com.androidtool.common.base.page.EmptyPage
import com.androidtool.common.base.page.ErrorPage
import com.androidtool.common.base.page.LoadingPage
class GiftRankListFragment : BaseBindingFragment<FragmentGiftRankListBinding>() {
private val viewModel: GiftListViewModel by viewModels()
private lateinit var id: String
// Integration of LoadSir page placeholder
override fun registerState() = binding.recyclerView
private fun handleUiState(state: GiftListUiState<GiftRankListBean.GiftRankItem>) {
when (state) {
is GiftListUiState.Loading -> {
showState<LoadingPage>()
}
is GiftListUiState.Empty -> {
binding.refreshLayout.finishRefresh()
showState<EmptyPage> ()
}
is GiftListUiState.Success -> {
showSuccess() // LoadSir integration
handleSuccessState(state)
}
is GiftListUiState.Error -> {
binding.refreshLayout.finishRefresh()
showState<ErrorPage> {
val hint = view.findViewById<TextView>(R.id.hint)
hint?.onClick {
viewModel.refresh()
}
}
}
}
}
}
import com.model.UserBean
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseMultiItemQuickAdapter
import com.chad.library.adapter.base.module.LoadMoreModule
import com.chad.library.adapter.base.viewholder.BaseViewHolder
// Using https://github.com/CymChad/BaseRecyclerViewAdapterHelper v3.x
// Simple list adapter
class SampleLoadMoreAdapter : BaseQuickAdapter<SampleUserInfo, BaseViewHolder>(R.layout.item_sample_user),
LoadMoreModule {}
// Complex multi-type list adapter
class InterestTagAdapter(private val chosenListListener: (List<UserBean>) -> Unit) :
BaseMultiItemQuickAdapter<InterestTagShowBean, BaseViewHolder>() {}
com.model package, import usingimport com.model.SomeBean
Always use project-provided utilities and extensions instead of creating custom implementations or introducing unnecessary third-party dependencies.
Note: This is a recommended project structure, but be flexible and adapt to existing project structures. Do not enforce these structural patterns if the project follows a different organization.
Assume I want to implement the feature:"one_feature":
app/src/main/java/com/package/one_feature
├── data
│ ├── repository
│ │ └── OneFeatureRepositoryImpl.kt
│ ├── datasource (optional)
│ │ ├── local
│ │ │ ├── OneFeatureDao.kt
│ │ │ └── OneFeatureDatabase.kt
│ │ └── remote
│ │ └── OneFeatureApiService.kt
│ ├── model
│ │ ├── OneFeatureEntity.kt
│ │ └── OneFeatureResponse.kt
│ └── mapper (optional)
│ └── OneFeatureMapper.kt
├── di (optional)
│ └── OneFeatureModule.kt
├── ui
│ ├── fragment
│ │ ├── OneFeatureFragment.kt
│ │ ├── TwoFeatureFragment.kt(optional)
│ │ ├── OneFeatureListAdapter.kt
│ │ ├── OneFeatureSubListViewModel.kt
│ │ └── OneFeatureListContract.kt(contain state,event,effect)
│ ├── view(optional)
│ │ ├── CustomViews.kt
│ │ └── OneFeatureItemView.kt
│ │── OneFeatureListContract.kt(contain state,event,effect)
│ ├── OneFeatureActivity.kt
│ └── OneFeatureViewModel.kt
└── domain(optional)
│ ├── usecase
│ │ ├── GetOneFeatureUseCase.kt
│ │ └── UpdateOneFeatureUseCase.kt
│ ├── repository
│ │ └── IOneFeatureRepository.kt
│ └── model
│ └── OneFeatureModel.kt
Note:
com/package/one_feature when importing.When implementing complex logic in Activities and Fragments, use property delegation pattern to organize related functionality into specialized Impl classes. The delegate classes should implement DefaultLifecycleObserver and provide a bindLifecycle() method for automatic lifecycle management.
class FloatingVideoFragment : BaseBindingFragment<FragmentFloatingVideoBinding>(),
DraggableBehavior by DraggableBehaviorImpl(),
LoopVideoPlayerBehavior by LoopVideoPlayerBehaviorImpl() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindLifecycle(lifecycle)
}
}
interface LoopVideoPlayerBehavior {
fun bindLifecycle(lifecycle: Lifecycle)
fun setEventVideo(urls: List<DemoEventVideo>)
fun setupVideoPlayer(container: ViewPager2)
fun isLooping(): Boolean
fun startLoop()
fun pauseLoop()
fun releasePlayerResource()
}
class LoopVideoPlayerBehaviorImpl : LoopVideoPlayerBehavior, DefaultLifecycleObserver {
override {
lifecycle.addObserver()
}
{ }
{ }
{ releasePlayerResource() }
}
State, Event, Effect should be integrated into a newly created OneFeatureContract.kt file
/**
* Contract file for a single feature
* Contains all state, event, and effect definitions for this feature
*/
/**
* Data class representing UI state
* Contains all data needed for UI display
*/
data class OneFeatureState(
val isLoading: Boolean = false,
val data: List<String> = emptyList(),
val error: String? = null,
val selectedItemId: String? = null
)
/**
* Sealed class representing user intentions or system events
* These events trigger state changes
*/
sealed interface OneFeatureEvent {
data object LoadData : OneFeatureEvent()
data object RefreshData : OneFeatureEvent()
data class DeleteItem(val itemId: String) : OneFeatureEvent()
}
/**
* Sealed class representing one-time side effects
* These effects are typically one-time operations like navigation, Toast or Snackbar displays
*/
sealed interface OneFeatureEffect {
data class ShowToast(val message: String) : OneFeatureEffect()
data class ShareContent(val content: String) : OneFeatureEffect()
}
When implementing MVI architecture, follow these data flow patterns:
Effect: Use Channel to send one-time events (like navigation, Toast notifications) from ViewModel to View layer
// In ViewModel
private val _effect = Channel<MyEffect>(Channel.BUFFERED)
val effect = _effect.receiveAsFlow()
fun showToast(message: String) {
viewModelScope.launch {
_effect.send(MyEffect.ShowToast(message))
}
}
// In View layer
import com.androidtool.common.utils.collectWhenStarted
viewModel.effect.collectWhenStarted { effect ->
when (effect) {
is MyEffect.ShowToast -> Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
is MyEffect.Navigate -> findNavController().navigate(effect.destination)
}
}
State: Use StateFlow to manage and expose UI state, ensuring consistency and observability
// In ViewModel
private val _state = MutableStateFlow(MyState())
val state = _state.asStateFlow()
Event: Trigger state updates by simply calling action methods on the ViewModel
// In ViewModel
fun onAction(event: MyEvent) {
when (event) {
is MyEvent.ItemClicked -> handleItemClick(event.item)
is MyEvent.RefreshRequested -> loadData()
}
}
// In View layer
binding.refreshButton.onClick {
viewModel.onAction(MyEvent.RefreshRequested)
}
recyclerView.adapter = adapter.apply {
setOnItemClickListener { adapter, view, position ->
val item = adapter.getItem(position)
viewModel.onAction(MyEvent.ItemClicked(item))
}
}
The project encapsulates LoadSir as a loading and error display framework, used as follows:
open fun registerState(): View? = binding.recycleview or
open fun registerState(): View? = binding.rootshowState<LoadingPage>, showState<ErrorPage>, showState<EmptyPage> to display state pages, and use
showSuccess() to display the page used in the XMLThe project encapsulates BaseBindingFragment, package in package com.androidtool.common.base:
package com.androidtool.common.base
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.androidtool.common.loadsir.LoadLayout
import com.androidtool.common.loadsir.LoadSir
import com.scwang.smart.refresh.layout.SmartRefreshLayout
abstract class BaseBindingFragment<VB : ViewBinding> : Fragment(),
IViewBinding<VB> by ViewBindingDelegate() {
protected var lazyLoaded = false
protected var initView = false
// State page management
lateinit var loadSir: LoadLayout
private set
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
lazyLoaded = false
initView = false
return createViewBinding(inflater, container, true)?.let { rootView ->
// Register state page management
registerState()?.let { compatibleSmartRefresh(it) } ?: run { rootView }
}
}
override {
.onViewCreated(view, savedInstanceState)
initView()
}
{
.onDestroyView()
lazyLoaded =
}
{
.onResume()
(!lazyLoaded) {
lazyLoaded =
onLazyLoad()
}
}
{}
=
= lazyLoaded
: View {
loadSir = LoadSir.register(target, listener = { onStateClick() })
(loadSir.parent ? SmartRefreshLayout)?.let {
it.removeView(loadSir)
it.setRefreshContent(loadSir)
}
loadSir.rootView
}
: View? =
=
= loadSir.show<LoadingPage>()
= loadSir.showSuccess(useAnim)
{
isShowState = (ifShow) {
Collection<*> -> ifShow.isEmpty()
Array<*> -> ifShow.isEmpty()
-> ifShow
->
}
(isShowState) loadSir.show<T>(
useAnim = useAnim, block = block
) loadSir.showSuccess()
}
}
Usage example:
import com.androidtool.common.base.BaseBindingFragment
import com.androidtool.common.utils.collectWhenStarted
class SampleListFragment : BaseBindingFragment<FragmentGiftRankListBinding>() {
private val viewModel: SampleListViewModel by viewModels()
private lateinit var id: String
// refresh placeholder usage
override fun registerState() = binding.recyclerView
private val adapter by lazy {
GiftRankListAdapter()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
id = arguments?.getString(DYNAMIC_ID) ?: "0"
viewModel.initDataById(id = id)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRefreshLayout()
setupRecyclerView()
observeUiState()
}
/**
* Setup pull-to-refresh
*/
private fun setupRefreshLayout() {
binding.refreshLayout.setOnRefreshListener {
viewModel.refresh()
}
}
/**
* Setup RecyclerView and load more
*/
{
binding.recyclerView.layoutManager = LinearLayoutManager(context)
binding.recyclerView.adapter = adapter
adapter.setOnItemClickListener { _, _, position ->
uId = adapter.[position].uid
}
adapter.loadMoreModule.apply {
isAutoLoadMore =
isEnableLoadMoreIfNotFullPage =
setOnLoadMoreListener {
viewModel.loadMore()
}
}
}
{
viewModel.listUiState.collectWhenStarted { state ->
handleUiState(state)
}
viewModel.listEffect.collectWhenStarted { event ->
(event) {
GiftListUiEvent.UpdatePeopleCount -> {
handleUpdatePeopleCount(event.count)
}
}
}
}
{
DYNAMIC_ID =
: Fragment {
fragment = SampleListFragment()
fragment.arguments = Bundle().apply {
putString(DYNAMIC_ID, dynamicId)
}
fragment
}
}
The project encapsulates BaseBindingActivity, integrating LoadSir and ViewBinding. Note that this BaseBindingActivity does not implement TitleBar, you need to implement it yourself:
abstract class BaseBindingActivity<V : ViewBinding> : RootActivity(),
IViewBinding<V> by ViewBindingDelegate() {
// State page management
lateinit var loadSir: LoadLayout
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = createViewBinding()
// Register state page management
registerState()?.let { target ->
loadSir = LoadSir.register(target, listener = { onStateClick() })
// Compatible with SmartRefreshLayout
(loadSir.parent as? SmartRefreshLayout)?.let {
it.removeView(loadSir)
it.setRefreshContent(loadSir)
}
}
setContentView((rootView.parent as? View) ?: rootView)
initView()
initData()
}
abstract fun initView()
open fun initData() = Unit
open fun registerState(): View? = null
open fun onStateClick() = Unit
fun = loadSir.show<LoadingPage>()
= loadSir.showSuccess(useAnim)
{
isShowState = (ifShow) {
Collection<*> -> ifShow.isEmpty()
Array<*> -> ifShow.isEmpty()
-> ifShow
->
}
(isShowState) loadSir.show<T>(
useAnim = useAnim, block = block
) loadSir.showSuccess()
}
}
Usage example using com.androidtool.common.widget.TitleBarView to integrate title bar:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.topic.TopicListActivity">
<com.androidtool.common.widget.TitleBarView
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TopicTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal=
=
=
=
=
=
=
=
= />
SampleBindingActivity:
class TopicListActivity : BaseBindingActivity<ActivityTopicListBinding>() {
private val viewModel: TopicListViewModel by viewModels()
private var tabLayoutMediator: TabLayoutMediator? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStatusBarTextColor(isLight = true)
initView()
observeData()
viewModel.loadData()
}
override fun initView() {
// Set title bar
binding.titleBar.setTitle(TranslateResource.getStringResources("topic"))
binding.titleBar.setOnBackClickListener {
finish()
}
}
override fun registerState(): View {
return binding.contentView
}
private fun observeData() {
viewModel.topicCategoryListUiState.collectWhenStarted {
when (it) {
TopicCategoryListUiState.Empty -> {
showState<EmptyPage>()
}
is TopicCategoryListUiState.Error -> {
showState<ErrorPage>()
}
TopicCategoryListUiState.Loading -> {
showState<LoadingPage>()
}
is TopicCategoryListUiState.Success -> {
showSuccess()
initTabLayoutAndViewPager(it.items)
}
}
}
}
{
pagerAdapter = TopicPagerAdapter(, categories)
binding.viewPager.adapter = pagerAdapter
tabLayoutMediator?.detach()
tabLayoutMediator =
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tabView = layoutInflater.inflate(R.layout.tab_item_topic, )
tabTitle = tabView.findViewById<TextView>(R.id.tvTabTitle)
tabTitle.text = categories[position].name
tab.customView = tabView
}.apply {
attach()
}
(categories.isNotEmpty()) {
binding.viewPager.setCurrentItem(, )
}
}
{
tabLayoutMediator?.detach()
tabLayoutMediator =
.onDestroy()
}
{
{
intent = Intent(context, TopicListActivity::.java)
context.startActivity(intent)
}
}
}
binding.titleBar.setTitle(TranslateResource.getStringResources("topic"))
binding.titleBar.setOnBackClickListener {
finish()
}
TitleBarView API:
/**
* Set title text
*/
fun setTitle(title: CharSequence) {}
/**
* Set back button click listener
*/
fun setOnBackClickListener(listener: () -> Unit) {}
/**
* Set extra button (icon) on the right
*/
fun setExtraButton(@DrawableRes resId: Int, clickListener: (v: View) -> Unit) {}
/**
* Set custom layout for the right side
*/
fun setExtraButtonLayout(view: View) {}
Adapters should use BaseRecyclerViewAdapterHelper V3.x, as follows:
import com.model.UserBean
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseMultiItemQuickAdapter
import com.chad.library.adapter.base.module.LoadMoreModule
import com.chad.library.adapter.base.viewholder.BaseViewHolder
// Using https://github.com/CymChad/BaseRecyclerViewAdapterHelper v3.x
// Simple list adapter
class SampleLoadMoreAdapter : BaseQuickAdapter<SampleUserInfo, BaseViewHolder>(R.layout.item_sample_user),
LoadMoreModule {}
// Complex multi-type list adapter
class InterestTagAdapter(private val chosenListListener: (List<UserBean>) -> Unit) :
BaseMultiItemQuickAdapter<InterestTagShowBean, BaseViewHolder>() {}
NOTICE: adhere to these project package import standards to maintain code consistency
import com.androidtool.common.extension.asResult
import com.androidtool.common.extension.requestToFlow
// Standard pattern
fun loadData(): Flow<Result<DataModel>> {
return requestToFlow { apiService.fetchData() }.asResult()
}
// Follow this pattern for data loading
fun loadData() {
viewModelScope.launch {
updateLoadingState()
repository.loadData()
.catch { exception -> handleError(exception) }
.collect { result ->
result.fold(
onSuccess = { data -> handleSuccess(data) },
onFailure = { exception -> handleError(exception) }
)
}
}
}
import com.androidtool.common.troll.BaseResponse
import com.androidtool.common.net.Apis
// 1. 项目中封装了 BaseResponse,所有的接口返回类型都为 BaseResponse
// 2. 项目中所有的参数都是通过Map<String, String>)进行请求的
interface ApiService {
@GET(Apis.ENDPOINT_DATA)
suspend fun getData(): BaseResponse<DataModel>
@GET(Apis.ENDPOINT_PARAMS_DATA)
suspend fun getParamsData(@QueryMap map: Map<String, String>): BaseResponse<List<DataModel>>
}