在设计应用的导航时,您可能希望根据条件逻辑导航到一个目标而不是另一个目标。例如,用户可能会通过深层链接导航到需要用户登录的目标,或者您可能在游戏中为玩家获胜或失败时设置不同的目标。
用户登录
在此示例中,用户尝试导航到需要身份验证的个人资料屏幕。由于此操作需要身份验证,因此如果用户尚未通过身份验证,则应将其重定向到登录屏幕。
此示例的导航图可能如下所示
为了进行身份验证,应用必须导航到login_fragment
,用户可以在其中输入用户名和密码进行身份验证。如果接受,则用户将被送回profile_fragment
屏幕。如果未接受,则用户将收到一条通知,告知其凭据无效,并使用Snackbar
。如果用户在未登录的情况下返回到个人资料屏幕,则他们将被发送到main_fragment
屏幕。
以下是此应用的导航图
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/main_fragment">
<fragment
android:id="@+id/main_fragment"
android:name="com.google.android.conditionalnav.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/navigate_to_profile_fragment"
app:destination="@id/profile_fragment"/>
</fragment>
<fragment
android:id="@+id/login_fragment"
android:name="com.google.android.conditionalnav.LoginFragment"
android:label="login_fragment"
tools:layout="@layout/login_fragment"/>
<fragment
android:id="@+id/profile_fragment"
android:name="com.google.android.conditionalnav.ProfileFragment"
android:label="fragment_profile"
tools:layout="@layout/fragment_profile"/>
</navigation>
MainFragment
包含一个按钮,用户可以点击该按钮以查看其个人资料。如果用户想查看个人资料屏幕,则必须先进行身份验证。此交互使用两个单独的片段建模,但它依赖于共享的用户状态。此状态信息不是这两个片段中的任何一个的责任,更适合保存在共享的UserViewModel
中。通过将其范围限定为活动(它实现ViewModelStoreOwner
),可以在片段之间共享此ViewModel
。在以下示例中,requireActivity()
解析为MainActivity
,因为MainActivity
承载ProfileFragment
Kotlin
class ProfileFragment : Fragment() { private val userViewModel: UserViewModel by activityViewModels() ... }
Java
public class ProfileFragment extends Fragment { private UserViewModel userViewModel; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class); ... } ... }
UserViewModel
中的用户数据通过 LiveData
公开,因此要决定导航到哪里,您应该观察此数据。导航到 ProfileFragment
时,如果存在用户数据,应用程序会显示欢迎消息。如果用户数据为 null
,则导航到 LoginFragment
,因为用户需要在查看其个人资料之前进行身份验证。在您的 ProfileFragment
中定义决定性逻辑,如下例所示
Kotlin
class ProfileFragment : Fragment() { private val userViewModel: UserViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navController = findNavController() userViewModel.user.observe(viewLifecycleOwner, Observer { user -> if (user != null) { showWelcomeMessage() } else { navController.navigate(R.id.login_fragment) } }) } private fun showWelcomeMessage() { ... } }
Java
public class ProfileFragment extends Fragment { private UserViewModel userViewModel; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class); final NavController navController = Navigation.findNavController(view); userViewModel.user.observe(getViewLifecycleOwner(), (Observer<User>) user -> { if (user != null) { showWelcomeMessage(); } else { navController.navigate(R.id.login_fragment); } }); } private void showWelcomeMessage() { ... } }
如果用户到达 ProfileFragment
时用户数据为 null
,则将其重定向到 LoginFragment
。
您可以使用 NavController.getPreviousBackStackEntry()
检索前一个目的地的 NavBackStackEntry
,它封装了该目的地的 NavController
特定状态。 LoginFragment
使用前一个 NavBackStackEntry
的 SavedStateHandle
设置一个初始值,指示用户是否已成功登录。如果用户立即按下系统后退按钮,这就是我们想要返回的状态。使用 SavedStateHandle
设置此状态可确保状态在进程死亡期间持续存在。
Kotlin
class LoginFragment : Fragment() { companion object { const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL" } private val userViewModel: UserViewModel by activityViewModels() private lateinit var savedStateHandle: SavedStateHandle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle savedStateHandle.set(LOGIN_SUCCESSFUL, false) } }
Java
public class LoginFragment extends Fragment { public static String LOGIN_SUCCESSFUL = "LOGIN_SUCCESSFUL" private UserViewModel userViewModel; private SavedStateHandle savedStateHandle; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class); savedStateHandle = Navigation.findNavController(view) .getPreviousBackStackEntry() .getSavedStateHandle(); savedStateHandle.set(LOGIN_SUCCESSFUL, false); } }
一旦用户输入用户名和密码,它们就会传递给 UserViewModel
进行身份验证。如果身份验证成功,UserViewModel
将存储用户数据。然后,LoginFragment
更新 SavedStateHandle
上的 LOGIN_SUCCESSFUL
值,并将其自身从返回堆栈中弹出。
Kotlin
class LoginFragment : Fragment() { companion object { const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL" } private val userViewModel: UserViewModel by activityViewModels() private lateinit var savedStateHandle: SavedStateHandle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle savedStateHandle.set(LOGIN_SUCCESSFUL, false) val usernameEditText = view.findViewById(R.id.username_edit_text) val passwordEditText = view.findViewById(R.id.password_edit_text) val loginButton = view.findViewById(R.id.login_button) loginButton.setOnClickListener { val username = usernameEditText.text.toString() val password = passwordEditText.text.toString() login(username, password) } } fun login(username: String, password: String) { userViewModel.login(username, password).observe(viewLifecycleOwner, Observer { result -> if (result.success) { savedStateHandle.set(LOGIN_SUCCESSFUL, true) findNavController().popBackStack() } else { showErrorMessage() } }) } fun showErrorMessage() { // Display a snackbar error message } }
Java
public class LoginFragment extends Fragment { public static String LOGIN_SUCCESSFUL = "LOGIN_SUCCESSFUL" private UserViewModel userViewModel; private SavedStateHandle savedStateHandle; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class); savedStateHandle = Navigation.findNavController(view) .getPreviousBackStackEntry() .getSavedStateHandle(); savedStateHandle.set(LOGIN_SUCCESSFUL, false); EditText usernameEditText = view.findViewById(R.id.username_edit_text); EditText passwordEditText = view.findViewById(R.id.password_edit_text); Button loginButton = view.findViewById(R.id.login_button); loginButton.setOnClickListener(v -> { String username = usernameEditText.getText().toString(); String password = passwordEditText.getText().toString(); login(username, password); }); } private void login(String username, String password) { userViewModel.login(username, password).observe(viewLifecycleOwner, (Observer<LoginResult>) result -> { if (result.success) { savedStateHandle.set(LOGIN_SUCCESSFUL, true); NavHostFragment.findNavController(this).popBackStack(); } else { showErrorMessage(); } }); } private void showErrorMessage() { // Display a snackbar error message } }
请注意,所有与身份验证相关的逻辑都保存在 UserViewModel
中。这很重要,因为 LoginFragment
或 ProfileFragment
都不负责确定用户如何进行身份验证。将您的逻辑封装在 ViewModel
中,不仅使其更易于共享,而且更易于测试。如果您的导航逻辑很复杂,则应特别通过测试来验证此逻辑。有关围绕可测试组件构建应用程序架构的更多信息,请参阅 应用程序架构指南。
回到 ProfileFragment
中,可以在 onCreate()
方法中观察 SavedStateHandle
中存储的 LOGIN_SUCCESSFUL
值。当用户返回到 ProfileFragment
时,将检查 LOGIN_SUCCESSFUL
值。如果该值为 false
,则可以将用户重定向回 MainFragment
。
Kotlin
class ProfileFragment : Fragment() { ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val navController = findNavController() val currentBackStackEntry = navController.currentBackStackEntry!! val savedStateHandle = currentBackStackEntry.savedStateHandle savedStateHandle.getLiveData<Boolean>(LoginFragment.LOGIN_SUCCESSFUL) .observe(currentBackStackEntry, Observer { success -> if (!success) { val startDestination = navController.graph.startDestination val navOptions = NavOptions.Builder() .setPopUpTo(startDestination, true) .build() navController.navigate(startDestination, null, navOptions) } }) } ... }
Java
public class ProfileFragment extends Fragment { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); NavController navController = NavHostFragment.findNavController(this); NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry(); SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle(); savedStateHandle.getLiveData(LoginFragment.LOGIN_SUCCESSFUL) .observe(navBackStackEntry, (Observer<Boolean>) success -> { if (!success) { int startDestination = navController.getGraph().getStartDestination(); NavOptions navOptions = new NavOptions.Builder() .setPopUpTo(startDestination, true) .build(); navController.navigate(startDestination, null, navOptions); } }); } ... }
如果用户成功登录,则 ProfileFragment
会显示欢迎消息。
此处使用的检查结果的技术允许您区分两种不同的情况
- 初始情况,用户未登录,应要求其登录。
- 用户未登录,因为**他们选择不登录**(
false
的结果)。
通过区分这些用例,您可以避免反复要求用户登录。处理失败情况的业务逻辑由您决定,可能包括显示一个覆盖层来解释用户为什么需要登录、完成整个活动或将用户重定向到不需要登录的目标位置,就像前面的代码示例中一样。