عند تصميم التنقل لتطبيقك، قد ترغب في الانتقال إلى أحد والوجهة مقابل أخرى بناءً على المنطق الشرطي. على سبيل المثال، قد يرغب مستخدم قد يتبع رابطًا لموضع معيّن يؤدي إلى وجهة تتطلب تسجيل دخول المستخدم أو قد تكون لديك وجهات مختلفة في اللعبة عندما بالفوز أو الخسارة.
تسجيل دخول المستخدم
في هذا المثال، يحاول المستخدم الانتقال إلى شاشة الملف الشخصي التي تتطلب المصادقة. ولأن هذا الإجراء يتطلب المصادقة، يجب على المستخدم ستتم إعادة توجيهه إلى شاشة تسجيل الدخول إذا لم تتم مصادقتها من قبل.
قد يبدو الرسم البياني للتنقل في هذا المثال على النحو التالي:
للمصادقة، يجب أن ينتقل التطبيق إلى 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() { ... } }
إذا كانت بيانات المستخدمين هي 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
رسالة ترحيب.
ويتيح لك الأسلوب المستخدم هنا للتحقق من النتيجة التمييز بين حالتين مختلفتين:
- الحالة الأولية التي يكون فيها المستخدم غير مسجّل الدخول ويجب أن يُطلب منه تسجيل الدخول.
- لم يسجّل المستخدم الدخول لأنه اختر عدم تسجيل الدخول (نتيجة
false
).
بتمييز حالات الاستخدام هذه، يمكنك تجنب تكرار طلب تسجيل الدخول للمستخدم. يُترك لك منطق الأعمال لمعالجة حالات الإخفاق وقد تتضمن عرض تراكب يشرح سبب احتياج المستخدم تسجيل الدخول أو إنهاء النشاط بأكمله أو إعادة توجيه المستخدم إلى وجهة لا يتطلب تسجيل الدخول، كما هو الحال في مثال الرمز السابق.