The morning starts not with coffee, but with a Merge Request
\n\n7:45 AM. The phone vibrates — it's not an alarm, it's GitLab sending a notification: your yesterday's Merge Request passed the CI/CD pipeline. While the kettle is boiling, I open my laptop and see a green checkmark. First victory of the day. Good morning, Kotlin.
\n\nMy name is Alexey, I'm a Kotlin developer at a product company. For the last three years, I've been writing in Kotlin for Android and a bit on the backend with Ktor. If you think a programmer's job is an endless "Hello World", then today I will shatter that myth. Welcome to my real workday.
\n\n8:30 — Daily Standup: synchronization and context
\n\nThe first thing that happens is the daily standup. We gather on Zoom, but without unnecessary "yesterday-today-blockers". We keep it concise: what was done, what's planned, any blockers. Today I'm working on the Push Notifications feature for users.
\n\n— Yesterday I finished refactoring NotificationService, rewrote it with coroutines. Today I'm connecting Firebase Cloud Messaging and testing Flow. No blockers.
By the way, it's during standups that the best architectural ideas are often born. For example, last week a colleague suggested replacing RxJava with Kotlin Flow — and we reduced the amount of boilerplate code by 40%. Here's what it looks like now:
\n\n// Instead of the old RxJava:\n// Observable.just(data).subscribeOn(Schedulers.io())\n\n// Using Kotlin Flow:\nfun observeNotifications(): Flow<Notification> = flow {\n val service = NotificationService()\n while (true) {\n emit(service.getNextNotification())\n delay(5000) // polling every 5 seconds\n }\n}.flowOn(Dispatchers.IO)\n\nThe difference is obvious: readability, type safety, and no callback hell.
\n\n9:15 — Development: writing code in Kotlin
\n\nAfter the standup, I dive into the code. Today's task is to implement processing of incoming push notifications. I'm using Kotlin Coroutines and StateFlow for managing UI state. Here's what my ViewModel looks like:
\n\nclass PushViewModel(\n private val repository: NotificationRepository\n) : ViewModel() {\n\n private val _uiState = MutableStateFlow<PushUiState>(PushUiState.Loading)\n val uiState: StateFlow<PushUiState> = _uiState.asStateFlow()\n\n fun loadNotifications() {\n viewModelScope.launch {\n repository.getNotifications()\n .catch { e ->\n _uiState.value = PushUiState.Error(e.message ?: "Unknown error")\n }\n .collect { notifications ->\n _uiState.value = PushUiState.Success(notifications)\n }\n }\n }\n}\n\nsealed class PushUiState {\n object Loading : PushUiState()\n data class Success(val data: List<Notification>) : PushUiState()\n data class Error(val message: String) : PushUiState()\n}\n\nNotice: I'm using a sealed class for UI state. This is a standard practice in modern Android development with Kotlin. No if-else chains — just pure typing.
\n\nBy the way, if you're new to Kotlin, I recommend paying attention to Scope functions: let, apply, also. They make code more concise. For example:
// Without scope functions\nval user = User()\nuser.name = "Alex"\nuser.age = 30\nsaveUser(user)\n\n// With apply\nval user = User().apply {\n name = "Alex"\n age = 30\n}\nsaveUser(user)\n\n11:00 — Code Review: learning from colleagues
\n\nAfter lunch (yes, I eat lunch at 11 — a habit from remote work) I open a Merge Request from a colleague. He's rewriting the authentication module with Kotlin Multiplatform. I read the code and see an interesting point:
\n\n// Was (