ناوبری مشروط

هنگام طراحی ناوبری برای برنامه خود، ممکن است بخواهید بر اساس منطق شرطی به یک مقصد در مقابل مقصد دیگر پیمایش کنید. به عنوان مثال، یک کاربر ممکن است پیوند عمیقی را به مقصدی دنبال کند که نیاز به ورود کاربر دارد، یا ممکن است مقصدهای مختلفی در یک بازی برای زمان برد یا باخت بازیکن داشته باشید.

ورود کاربر

در این مثال، یک کاربر تلاش می کند به صفحه نمایه ای که نیاز به احراز هویت دارد، حرکت کند. از آنجا که این عمل نیاز به احراز هویت دارد، اگر کاربر قبلاً احراز هویت نشده باشد باید به صفحه ورود هدایت شود.

نمودار ناوبری برای این مثال ممکن است چیزی شبیه به این باشد:

یک جریان ورود به سیستم به طور مستقل از جریان ناوبری اصلی برنامه مدیریت می شود.
شکل 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 است:

کاتلین

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 ).

با تشخیص این موارد استفاده، می توانید از درخواست مکرر کاربر برای ورود به سیستم خودداری کنید. منطق کسب و کار برای رسیدگی به موارد شکست به شما واگذار می شود و ممکن است شامل نمایش پوششی باشد که توضیح می دهد که چرا کاربر باید وارد سیستم شود، تمام فعالیت را به پایان برساند یا کاربر را به مقصدی هدایت کند که نیازی به ورود به سیستم ندارد، همانطور که در این مورد وجود داشت. نمونه کد قبلی