สูตรการนำทางแบบมีเงื่อนไข
สูตรนี้แสดงวิธีใช้การนำทางแบบมีเงื่อนไข ซึ่งจะเข้าถึงปลายทางบางอย่างได้ก็ต่อเมื่อเป็นไปตามเงื่อนไข (ในกรณีนี้คือหากผู้ใช้เข้าสู่ระบบ)
วิธีการทำงาน
ตัวอย่างนี้มีProfileปลายทางที่กำหนดให้ผู้ใช้ต้องเข้าสู่ระบบ หากผู้ใช้ไม่ได้เข้าสู่ระบบและพยายามไปยัง Profile ระบบจะเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอ Login หลังจากเข้าสู่ระบบสำเร็จแล้ว ระบบจะนำผู้ใช้ไปยังหน้าจอ Profile โดยอัตโนมัติ
AppBackStack
หัวใจสำคัญของสูตรนี้คือคลาส AppBackStack ที่กำหนดเอง ซึ่งห่อหุ้มตรรกะสำหรับการนำทางแบบมีเงื่อนไข
-
RequiresLoginอินเทอร์เฟซ: ใช้เครื่องหมายอินเทอร์เฟซRequiresLoginเพื่อระบุปลายทางที่กำหนดให้ผู้ใช้ต้องเข้าสู่ระบบ ปลายทางProfileจะใช้การติดตั้งใช้งานอินเทอร์เฟซนี้ -
เปลี่ยนเส้นทางไปยังการเข้าสู่ระบบ: เมื่อมีการเรียกใช้ฟังก์ชัน
addที่มีปลายทางซึ่งใช้RequiresLoginและผู้ใช้ไม่ได้เข้าสู่ระบบAppBackStackจะจัดเก็บปลายทางที่ต้องการและเพิ่มเส้นทางLoginลงในสแต็กย้อนกลับแทน -
การจัดการการเข้าสู่ระบบ: เมื่อมีการเรียกใช้ฟังก์ชัน
loginระบบจะตั้งค่าสถานะของผู้ใช้เป็นเข้าสู่ระบบแล้ว หากมีปลายทางที่จัดเก็บไว้ซึ่งผู้ใช้พยายามเข้าถึง ระบบจะเพิ่มปลายทางนั้นลงในสแต็กย้อนกลับและนำหน้าจอLoginออก -
การจัดการการออกจากระบบ: เมื่อมีการเรียกใช้ฟังก์ชัน
logoutฟังก์ชันนี้จะตั้งค่าสถานะของผู้ใช้เป็นออกจากระบบและนำปลายทางออกจากสแต็กย้อนกลับที่กำหนดให้ผู้ใช้ต้องเข้าสู่ระบบ
วิธีนี้ช่วยให้คุณจัดการการนำทางแบบมีเงื่อนไขได้อย่างราบรื่นด้วยการรวมตรรกะไว้ในการใช้งาน Back Stack ที่กำหนดเอง
/* * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.conditional import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSerializable import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.serialization.NavBackStackSerializer import androidx.navigation3.runtime.serialization.NavKeySerializer import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.content.ContentBlue import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.content.ContentYellow import kotlinx.serialization.Serializable /** * Class for representing navigation keys in the app. * * Note: We use a sealed class because KotlinX Serialization handles * polymorphic serialization of sealed classes automatically. * * @param requiresLogin - true if the navigation key requires that the user is logged in * to navigate to it */ @Serializable sealed class ConditionalNavKey(val requiresLogin: Boolean = false) : NavKey /** * Key representing home screen */ @Serializable private data object Home : ConditionalNavKey() /** * Key representing profile screen that is only accessible once the user has logged in */ @Serializable private data object Profile : ConditionalNavKey(requiresLogin = true) /** * Key representing login screen * * @param redirectToKey - navigation key to redirect to after successful login */ @Serializable private data class Login( val redirectToKey: ConditionalNavKey? = null ) : ConditionalNavKey() class ConditionalActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val backStack = rememberNavBackStack<ConditionalNavKey>(Home) var isLoggedIn by rememberSaveable { mutableStateOf(false) } val navigator = remember { Navigator( backStack = backStack, onNavigateToRestrictedKey = { redirectToKey -> Login(redirectToKey) }, isLoggedIn = { isLoggedIn } ) } NavDisplay( backStack = backStack, onBack = { navigator.goBack() }, entryProvider = entryProvider { entry<Home> { ContentGreen("Welcome to Nav3. Logged in? ${isLoggedIn}") { Column { Button(onClick = dropUnlessResumed { navigator.navigate(Profile) }) { Text("Profile") } Button(onClick = dropUnlessResumed { navigator.navigate(Login()) }) { Text("Login") } } } } entry<Profile> { ContentBlue("Profile screen (only accessible once logged in)") { Button(onClick = dropUnlessResumed { isLoggedIn = false navigator.navigate(Home) }) { Text("Logout") } } } entry<Login> { key -> ContentYellow("Login screen. Logged in? $isLoggedIn") { Button(onClick = dropUnlessResumed { isLoggedIn = true key.redirectToKey?.let { targetKey -> backStack.remove(key) navigator.navigate(targetKey) } }) { Text("Login") } } } } ) } } } // An overload of `rememberNavBackStack` that returns a subtype of `NavKey`. // See https://issuetracker.google.com/issues/463382671 for a discussion of this function @Composable fun <T : NavKey> rememberNavBackStack(vararg elements: T): NavBackStack<T> { return rememberSerializable( serializer = NavBackStackSerializer(elementSerializer = NavKeySerializer()) ) { NavBackStack(*elements) } }
/* * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.conditional import androidx.navigation3.runtime.NavBackStack /** * Provides navigation events with built-in support for conditional access. If the user attempts to * navigate to a [ConditionalNavKey] that requires login ([ConditionalNavKey.requiresLogin] is true) * but is not currently logged in, the Navigator will redirect the user to a login key. * * @property backStack The back stack that is modified by this class * @property onNavigateToRestrictedKey A lambda that is called when the user attempts to navigate * to a key that requires login. This should return the key that represents the login screen. The * user's target key is supplied as a parameter so that after successful login the user can be * redirected to their target destination. * @property isLoggedIn A lambda that returns whether the user is logged in. */ class Navigator( private val backStack: NavBackStack<ConditionalNavKey>, private val onNavigateToRestrictedKey: (targetKey: ConditionalNavKey?) -> ConditionalNavKey, private val isLoggedIn: () -> Boolean, ) { fun navigate(key: ConditionalNavKey) { if (key.requiresLogin && !isLoggedIn()) { val loginKey = onNavigateToRestrictedKey(key) backStack.add(loginKey) } else { backStack.add(key) } } fun goBack() = backStack.removeLastOrNull() }