هنگام طراحی ناوبری برای برنامه خود، ممکن است بخواهید بر اساس منطق شرطی به یک مقصد در مقابل مقصد دیگر پیمایش کنید. به عنوان مثال، یک کاربر ممکن است پیوند عمیقی را به مقصدی دنبال کند که نیاز به ورود کاربر دارد، یا ممکن است مقصدهای مختلفی در یک بازی برای زمان برد یا باخت بازیکن داشته باشید.
ورود کاربر
در این مثال، یک کاربر تلاش می کند به صفحه نمایه ای که نیاز به احراز هویت دارد، حرکت کند. از آنجا که این عمل نیاز به احراز هویت دارد، اگر کاربر قبلاً احراز هویت نشده باشد باید به صفحه ورود هدایت شود.
نمودار ناوبری برای این مثال ممکن است چیزی شبیه به این باشد:
![یک جریان ورود به سیستم به طور مستقل از جریان ناوبری اصلی برنامه مدیریت می شود.](https://developer.android.com/static/images/guide/navigation/navigation-conditional-login.png?hl=fa)
برای احراز هویت، برنامه باید به 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
است:
کاتلین
class ProfileFragment : Fragment() { private val userViewModel: UserViewModel by activityViewModels() ... }
جاوا
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
خود تعریف کنید:
کاتلین
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() { ... } }
جاوا
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
تضمین میکند که وضعیت از طریق مرگ فرآیند باقی میماند.
کاتلین
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) } }
جاوا
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
به روز می کند و از پشته پشتی خارج می شود.
کاتلین
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 } }
جاوا
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
هدایت شود.
کاتلین
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) } }) } ... }
جاوا
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
).
با تشخیص این موارد استفاده، می توانید از درخواست مکرر کاربر برای ورود به سیستم خودداری کنید. منطق کسب و کار برای رسیدگی به موارد شکست به شما واگذار می شود و ممکن است شامل نمایش پوششی باشد که توضیح می دهد که چرا کاربر باید وارد سیستم شود، تمام فعالیت را به پایان برساند یا کاربر را به مقصدی هدایت کند که نیازی به ورود به سیستم ندارد، همانطور که در این مورد وجود داشت. نمونه کد قبلی