條件式導覽

設計應用程式導覽時,您可能會想前往 比對目的地與另一個目的地例如使用者 可能會追蹤前往某個目的地的深層連結,而該到達網頁會要求使用者記錄 或是在遊戲中為玩家指定不同的到達網頁 勝算條件

使用者登入

在這個範例中,使用者嘗試前往需要 驗證。由於這個動作需要驗證,因此使用者 如果使用者尚未通過驗證,會重新導向至登入畫面。

這個範例的導覽圖大致如下:

登入流程的處理獨立於應用程式             導覽流程。
圖 1. 登入流程的處理獨立於 應用程式的主要導覽流程。

如要進行驗證,應用程式必須前往 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 中。 在片段之間共用這個 ViewModel 的方法是將其範圍限定為活動。 實作 ViewModelStoreOwner在以下範例中 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 會使用 SavedStateHandle 的 先前的 NavBackStackEntry 來設定初始值,指出 位使用者成功登入。這是我們要傳回的狀態 使用者立即按下系統返回按鈕。使用 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。這很重要,因為您 使用 LoginFragmentProfileFragment 來判斷使用者 並通過完整驗證將邏輯封裝在 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)。

藉由區分這些用途,可避免重複詢問 使用者登入。處理失敗情況的商業邏輯由您決定 還可能包括顯示重疊廣告 說明使用者為何需要 登入、完成整個活動,或將使用者重新導向至目的地 而且不需要登入,就如上一個程式碼範例所示