Android Jetpack Compose - 具有深层链接支持的身份验证导航流程

问题描述 投票:0回答:1

我正在开发一个应用程序,其中有 3 个屏幕:登录屏幕、报价列表屏幕和报价详细信息屏幕。 我想基于用户

isLoggedIn
布尔状态并具有深层链接支持,在登录和报价列表屏幕之间实现简单的条件导航。

我读了一篇好文章并尝试遵循官方导航原则(单一NavHost,固定起始目的地)和深层链接支持。 不幸的是,官方文档并没有完全涵盖它。

这是项目中导航逻辑的基本代码:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)

        setContent {
            val isLoggedIn by viewModel.isLoggedIn.collectAsStateWithLifecycle()
            val navController = rememberNavController()

            // without it the deeplink works
            LaunchedEffect(key1 = isLoggedIn) {
                if (isLoggedIn) {
                    navController.navigate(QuotesNavigationGraph) {
                        popUpTo(0)
                    }
                } else {
                    navController.navigate(LoginNavigationGraph) {
                        popUpTo(0)
                    }
                }
            }

            TemplateTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    NavHost(
                        navController = navController,
                        startDestination = QuotesNavigationGraph
                    ) {
                        navigation(
                            route = LoginNavigationGraph,
                            startDestination = LoginNavigationRoute,
                        ) {
                            loginScreen()
                        }

                        navigation(
                            route = QuotesNavigationGraph,
                            startDestination = QuotesNavigationRoute
                        ) {
                            quotesScreen(
                                onQuoteClick = { quoteId ->
                                    navController.navigateToQuoteDetail(quoteId)
                                }
                            )
                            quoteDetailScreen(
                                onBackClick = {
                                    navController.navigateUp()
                                }
                            )
                        }
                    }
                }
            }
        }
    }
}

@HiltViewModel
class MainViewModel @Inject constructor(
    userDataRepository: UserDataRepository
) : ViewModel() {

    val isLoggedIn = userDataRepository.isLoggedIn
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = true
        )
}
const val LoginNavigationGraph = "login_graph"
const val LoginNavigationRoute = "login_route"

fun NavGraphBuilder.loginScreen() {
    composable(route = LoginNavigationRoute) {
        LoginRoute()
    }
}
const val QuotesNavigationGraph = "quotes_graph"
const val QuotesNavigationRoute = "quotes_route"

fun NavGraphBuilder.quotesScreen(
    onQuoteClick: (Int) -> Unit,
) {
    composable(route = QuotesNavigationRoute) {
        QuotesRoute(
            onQuoteClick = onQuoteClick
        )
    }
}
const val QuoteDetailNavigationRoute = "quote_detail_route"
const val QuoteIdArg = "quoteId"

fun NavController.navigateToQuoteDetail(
    quoteId: Int = -1,
    navOptions: NavOptions? = null
) {
    this.navigate(
        QuoteDetailNavigationRoute.plus("/$quoteId"),
        navOptions
    )
}

fun NavGraphBuilder.quoteDetailScreen(
    onBackClick: () -> Unit
) {
    composable(
        route = QuoteDetailNavigationRoute.plus("/{$QuoteIdArg}"),
        arguments = listOf(
            navArgument(QuoteIdArg) { type = NavType.IntType },
        ),
        deepLinks = listOf(
            navDeepLink {
                uriPattern = "https://template.com/{$QuoteIdArg}"
            }
        ),
    ) {
        QuoteDetailRoute(
            onBackClick = onBackClick
        )
    }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ....
        <activity
            android:name=".feature.MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.Template">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data
                    android:host="template.com"
                    android:scheme="https" />
            </intent-filter>
        </activity>
    </application>

</manifest>

一般来说,几乎所有事情都适用于这种方法:

  • 如果用户
    isLoggedIn == true
    应用程序打开主屏幕 - 行情列表
  • 如果用户
    isLoggedIn == false
    应用程序打开登录屏幕,但显示报价列表一秒钟(这不好)

这种方法的问题:

  • deeplink 不适用于此方法,如果用户
    isLoggedIn == true
    它会打开报价列表而不是报价详细信息屏幕
  • 如果我从 MainActivity 中删除 LaunchedEffect 块,则深层链接会按预期打开报价详细信息屏幕,但它会破坏登录导航流程

所以我的问题是:

  1. 我是否以正确的方式实现应用程序导航?
  2. 如果我不这样做,你能推荐一些好的解决方案/示例/项目/库,可以通过深度链接支持覆盖登录流程,并且遵循官方导航原则吗?

我想要实现的目标:

  • 如果用户
    isLoggedIn == true
    应用程序打开主屏幕 - 行情列表
  • 如果用户
    isLoggedIn == false
    应用程序将打开登录屏幕
  • 如果用户
    isLoggedIn == true
    深层链接应打开报价详细信息屏幕
  • 如果用户
    isLoggedIn == false
    应用程序应打开登录屏幕,并且在用户登录后,它应将用户导航至报价详细信息屏幕
android authentication deep-linking jetpack-compose-navigation
1个回答
0
投票

我在我的项目中所做的与你的代码是一样的:

NavHost(
    navController = navController,
    startDestination = if (isLoggedIn) QuotesNavigationGraph else LoginNavigationGraph
) { ... }

我知道这不是一个好的做法,因为会使 startDestination 可变,但在您有更好的解决方案之前,这是一个很好的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.