Android में Kotlin Coroutines
Coroutines क्यों?
Android screen को main thread पर draw करता है। वहाँ database load या API call करें तो app अटक जाती है; ~5 seconds बाद Android "App isn't responding" (ANR) दिखाता है। Coroutines background काम को normal ऊपर-से-नीचे code में लिखने देती हैं — बिना callbacks।
Main thread block
- Main पर Thread.sleep / network
- UI अटका, taps काम नहीं करते
- ANR dialog
Coroutine
- suspend fun loadNotes()
- इंतज़ार में thread free
- UI smooth, result बाद में
suspend functions और launch
ये examples plain Kotlin हैं और outputs असली हैं। App में runBlocking की जगह viewModelScope use करेंगे।
import kotlinx.coroutines.*
suspend fun loadNotes(): List<String> {
delay(1000) // pretend database / network work
return listOf("Maths homework", "Physics notes")
}
fun main() = runBlocking {
println("1. Start (thread: main)")
val job = launch {
val notes = loadNotes() // suspends, does NOT block the thread
println("3. Loaded ${notes.size} notes")
}
println("2. UI is still free while loading…")
job.join()
println("4. Done")
}async से parallel काम
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun getProfile(): String { delay(1000); return "Riya" }
suspend fun getMarks(): Int { delay(1000); return 92 }
fun main() = runBlocking {
val sequential = measureTimeMillis {
val p = getProfile(); val m = getMarks()
println("Sequential: $p $m")
}
val parallel = measureTimeMillis {
val p = async { getProfile() }
val m = async { getMarks() }
println("Parallel: ${p.await()} ${m.await()}")
}
println("sequential ≈ ${sequential / 1000}s, parallel ≈ ${parallel / 1000}s")
}Dispatchers और withContext
| Dispatcher | किसके लिए |
|---|---|
| Dispatchers.Main | Views update करना (viewModelScope / lifecycleScope में default) |
| Dispatchers.IO | Files, plain JDBC/SQLite, blocking network libraries |
| Dispatchers.Default | CPU-heavy काम: बड़ी lists sort, parsing, image processing |
import kotlinx.coroutines.*
fun main() = runBlocking {
val result = withContext(Dispatchers.Default) { // heavy CPU work off the main thread
(1..1_000_000).sumOf { it.toLong() }
}
println("Sum = $result")
try {
withTimeout(500) { delay(2000); println("never printed") }
} catch (e: TimeoutCancellationException) {
println("Timed out after 500 ms")
}
val job = launch { repeat(10) { i -> println("tick $i"); delay(300) } }
delay(700)
job.cancel() // like leaving the screen
println("Cancelled: ${job.isCancelled}")
}Android scopes
// ViewModel – cancelled automatically when the ViewModel is cleared
class NoteViewModel(app: Application) : AndroidViewModel(app) {
fun save(note: Note) = viewModelScope.launch {
repo.save(note) // Room suspend function
}
}
// Fragment / Activity – cancelled when the lifecycle is destroyed
viewLifecycleOwner.lifecycleScope.launch {
val note = viewModel.getNote(id) // suspend
binding.etTitle.setText(note?.title)
}
// Collect a Flow only while the screen is visible
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.results.collect { adapter.submitList(it) }
}
}Timeouts और cancellation
जब scope cancel होता है (user screen छोड़ता है), उसकी सारी coroutines अगले suspension point पर रुक जाती हैं — कोई leak नहीं, मरे views को update नहीं। withTimeout धीमा काम रोकता है।
Errors handle करना
viewModelScope.launch {
_tip.value = TipState.Loading
_tip.value = try {
val tips = TipsApi.service.getTips().tips // may throw IOException / HttpException
TipState.Success(tips.random())
} catch (e: Exception) {
TipState.Error(e.message ?: "Network error")
}
}Student Diary का असली example
// AddEditNoteFragment – copy the picked photo on a background thread
private fun copyImage(uri: Uri) {
viewLifecycleOwner.lifecycleScope.launch {
val path = withContext(Dispatchers.IO) { // file work → IO thread
ImageStore.copyToAppStorage(requireContext(), uri)
}
if (path == null) toast(R.string.image_error) else { imagePath = path; showImage() } // back on Main
}
}
// ReminderWorker – WorkManager's CoroutineWorker runs doWork() as a suspend function
override suspend fun doWork(): Result {
val note = AppDatabase.get(applicationContext).noteDao().getById(noteId) ?: return Result.success()
NotificationHelper.show(applicationContext, note.id, note.title, note.subject)
return Result.success()
}आम गलतियाँ
GlobalScope.launchuse करना → screen जाने के बाद भी काम चलता रहता है।- Coroutine में
Thread.sleep→ thread block;delayuse करें। CancellationExceptionपकड़कर ignore करना → coroutine cancel नहीं हो पाती। इसे re-throw करें (या specific exceptions पकड़ें)।Dispatchers.IOसे view update → crash; Main पर लौटें (पहलेwithContextblock खत्म करें)।
सारांश
suspendfunctions बिना block किए रुकते हैं;launchयाasyncसे शुरू करें।- UI के लिए Main, files/blocking I/O के लिए IO, CPU काम के लिए Default;
withContextसे बदलें। viewModelScopeऔरlifecycleScopeuse करें; risky calls try/catch में रखें।
ViewModel में "profile" और "marks" (delay वाले दो suspend functions) async से parallel load कीजिए, loading के दौरान progress bar दिखाइए, और किसी के throw करने पर error Snackbar दिखाइए।
