Android Kotlin Development

VerifiedSafe

Build native Android apps with Kotlin, MVVM, Jetpack Compose, Retrofit, and Room.

Sby Skills Guide Bot
DevelopmentIntermediate
8/2/2026
Claude CodeCursorWindsurfCopilotCodex
#android#kotlin#jetpack-compose#mvvm#retrofit

Recommended for

Automated summary

This skill guides the development of native Android apps in Kotlin using Retrofit, Hilt and MVVM ViewModels that expose StateFlow.

Strengths

  • Provides complete, consistent examples of data models, a Retrofit API interface and a network client with an auth interceptor
  • Demonstrates a clean MVVM implementation with MutableStateFlow/StateFlow and @HiltViewModel injection
  • Covers loading and error state handling in ViewModels, an often-neglected area

Limitations

  • The description promises Room, Jetpack Compose and navigation architecture, but the supplied content stops before covering them and appears truncated
  • The base URL is hardcoded and token access relies on an undefined static PreferencesManager
  • Error handling is inconsistent (silent println in ItemsViewModel) and there are no tests or Compose UI examples
When to use it

Use it to bootstrap or structure a Kotlin Android app built around Retrofit, Hilt and MVVM ViewModels.

When not to use it

Skip it if you need finished examples of Room, Compose navigation or Jetpack Compose screens, which the provided content does not include.

Security analysis

Safe
Quality score76/100

The skill contains only instructional prose and Kotlin code examples. It does not instruct the agent to execute shell commands, download payloads, delete files, exfiltrate data, or disable safety mechanisms. The only notable security consideration is standard guidance around token handling in the sample interceptor, which is a code-quality concern rather than a listing risk.

Findings
  • •Minor security note: the sample Retrofit interceptor attaches a bearer token to every request; real implementations should scope credentials to trusted hosts and prefer secure token storage.
How this analysis is made

Examples

Set up an API layer
Create a Retrofit ApiService and Hilt NetworkModule for my Kotlin Android app, including a bearer-token interceptor and timeouts.
Build an MVVM ViewModel
Write a @HiltViewModel in Kotlin that loads a list of items from Retrofit and exposes items, isLoading and errorMessage as StateFlow.
Screen state handling
Refactor my Android ViewModel so loading and error states use StateFlow and errors are surfaced to the UI instead of being printed.

name: android-kotlin-development description: Develop native Android apps with Kotlin. Covers MVVM with Jetpack, Compose for modern UI, Retrofit for API calls, Room for local storage, and navigation architecture.

Android Kotlin Development

Overview

Build robust native Android applications using Kotlin with modern architecture patterns, Jetpack libraries, and Compose for declarative UI.

When to Use

  • Creating native Android applications with best practices
  • Using Kotlin for type-safe development
  • Implementing MVVM architecture with Jetpack
  • Building modern UIs with Jetpack Compose
  • Integrating with Android platform APIs

Instructions

1. Models & API Service

// Models
data class User(
  val id: String,
  val name: String,
  val email: String,
  val avatarUrl: String? = null
)

data class Item(
  val id: String,
  val title: String,
  val description: String,
  val imageUrl: String? = null,
  val price: Double
)

// API Service with Retrofit
interface ApiService {
  @GET("/users/{id}")
  suspend fun getUser(@Path("id") userId: String): User

  @PUT("/users/{id}")
  suspend fun updateUser(
    @Path("id") userId: String,
    @Body user: User
  ): User

  @GET("/items")
  suspend fun getItems(@Query("filter") filter: String = "all"): List<Item>

  @POST("/items")
  suspend fun createItem(@Body item: Item): Item
}

