1. Loading Data in a Fragment
Scenario: Load and display user data from a ViewModel.
lifecycleScope.launchWhenStarted {
viewModel.userData
.filterNotNull()
.collect { user ->
textView.text = user.name
}
}
2. Making a Network Request with Retrofit
Scenario: Fetch user data from an API using Retrofit
lifecycleScope.launch {
runCatching { api.getUser() }
.onSuccess { user ->
textView.text = user.name
}
.onFailure { error ->
// Handle error
}
}
lifecycleScope.launch {
runCatching { api.getData() }
.onSuccess { response ->
if (response.isSuccessful) {
textView.text = response.body()?.data
} else {
showError("Error: ${response.code()}")
}
}
.onFailure {
showError("Unexpected Error")
}
}
3. Handling Button Clicks
Scenario: Respond to a button click event.
button.setOnClickListener {
context?.let {
Toast.makeText(it, "Clicked", Toast.LENGTH_SHORT).show()
}
}
4. Navigating Between Fragments
Scenario: Navigate from one fragment to another using Navigation Component.
val action = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
findNavController().navigate(action)
5. Displaying a List with RecyclerView
Scenario: Display a list of items using RecyclerView.
val adapter = MyAdapter().apply {
submitList(items)
}
recyclerView.adapter = adapter
class MyAdapter : ListAdapter<Item, ItemViewHolder>(ItemDiffCallback()) {
// ...
}
class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
class MyAdapter @Inject constructor() : PagingDataAdapter<Item, MyViewHolder>(DiffCallback()) {
// Integrates with Paging 3 library
}
6. Implementing a Search Feature with Debounce
Scenario: Implement a search functionality that waits for the user to stop typing before making a network request.
// Mid:
private var searchJob: Job? = null
searchEditText.addTextChangedListener {
val query = it.toString()
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(300)
performSearch(query)
}
}
// Senior:
val queryFlow = callbackFlow {
val watcher = searchEditText.addTextChangedListener {
trySend(it.toString())
}
awaitClose { searchEditText.removeTextChangedListener(watcher) }
}
lifecycleScope.launch {
queryFlow
.debounce(300)
.distinctUntilChanged()
.collect { query ->
performSearch(query)
}
}
private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Idle)
val uiState: StateFlow<SearchUiState> = _uiState
private val searchDebounce = MutableStateFlow("")
private fun observeSearch() {
viewModelScope.launch {
searchDebounce
.debounce(300)
.filter { it.isNotBlank() }
.distinctUntilChanged()
.flatMapLatest { query ->
flow {
emit(SearchUiState.Loading)
val results = runCatching { repository.searchDrugs(query) }
.getOrElse { throw it }
emit(SearchUiState.Success(results))
}.catch { e ->
emit(SearchUiState.Error(e.message ?: "Unexpected error"))
}.flowOn(ioDispatcher)
}
.collect { _uiState.value = it }
}
}
07. Managing State with ViewModel and LiveData
Scenario: Manage UI state using ViewModel and LiveData.
// Junior:
var count = 0
fun increment() {
count++
textView.text = count.toString()
}
// Mid:
class MyViewModel : ViewModel() {
val count = MutableLiveData<Int>()
fun increment() {
val current = count.value ?: 0
count.value = current + 1
}
}
// Senior:
class MyViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value += 1
}
}
08. Requesting Permissions
Scenario: Request location permission at runtime.
class PermissionManager(private val activity: Activity) {
fun hasPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED
}
fun requestPermissions(vararg permissions: String, requestCode: Int) {
ActivityCompat.requestPermissions(activity, permissions, requestCode)
}
}
// Usage
if (!permissionManager.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
permissionManager.requestPermissions(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_REQUEST_CODE)
}
09. Data Storage (SharedPreferences vs DataStore)
Scenario: Store user preference like theme or token.
Switches to DataStore and uses Flow for reactivity
class UserPreferences(context: Context) {
private val dataStore = context.createDataStore(name = "user_prefs")
val tokenFlow: Flow<String?> = dataStore.data
.map { it["token"] }
suspend fun saveToken(token: String) {
dataStore.edit {
it["token"] = token
}
}
}
10. Image Loading (with Glide/Coil)
// Mid-level
fun ImageView.loadImage(url: String) {
Glide.with(this.context)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(this)
}
Reusable component with fallback, caching, and testing
consideration.
// Senior
object ImageLoader {
fun load(context: Context, imageView: ImageView, url: String) {
Glide.with(context)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView)
}
}
// Usage
ImageLoader.load(context, imageView, url)
DRY-Compliant Approach:
fun Date.toFormattedString(): String {
val formatter = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
return formatter.format(this)
}
DRY-Compliant Approach:
if (text != null && text.isNotEmpty()) {
// do something
}
fun String?.isNotNullOrEmpty(): Boolean = this != null && this.isNotEmpty()
11. Managing UI Events (e.g., Button Click Debounce)
// Mid-level
button.setOnClickListener(object : View.OnClickListener {
private var lastClickTime = 0L
override fun onClick(v: View?) {
if (System.currentTimeMillis() - lastClickTime < 500) return
lastClickTime = System.currentTimeMillis()
// handle click
}
})
// Senior: Debounce extension function to apply to any View.
fun View.setDebouncedClickListener(delay: Long = 500L, action: () -> Unit) {
var lastClickTime = 0L
setOnClickListener {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime > delay) {
lastClickTime = currentTime
action()
}
}
}
// Usage
button.setDebouncedClickListener {
// handle click
}
12. Handling Null Safety
val name: String = getName() ?: return
println(name.length)
13. Using Extension Functions
fun User.getFullName(): String {
return "$firstName $lastName"
}
// Senior:
val User.fullName: String
get() = "$firstName $lastName"
14. Implementing Singleton Pattern
@Singleton
class DatabaseHelper @Inject constructor() {
// Initialization code
}
15. Using Coroutines for Asynchronous Tasks
Scenario: Fetching data asynchronously.
// Mid-level:
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) { fetchData() }
updateUI(data)
}
// Senior:
viewModelScope.launch {
try {
val data = repository.getData()
_state.value = UiState.Success(data)
} catch (e: Exception) {
_state.value = UiState.Error(e)
}
}
16. Using Sealed Classes for UI State
// Mid-level:
enum class UiState { LOADING, SUCCESS, ERROR }
// Senior:
sealed class UiState {
object Loading : UiState()
data class Success(val data: Data) : UiState()
data class Error(val exception: Throwable) : UiState()
}
17. Handling Configuration Changes
Scenario: Preserving data on rotation.
// Senior:
// Combine ViewModel with SavedStateHandle
17.
🎓 𝐉𝐮𝐧𝐢𝐨𝐫:
val call = api.getUser()
call.enqueue(object : Callback<User> {
override fun onResponse(...) { ... }
override fun onFailure(...) { ... }
})
🧑💻 𝐌𝐢𝐝:
lifecycleScope.launch {
try {
val user = api.getUser()
// Update UI
} catch (e: Exception) {
// Handle Error
}
}
🧙 𝐒𝐞𝐧𝐢𝐨𝐫:
val flow = flow { emit(api.getUser()) }
.flowOn(Dispatchers.IO)
.catch { handleError(it) }
.onEach { updateUI(it) }
lifecycleScope.launchWhenStarted { flow.collect() }
18.
🎓 𝐉𝐮𝐧𝐢𝐨𝐫:
try {
val result = repository.searchDrugs(query) // Might run on Main Thread ❌
searchResults.value = result
} catch (e: Exception) {
errorMessage.value = e.message
} finally {
isLoading.value = false
}
🧑💻 𝐌𝐢𝐝:
runCatching {
repository.searchDrugs(query)
}.onSuccess { results ->
_uiState.value = SearchUiState.Success(results)
}.onFailure { e ->
_uiState.value = SearchUiState.Error(e.message ?: "Unknown error")
}
🧙 𝐒𝐞𝐧𝐢𝐨𝐫:
flow {
emit(SearchUiState.Loading)
val results = runCatching { repository.searchDrugs(query) }
.getOrElse { throw it }
emit(SearchUiState.Success(results))
}.catch { e ->
emit(SearchUiState.Error(e.message ?: "Unexpected error"))
}.flowOn(ioDispatcher)
Top comments (0)