การนำทางแบบมีเงื่อนไข

เมื่อออกแบบการนำทางสำหรับแอป คุณอาจต้องการให้ไปยัง กับปลายทางอื่นตามตรรกะตามเงื่อนไข ตัวอย่างเช่น ผู้ใช้ อาจติดตาม Deep Link ไปยังปลายทางที่กำหนดให้ผู้ใช้ต้องบันทึก หรือคุณอาจมีจุดหมายที่ต่างกันในเกม จะแพ้หรือชนะ

การเข้าสู่ระบบของผู้ใช้

ในตัวอย่างนี้ ผู้ใช้พยายามจะไปยังหน้าจอโปรไฟล์ที่ การตรวจสอบสิทธิ์ เนื่องจากการดำเนินการนี้ต้องมีการตรวจสอบสิทธิ์ ผู้ใช้ควร ระบบจะเปลี่ยนเส้นทางไปยังหน้าจอเข้าสู่ระบบหากยังไม่ได้ตรวจสอบสิทธิ์

กราฟการนำทางในตัวอย่างนี้อาจมีลักษณะดังนี้

วันที่ ขั้นตอนการเข้าสู่ระบบจะได้รับการจัดการแยกจากหน้าหลัก             การนำทาง
รูปที่ 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 มีปุ่มที่ผู้ใช้คลิกเพื่อดูโปรไฟล์ได้ หากผู้ใช้ต้องการดูหน้าจอโปรไฟล์ ผู้ใช้จะต้องตรวจสอบสิทธิ์ก่อน ช่วงเวลานี้ การประมาณการโต้ตอบจะสร้างขึ้นโดยใช้ส่วนย่อย 2 ส่วนแยกกัน แต่ขึ้นอยู่กับการแชร์ สถานะผู้ใช้ ข้อมูลสถานะนี้ไม่ใช่ความรับผิดชอบของ ส่วนย่อยทั้ง 2 นี้และได้รับการเก็บรักษาไว้อย่างเหมาะสมใน 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() {         ...     } }

หากข้อมูลผู้ใช้คือ null เมื่อไปถึงProfileFragment จะเป็น เปลี่ยนเส้นทางไปยัง 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 จะอัปเดต ค่า LOGIN_SUCCESSFUL ใน 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)          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 ซึ่งเป็นค่า LOGIN_SUCCESSFUL ที่จัดเก็บไว้ใน SavedStateHandle สามารถสังเกตได้ใน onCreate() เมื่อผู้ใช้กลับไปที่ 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 จะแสดง ข้อความต้อนรับ

เทคนิคที่ใช้ในการตรวจสอบผลลัพธ์นี้ช่วยในการแยกแยะ ระหว่าง 2 กรณีที่แตกต่างกัน

  • กรณีแรกที่ผู้ใช้ไม่ได้เข้าสู่ระบบและควรขอให้ดำเนินการ เข้าสู่ระบบ
  • ผู้ใช้ไม่ได้เข้าสู่ระบบเนื่องจากเลือกที่จะไม่เข้าสู่ระบบ (ผลลัพธ์จาก false)

การแยกความแตกต่างระหว่างกรณีการใช้งานเหล่านี้จะช่วยให้คุณหลีกเลี่ยงการถาม ให้ผู้ใช้เข้าสู่ระบบ คุณจะมีตรรกะทางธุรกิจสำหรับจัดการกับกรณีความล้มเหลว และอาจรวมถึงการแสดงโฆษณาซ้อนทับที่อธิบายเหตุผลที่ผู้ใช้ต้อง เข้าสู่ระบบ ทำกิจกรรมให้เสร็จ หรือเปลี่ยนเส้นทางผู้ใช้ไปยังปลายทาง ซึ่งไม่จำเป็นต้องเข้าสู่ระบบ เหมือนในตัวอย่างโค้ดก่อนหน้านี้