Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ package template.core.base.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -97,4 +100,18 @@ abstract class BaseViewModel<S, E, A>(
protected fun sendEvent(event: E) {
viewModelScope.launch { eventChannel.send(event) }
}

/**
* Launches a coroutine on [Dispatchers.Default] for network or database operations.
* Use this instead of `viewModelScope.launch` for any I/O-bound work to avoid
* blocking the main thread and causing UI freezes.
*
* Note: Uses Default dispatcher for multiplatform compatibility (IO is not available on JS).
*
* @param block The suspending block to execute on the background dispatcher.
* @return The [Job] representing the coroutine.
*/
protected fun launchIO(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(Dispatchers.Default, block = block)
}
Comment on lines +104 to +116
Copy link

@coderabbitai coderabbitai bot Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the BaseViewModel.kt file
find . -name "BaseViewModel.kt" -type f

Repository: openMF/kmp-project-template

Length of output: 146


🏁 Script executed:

# Search for launchIO function definition and any usages
rg -n "launchIO" --type=kt -B 2 -A 5

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Check the specific file to see the actual current state
cat -n core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt | sed -n '100,120p'

Repository: openMF/kmp-project-template

Length of output: 950


🏁 Script executed:

# Search for viewModelScope.launch usage patterns in ViewModels to understand practices
rg -n "viewModelScope\.launch\|launchIO" --type=kt -C 2 | head -100

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Search for launchIO usages without type filter
rg -n "launchIO" -C 3

Repository: openMF/kmp-project-template

Length of output: 916


🏁 Script executed:

# Search for Dispatchers.Default and Dispatchers.IO usage to understand context
rg -n "Dispatchers\." --glob="*.kt" --glob="*.kts" | head -50

Repository: openMF/kmp-project-template

Length of output: 3685


🏁 Script executed:

# Check for any blocking I/O patterns (Thread.sleep, file I/O, etc) in ViewModels
rg -n "Thread\.sleep|readText|readBytes|blockingGet|runBlocking" --glob="*.kt" --glob="*.kts" | head -30

Repository: openMF/kmp-project-template

Length of output: 510


🏁 Script executed:

# Examine DispatchManagerImpl to understand the dispatcher abstraction
cat -n core-base/common/src/nonAndroidMain/kotlin/template/core/base/common/manager/DispatchManagerImpl.kt

Repository: openMF/kmp-project-template

Length of output: 1383


🏁 Script executed:

# Check androidMain version
cat -n core-base/common/src/androidMain/kotlin/template/core/base/common/manager/DispatchManagerImpl.kt

Repository: openMF/kmp-project-template

Length of output: 1378


🏁 Script executed:

# Check if BaseViewModel has access to DispatchManager or similar abstractions
rg -n "DispatchManager\|CoroutineDispatcher" core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt -B 3 -A 3

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

# Check full BaseViewModel to understand its dependencies
cat -n core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt | head -50

Repository: openMF/kmp-project-template

Length of output: 2401


🏁 Script executed:

# Check the DispatcherManager interface
rg -n "interface DispatcherManager" -A 10

Repository: openMF/kmp-project-template

Length of output: 1380


🏁 Script executed:

# Check if BaseViewModel should have access to DispatcherManager
rg -n "DispatcherManager" --glob="*.kt" | grep -E "(inject|provide|Module)" | head -20

Repository: openMF/kmp-project-template

Length of output: 1032


🏁 Script executed:

# Look for imports of DispatcherManager in UI layer
rg -n "DispatcherManager" core-base/ui/ -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

# Check if there's a pattern for injecting DispatcherManager into ViewModels
rg -n "ViewModel" core-base/common/src/commonMain/kotlin/template/core/base/common/manager/ -A 5 -B 5

Repository: openMF/kmp-project-template

Length of output: 53


launchIO name and KDoc are semantically incorrect for Dispatchers.Default.

Dispatchers.Default is a CPU-bound dispatcher (thread pool capped at CPU cores). Dispatchers.IO — available on JVM and Native targets — is the dispatcher designed for offloading blocking I/O-intensive operations. The codebase already has a DispatcherManager abstraction that handles this: on Android, manager.default maps to Dispatchers.IO; on non-Android platforms, it falls back to Dispatchers.Default for compatibility.

Two concrete problems with the current implementation:

  1. Name launchIO implies IO-dispatcher semantics. Developers reading call sites will expect optimal behavior for I/O operations (on Android, the 64-thread IO pool); instead they get a pool capped at CPU cores. A name like launchBackground or launchOnDefault would be accurate.

  2. KDoc is inaccurate and dangerous. The comment reads "for network or database operations" and "I/O-bound work" — the exact description of Dispatchers.IO. If a developer follows the KDoc literally and performs blocking I/O (e.g., legacy JDBC, File.readText) inside launchIO, Dispatchers.Default's small thread pool will be exhausted, starving all coroutines in the app.

    Additionally, the function bypasses the existing DispatcherManager abstraction, using raw Dispatchers.Default instead of injecting the platform-appropriate dispatcher.

✏️ Suggested rename and corrected KDoc
-    /**
-     * Launches a coroutine on [Dispatchers.Default] for network or database operations.
-     * Use this instead of `viewModelScope.launch` for any I/O-bound work to avoid
-     * blocking the main thread and causing UI freezes.
-     *
-     * Note: Uses Default dispatcher for multiplatform compatibility (IO is not available on JS).
-     *
-     * `@param` block The suspending block to execute on the background dispatcher.
-     * `@return` The [Job] representing the coroutine.
-     */
-    protected fun launchIO(block: suspend CoroutineScope.() -> Unit): Job {
-        return viewModelScope.launch(Dispatchers.Default, block = block)
-    }
+    /**
+     * Launches a coroutine on [Dispatchers.Default] for background work such as
+     * calling suspending repository or data-source APIs off the main thread.
+     *
+     * **Important:** The [block] must only invoke *suspending* (non-blocking) functions.
+     * Performing blocking I/O (e.g. `Thread.sleep`, blocking file/network APIs) will
+     * exhaust the Default dispatcher's CPU-sized thread pool and starve other coroutines.
+     *
+     * [Dispatchers.Default] is used instead of [Dispatchers.IO] for Kotlin Multiplatform
+     * compatibility — `Dispatchers.IO` is not available on JS/WasmJS targets.
+     *
+     * `@param` block The suspending block to execute on the background dispatcher.
+     * `@return` The [Job] representing the coroutine.
+     */
+    protected fun launchBackground(block: suspend CoroutineScope.() -> Unit): Job {
+        return viewModelScope.launch(Dispatchers.Default, block = block)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt`
around lines 104 - 116, The function launchIO currently uses Dispatchers.Default
and has misleading KDoc; rename launchIO to launchBackground (or
launchOnDefault), update its KDoc to state it launches on the
platform-appropriate background dispatcher via DispatcherManager.default (not
"I/O-bound" wording), and change the implementation to use
dispatcherManager.default (e.g.,
viewModelScope.launch(dispatcherManager.default, block = block)) so callers get
the correct dispatcher mapping across platforms; update any call sites of
launchIO to the new name.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@therajanmaurya This review is correct, and ViewModelScope already bounded to the lifecycle of the app with the required dispatcher, and we won't need this anti-pattern, and hardcoding the dispatcher will cause the codebase to test as while testing we need to swap out with unconfined dispather.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

}
Loading