條件式導覽

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

使用者登入

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

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

登入流程的處理獨立於應用程式
            導覽流程。
圖 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)。

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