// Network client setup
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
  @Provides
  @Singleton
  fun provideRetrofit(): Retrofit {
    val httpClient = OkHttpClient.Builder()
      .addInterceptor { chain ->
        val original = chain.request()
        val requestBuilder = original.newBuilder()

        val token = PreferencesManager.getToken()
        if (token.isNotEmpty()) {
          requestBuilder.addHeader("Authorization", "Bearer $token")
        }

        requestBuilder.addHeader("Content-Type", "application/json")
        chain.proceed(requestBuilder.build())
      }
      .connectTimeout(30, TimeUnit.SECONDS)
      .readTimeout(30, TimeUnit.SECONDS)
      .build()

    return Retrofit.Builder()
      .baseUrl("https://api.example.com")
      .client(httpClient)
      .addConverterFactory(GsonConverterFactory.create())
      .build()
  }

  @Provides
  @Singleton
  fun provideApiService(retrofit: Retrofit): ApiService {
    return retrofit.create(ApiService::class.java)
  }
}

2. MVVM ViewModels with Jetpack

@HiltViewModel
class UserViewModel @Inject constructor(
  private val apiService: ApiService
) : ViewModel() {
  private val _user = MutableStateFlow<User?>(null)
  val user: StateFlow<User?> = _user.asStateFlow()

  private val _isLoading = MutableStateFlow(false)
  val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

  private val _errorMessage = MutableStateFlow<String?>(null)
  val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()

  fun fetchUser(userId: String) {
    viewModelScope.launch {
      _isLoading.value = true
      _errorMessage.value = null

      try {
        val user = apiService.getUser(userId)
        _user.value = user
      } catch (e: Exception) {
        _errorMessage.value = e.message ?: "Unknown error"
      } finally {
        _isLoading.value = false
      }
    }
  }

  fun logout() {
    _user.value = null
  }
}

@HiltViewModel
class ItemsViewModel @Inject constructor(
  private val apiService: ApiService
) : ViewModel() {
  private val _items = MutableStateFlow<List<Item>>(emptyList())
  val items: StateFlow<List<Item>> = _items.asStateFlow()

  private val _isLoading = MutableStateFlow(false)
  val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

  fun fetchItems(filter: String = "all") {
    viewModelScope.launch {
      _isLoading.value = true
      try {
        val items = apiService.getItems(filter)
        _items.value = items
      } catch (e: Exception) {
        println("Error fetching items: ${e.message}")
      } finally {
        _isLoading.value = false
      }
    }
  }

  fun addItem(item: Item) {
    viewModelScope.launch {
      try {
        val created = apiService.createItem(item)
        _items.value = _items.value + created
      } catch (e: Exception) {
        println("Error creating item: ${e.message}")
      }
    }
  }
}

3. Jetpack Compose UI

@Composable
fun MainScreen() {
  val navController = rememberNavController()

  NavHost(navController = navController, startDestination = "home") {
    composable("home") { HomeScreen(navController) }
    composable("profile") { ProfileScreen(navController) }
    composable("details/{itemId}") { backStackEntry ->
      val itemId = backStackEntry.arguments?.getString("itemId") ?: return@composable
      DetailsScreen(itemId = itemId, navController = navController)
    }
  }
}

@Composable
fun HomeScreen(navController: NavController) {
  val viewModel: ItemsViewModel = hiltViewModel()
  val items by viewModel.items.collectAsState()
  val isLoading by viewModel.isLoading.collectAsState()

  LaunchedEffect(Unit) {
    viewModel.fetchItems()
  }

  Scaffold(
    topBar = { TopAppBar(title = { Text("Items") }) }
  ) { paddingValues ->
    if (isLoading) {
      Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        CircularProgressIndicator()
      }
    } else {
      LazyColumn(
        modifier = Modifier
          .padding(paddingValues)
          .fillMaxSize(),
        contentPadding = PaddingValues(8.dp)
      ) {
        items(items) { item ->
          ItemCard(
            item = item,
            onClick = { navController.navigate("details/${item.id}") }
          )
        }
      }
    }
  }
}

