設計應用程式導覽時,您可能會想前往 比對目的地與另一個目的地例如使用者 可能會追蹤前往某個目的地的深層連結,而該到達網頁會要求使用者記錄 或是在遊戲中為玩家指定不同的到達網頁 勝算條件
使用者登入
在這個範例中,使用者嘗試前往需要 驗證。由於這個動作需要驗證,因此使用者 如果使用者尚未通過驗證,會重新導向至登入畫面。
這個範例的導覽圖大致如下:
如要進行驗證,應用程式必須前往 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
。這很重要,因為您
使用 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
)。
藉由區分這些用途,可避免重複詢問 使用者登入。處理失敗情況的商業邏輯由您決定 還可能包括顯示重疊廣告 說明使用者為何需要 登入、完成整個活動,或將使用者重新導向至目的地 而且不需要登入,就如上一個程式碼範例所示