fix: share ConfigViewModel via nested NavGraph to prevent infinite spinner
List and Detail screens were creating separate ViewModel instances, causing a race condition where Detail's schema was always null. Using a nested navigation graph scopes a single ViewModel to the parent back stack entry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
790cd6a411
commit
86550e0ab6
4 changed files with 38 additions and 20 deletions
|
|
@ -9,18 +9,22 @@ import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.navigation
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import io.clawdroid.backend.config.ConfigSectionDetailScreen
|
import io.clawdroid.backend.config.ConfigSectionDetailScreen
|
||||||
import io.clawdroid.backend.config.ConfigSectionListScreen
|
import io.clawdroid.backend.config.ConfigSectionListScreen
|
||||||
|
import io.clawdroid.backend.config.ConfigViewModel
|
||||||
import io.clawdroid.core.ui.theme.ClawDroidTheme
|
import io.clawdroid.core.ui.theme.ClawDroidTheme
|
||||||
import io.clawdroid.feature.chat.screen.ChatScreen
|
import io.clawdroid.feature.chat.screen.ChatScreen
|
||||||
import io.clawdroid.feature.chat.screen.SettingsScreen
|
import io.clawdroid.feature.chat.screen.SettingsScreen
|
||||||
import io.clawdroid.navigation.NavRoutes
|
import io.clawdroid.navigation.NavRoutes
|
||||||
import io.clawdroid.settings.AppSettingsScreen
|
import io.clawdroid.settings.AppSettingsScreen
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
|
@ -48,22 +52,37 @@ class MainActivity : ComponentActivity() {
|
||||||
onNavigateToAppSettings = { navController.navigate(NavRoutes.APP_SETTINGS) },
|
onNavigateToAppSettings = { navController.navigate(NavRoutes.APP_SETTINGS) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(NavRoutes.BACKEND_SETTINGS) {
|
navigation(
|
||||||
ConfigSectionListScreen(
|
route = NavRoutes.BACKEND_SETTINGS,
|
||||||
onNavigateBack = { navController.popBackStack() },
|
startDestination = NavRoutes.BACKEND_SETTINGS_LIST,
|
||||||
onSectionSelected = { sectionKey ->
|
) {
|
||||||
navController.navigate("backend_settings/$sectionKey")
|
composable(NavRoutes.BACKEND_SETTINGS_LIST) { entry ->
|
||||||
},
|
val parentEntry = remember(entry) {
|
||||||
)
|
navController.getBackStackEntry(NavRoutes.BACKEND_SETTINGS)
|
||||||
}
|
}
|
||||||
composable(
|
val viewModel: ConfigViewModel = koinViewModel(viewModelStoreOwner = parentEntry)
|
||||||
NavRoutes.BACKEND_SETTINGS_SECTION,
|
ConfigSectionListScreen(
|
||||||
arguments = listOf(navArgument("sectionKey") { type = NavType.StringType }),
|
onNavigateBack = { navController.popBackStack() },
|
||||||
) { backStackEntry ->
|
onSectionSelected = { sectionKey ->
|
||||||
ConfigSectionDetailScreen(
|
navController.navigate("backend_settings/$sectionKey")
|
||||||
sectionKey = backStackEntry.arguments?.getString("sectionKey") ?: "",
|
},
|
||||||
onNavigateBack = { navController.popBackStack() },
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
NavRoutes.BACKEND_SETTINGS_SECTION,
|
||||||
|
arguments = listOf(navArgument("sectionKey") { type = NavType.StringType }),
|
||||||
|
) { entry ->
|
||||||
|
val parentEntry = remember(entry) {
|
||||||
|
navController.getBackStackEntry(NavRoutes.BACKEND_SETTINGS)
|
||||||
|
}
|
||||||
|
val viewModel: ConfigViewModel = koinViewModel(viewModelStoreOwner = parentEntry)
|
||||||
|
ConfigSectionDetailScreen(
|
||||||
|
sectionKey = entry.arguments?.getString("sectionKey") ?: "",
|
||||||
|
onNavigateBack = { navController.popBackStack() },
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(NavRoutes.APP_SETTINGS) {
|
composable(NavRoutes.APP_SETTINGS) {
|
||||||
AppSettingsScreen(
|
AppSettingsScreen(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ object NavRoutes {
|
||||||
const val CHAT = "chat"
|
const val CHAT = "chat"
|
||||||
const val SETTINGS = "settings"
|
const val SETTINGS = "settings"
|
||||||
const val BACKEND_SETTINGS = "backend_settings"
|
const val BACKEND_SETTINGS = "backend_settings"
|
||||||
|
const val BACKEND_SETTINGS_LIST = "backend_settings/list"
|
||||||
const val BACKEND_SETTINGS_SECTION = "backend_settings/{sectionKey}"
|
const val BACKEND_SETTINGS_SECTION = "backend_settings/{sectionKey}"
|
||||||
const val APP_SETTINGS = "app_settings"
|
const val APP_SETTINGS = "app_settings"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,13 @@ import io.clawdroid.core.ui.theme.NeonCyan
|
||||||
import io.clawdroid.core.ui.theme.TextPrimary
|
import io.clawdroid.core.ui.theme.TextPrimary
|
||||||
import io.clawdroid.core.ui.theme.TextSecondary
|
import io.clawdroid.core.ui.theme.TextSecondary
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigSectionDetailScreen(
|
fun ConfigSectionDetailScreen(
|
||||||
sectionKey: String,
|
sectionKey: String,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: ConfigViewModel = koinViewModel(),
|
viewModel: ConfigViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
|
||||||
|
|
@ -42,14 +42,13 @@ import io.clawdroid.core.ui.theme.GlassWhite
|
||||||
import io.clawdroid.core.ui.theme.NeonCyan
|
import io.clawdroid.core.ui.theme.NeonCyan
|
||||||
import io.clawdroid.core.ui.theme.TextPrimary
|
import io.clawdroid.core.ui.theme.TextPrimary
|
||||||
import io.clawdroid.core.ui.theme.TextSecondary
|
import io.clawdroid.core.ui.theme.TextSecondary
|
||||||
import org.koin.androidx.compose.koinViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigSectionListScreen(
|
fun ConfigSectionListScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onSectionSelected: (sectionKey: String) -> Unit,
|
onSectionSelected: (sectionKey: String) -> Unit,
|
||||||
viewModel: ConfigViewModel = koinViewModel(),
|
viewModel: ConfigViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue