Skip to content
Open
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
11 changes: 10 additions & 1 deletion app/src/main/kotlin/io/github/asutorufa/yuhaiin/compose/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
Expand All @@ -27,6 +28,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import io.github.asutorufa.yuhaiin.MainActivity
import io.github.asutorufa.yuhaiin.MainApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext


@Composable
Expand Down Expand Up @@ -78,14 +81,20 @@ fun Main(activity: MainActivity) {

NavHost(navController, "Home") {
composable("Home") {
val addresses by produceState(initialValue = emptyList<String>()) {
value = withContext(Dispatchers.IO) {
MainApplication.getAddresses()
}
}
Comment on lines +84 to +88
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

While using produceState is a valid way to handle this asynchronous operation, for better separation of concerns and alignment with recommended Android architecture, consider moving this data-fetching logic into a ViewModel.

A ViewModel would survive configuration changes and can be scoped to the navigation graph to retain data across screen transitions. It would hold the state and expose it as a StateFlow, which the Composable can then collect.

Example:

// In a new HomeViewModel.kt
class HomeViewModel : ViewModel() {
    val addresses: StateFlow<List<String>> = flow {
        emit(MainApplication.getAddresses())
    }.flowOn(Dispatchers.IO)
     .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
     )
}

// In Main.kt, you would get the ViewModel and collect the state
@Composable
fun HomeRoute(
    // Assuming you have viewmodel-compose dependency
    viewModel: HomeViewModel = viewModel() 
) {
    val addresses by viewModel.addresses.collectAsState()
    // ... pass addresses to SettingCompose
}

This approach makes your UI component (composable("Home")) more passive and easier to test, as the logic is now encapsulated in the ViewModel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

medium

While using produceState is a valid way to handle this asynchronous operation, for better separation of concerns and alignment with recommended Android architecture, consider moving this data-fetching logic into a ViewModel.

A ViewModel would survive configuration changes and can be scoped to the navigation graph to retain data across screen transitions. It would hold the state and expose it as a StateFlow, which the Composable can then collect.

Example:

// In a new HomeViewModel.kt
class HomeViewModel : ViewModel() {
    val addresses: StateFlow<List<String>> = flow {
        emit(MainApplication.getAddresses())
    }.flowOn(Dispatchers.IO)
     .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
     )
}

// In Main.kt, you would get the ViewModel and collect the state
@Composable
fun HomeRoute(
    // Assuming you have viewmodel-compose dependency
    viewModel: HomeViewModel = viewModel() 
) {
    val addresses by viewModel.addresses.collectAsState()
    // ... pass addresses to SettingCompose
}

This approach makes your UI component (composable("Home")) more passive and easier to test, as the logic is now encapsulated in the ViewModel.

@jules

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for the suggestion! While I agree that using a ViewModel would be a cleaner architectural choice for more complex screens, the current codebase does not appear to use ViewModels at all. My goal was to provide a minimally invasive performance fix that aligns with the existing architecture. Given that the Home screen is relatively simple and produceState effectively offloads the blocking syscall to Dispatchers.IO, I believe this solution is appropriate for now. If the project decides to adopt a ViewModel-based architecture in the future, this would be a great candidate for migration.


SettingCompose(
navController = navController,
vpnState = vpnState,
stopService = { activity.vpnBinder?.stop() },
startService = { activity.startService() },
animatedContentScope = this@composable,
store = MainApplication.store,
addresses = MainApplication.getAddresses(),
addresses = addresses,
)
}

Expand Down