Kotlin and Android Fundamentals¶
★★★★★ Intermediate
Kotlin is the primary language for Android development. This entry covers Kotlin language basics, Android project structure, build.gradle configuration, Activities, Fragments, navigation, and UI components.
Key Facts¶
data classauto-generatesequals(),hashCode(),toString(),copy()companion objectprovides static-like members accessed via class namelateinit vardeclares a non-nullable property initialized later (not in constructor)@Volatileguarantees value is read from main memory, not CPU cache - critical for singletonssynchronized(this)prevents simultaneous access from multiple threads- Android uses XML layouts (
res/layout/) while SwiftUI uses declarative code SharedPreferencesprovides simple key-value persistent storage
Patterns¶
Project Structure¶
app/
src/main/
java/com.example.app/
di/ - dependency injection (Dagger 2)
database/ - Room entities, DAOs, database class
rest/ - Retrofit API interfaces and data models
fragments/ - Fragment classes
activities/ - Activity classes
viewmodel/ - ViewModel classes
res/
layout/ - XML layout files
drawable/ - images and vector assets
values/ - strings.xml, colors.xml, styles.xml
menu/ - menu XML files
navigation/ - Navigation graph XML
build.gradle - module-level dependencies
build.gradle - project-level repository settings
build.gradle Key Dependencies¶
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.example.app"
minSdkVersion 21
targetSdkVersion 34
}
}
dependencies {
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
// Architecture components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0"
// Room
implementation "androidx.room:room-runtime:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
// Retrofit + Gson
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
// Glide (image loading)
implementation "com.github.bumptech.glide:glide:4.15.0"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.6.0"
implementation "androidx.navigation:navigation-ui-ktx:2.6.0"
// RecyclerView
implementation "androidx.recyclerview:recyclerview:1.3.0"
}
data class¶
data class GeckoCoin(
val id: String,
val symbol: String,
val name: String,
val image: String,
val current_price: Float,
val market_cap: Float,
val market_cap_rank: Int
)
Fragment Lifecycle¶
class RecordFragment : Fragment() {
private lateinit var viewModel: RecordViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_record, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val application = requireNotNull(activity).application
viewModel = ViewModelProviders.of(this,
RecordViewModelFactory(application))
.get(RecordViewModel::class.java)
viewModel.elapsedTime.observe(viewLifecycleOwner, Observer { time ->
timerText.text = time
})
}
}
Activity Navigation¶
// Start another Activity
val intent = Intent(this, SettingsActivity::class.java)
intent.putExtra("KEY", value)
startActivity(intent)
// Receive in destination
val value = intent.getStringExtra("KEY")
// Go back
finish()
Navigation Component¶
<!-- nav_graph.xml -->
<navigation app:startDestination="@id/recordFragment">
<fragment android:id="@+id/recordFragment"
android:name=".record.RecordFragment" />
<fragment android:id="@+id/listFragment"
android:name=".list.ListFragment" />
</navigation>
// Navigate between fragments
findNavController().navigate(R.id.action_listFragment_to_playerFragment)
// With arguments
val bundle = bundleOf("itemPath" to filePath)
findNavController().navigate(R.id.action, bundle)
// Receive arguments
val itemPath = arguments?.getString("itemPath")
ConstraintLayout XML¶
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnAction"
app:layout_constraintTop_toBottomOf="@id/tvTitle"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerView Adapter¶
class CurrencyAdapter(
private var items: List<GeckoCoin>,
private val context: Context
) : RecyclerView.Adapter<CurrencyAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvName: TextView = itemView.findViewById(R.id.tvName)
val ivIcon: ImageView = itemView.findViewById(R.id.ivIcon)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.recycler_view_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val coin = items[position]
holder.tvName.text = coin.name
Glide.with(context).load(coin.image).into(holder.ivIcon)
holder.itemView.setOnClickListener { /* handle click */ }
}
override fun getItemCount() = items.size
fun updateItems(newItems: List<GeckoCoin>) {
items = newItems
notifyDataSetChanged()
}
}
Setup in Fragment¶
val adapter = CurrencyAdapter(emptyList(), requireContext())
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = adapter
viewModel.coins.observe(viewLifecycleOwner) { coins ->
adapter.updateItems(coins)
}
AlertDialog¶
AlertDialog.Builder(activity)
.setTitle(R.string.dialog_title)
.setMessage(R.string.dialog_message)
.setPositiveButton(R.string.yes) { dialog, _ -> dialog.cancel() }
.setNegativeButton(R.string.no) { dialog, _ -> dialog.cancel() }
.show()
SharedPreferences¶
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putLong("TRIGGER_TIME", value).apply() // write
val time = prefs.getLong("TRIGGER_TIME", 0L) // read with default
Toast¶
String Resources¶
<!-- res/values/strings.xml -->
<resources>
<string name="app_name">MyApp</string>
<string name="recording_started">Recording started</string>
</resources>
Glide (Image Loading)¶
Glide.with(context)
.load(imageUrl)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error_image)
.into(imageView)
Gotchas¶
lateinitproperties crash withUninitializedPropertyAccessExceptionif accessed before initializationnotifyDataSetChanged()redraws all items - useDiffUtilfor efficient partial updates in productiononActivityCreated()is deprecated - useonViewCreated()for modern Fragment lifecycle- Android XML layouts are verbose compared to SwiftUI declarative code
kaptplugin required for annotation processing (Room, Dagger) - without it, code generation failsstartActivityForResult()is deprecated - use Activity Result API in modern code
See Also¶
- android mvvm architecture - ViewModel, LiveData, Repository pattern
- android room database - Room persistence framework
- android retrofit networking - Retrofit REST API client
- android dagger dependency injection - Dagger 2 DI setup