@Composable
fun ItemCard(item: Item, onClick: () -> Unit) {
  Card(
    modifier = Modifier
      .fillMaxWidth()
      .padding(8.dp)
      .clickable { onClick() }
  ) {
    Row(modifier = Modifier.padding(16.dp)) {
      Column(modifier = Modifier.weight(1f)) {
        Text(text = item.title, style = MaterialTheme.typography.headlineSmall)
        Text(text = item.description, style = MaterialTheme.typography.bodyMedium)
        Text(text = "$${item.price}", style = MaterialTheme.typography.bodySmall)
      }
      Icon(imageVector = Icons.Default.ArrowForward, contentDescription = null)
    }
  }
}

@Composable
fun ProfileScreen(navController: NavController) {
  val viewModel: UserViewModel = hiltViewModel()
  val user by viewModel.user.collectAsState()
  val isLoading by viewModel.isLoading.collectAsState()

  LaunchedEffect(Unit) {
    viewModel.fetchUser("current-user")
  }

  Scaffold(
    topBar = { TopAppBar(title = { Text("Profile") }) }
  ) { paddingValues ->
    if (isLoading) {
      Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        CircularProgressIndicator()
      }
    } else if (user != null) {
      Column(
        modifier = Modifier
          .padding(paddingValues)
          .fillMaxSize()
          .padding(16.dp)
      ) {
        Text(text = user!!.name, style = MaterialTheme.typography.headlineMedium)
        Text(text = user!!.email, style = MaterialTheme.typography.bodyMedium)

        Spacer(modifier = Modifier.height(24.dp))

        Button(
          onClick = { viewModel.logout() },
          modifier = Modifier.fillMaxWidth()
        ) {
          Text("Logout")
        }
      }
    }
  }
}

@Composable
fun DetailsScreen(itemId: String, navController: NavController) {
  Scaffold(
    topBar = {
      TopAppBar(
        title = { Text("Details") },
        navigationIcon = {
          IconButton(onClick = { navController.popBackStack() }) {
            Icon(Icons.Default.ArrowBack, contentDescription = "Back")
          }
        }
      )
    }
  ) { paddingValues ->
    Column(
      modifier = Modifier
        .padding(paddingValues)
        .fillMaxSize()
        .padding(16.dp),
      horizontalAlignment = Alignment.CenterHorizontally,
      verticalArrangement = Arrangement.Center
    ) {
      Text("Item ID: $itemId", style = MaterialTheme.typography.headlineSmall)
    }
  }
}

Best Practices

✅ DO

  • Use Kotlin for all new Android code
  • Implement MVVM with Jetpack libraries
  • Use Jetpack Compose for UI development
  • Leverage coroutines for async operations
  • Use Room for local data persistence
  • Implement proper error handling
  • Use Hilt for dependency injection
  • Use StateFlow for reactive state
  • Test on multiple device types
  • Follow Android design guidelines

❌ DON'T

  • Store tokens in SharedPreferences
  • Make network calls on main thread
  • Ignore lifecycle management
  • Skip null safety checks
  • Hardcode strings and resources
  • Ignore configuration changes
  • Store passwords in code
  • Deploy without device testing
  • Use deprecated APIs
  • Accumulate memory leaks

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/android-kotlin-development-msba3n0n/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/android-kotlin-development-msba3n0n' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/android-kotlin-development-msba3n0n/download' --output '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md.part' && mv '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md.part' '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && test -s '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/android-kotlin-development' && cp -R '.skill-downloads/android-kotlin-development-msba3n0n' '.claude/skills/android-kotlin-development'

Cursor
test -f '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && test -s '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/android-kotlin-development' && cp -R '.skill-downloads/android-kotlin-development-msba3n0n' '.cursor/skills/android-kotlin-development'

Codex
test -f '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && test -s '.skill-downloads/android-kotlin-development-msba3n0n/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/android-kotlin-development' && cp -R '.skill-downloads/android-kotlin-development-msba3n0n' '.agents/skills/android-kotlin-development'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/android-kotlin-development-msba3n0n'

openskills read 'android-kotlin-development-msba3n0n'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/android-kotlin-development-msba3n0n/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills