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