শর্তসাপেক্ষ নেভিগেশন

আপনার অ্যাপের জন্য নেভিগেশন ডিজাইন করার সময়, আপনি শর্তসাপেক্ষ যুক্তির উপর ভিত্তি করে একটি গন্তব্য বনাম অন্য গন্তব্যে নেভিগেট করতে চাইতে পারেন। উদাহরণস্বরূপ, একজন ব্যবহারকারী একটি গন্তব্যের একটি গভীর লিঙ্ক অনুসরণ করতে পারে যার জন্য ব্যবহারকারীকে লগ ইন করতে হবে, অথবা খেলোয়াড় যখন জিতবে বা হারবে তখন গেমে আপনার আলাদা গন্তব্য থাকতে পারে।

ব্যবহারকারী লগ - ইন

এই উদাহরণে, একজন ব্যবহারকারী একটি প্রোফাইল স্ক্রিনে নেভিগেট করার চেষ্টা করে যার জন্য প্রমাণীকরণ প্রয়োজন। যেহেতু এই ক্রিয়াটির জন্য প্রমাণীকরণের প্রয়োজন, ব্যবহারকারীকে একটি লগইন স্ক্রিনে পুনঃনির্দেশিত করা উচিত যদি তারা ইতিমধ্যেই প্রমাণীকৃত না হয়৷

এই উদাহরণের জন্য নেভিগেশন গ্রাফটি এইরকম দেখতে পারে:

একটি লগইন প্রবাহ অ্যাপের প্রধান নেভিগেশন প্রবাহ থেকে স্বাধীনভাবে পরিচালনা করা হয়।
চিত্র 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() {
        ...
    }
}

ব্যবহারকারীর ডেটা যদি null থাকে যখন তারা ProfileFragment পৌঁছায়, সেগুলি LoginFragment পুনঃনির্দেশিত হয়।

আপনি আগের গন্তব্যের জন্য NavBackStackEntry পুনরুদ্ধার করতে NavController.getPreviousBackStackEntry() ব্যবহার করতে পারেন, যা গন্তব্যের জন্য NavController নির্দিষ্ট অবস্থাকে অন্তর্ভুক্ত করে। ব্যবহারকারী সফলভাবে লগ ইন করেছে কিনা তা নির্দেশ করে একটি প্রাথমিক মান সেট করতে LoginFragment পূর্ববর্তী NavBackStackEntry এর SavedStateHandle ব্যবহার করে। ব্যবহারকারী যদি অবিলম্বে সিস্টেম ব্যাক বোতাম টিপুন তাহলে আমরা সেই অবস্থায় ফিরে আসতে চাই। 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 তারপর SavedStateHandleLOGIN_SUCCESSFUL মান আপডেট করে এবং ব্যাক স্ট্যাকের থেকে নিজেকে পপ করে দেয়।

কোটলিন

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 এ ফিরে, SavedStateHandle এ সংরক্ষিত LOGIN_SUCCESSFUL মান 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 ফলাফল)।

এই ব্যবহারের ক্ষেত্রে পার্থক্য করে, আপনি বারবার ব্যবহারকারীকে লগইন করতে বলা এড়াতে পারেন। ব্যর্থতার মামলাগুলি পরিচালনা করার জন্য ব্যবসায়িক যুক্তি আপনার উপর ছেড়ে দেওয়া হয়েছে এবং এতে একটি ওভারলে প্রদর্শন করা অন্তর্ভুক্ত থাকতে পারে যা ব্যাখ্যা করে যে কেন ব্যবহারকারীকে লগইন করতে হবে, সম্পূর্ণ কার্যকলাপ শেষ করতে হবে, বা ব্যবহারকারীকে এমন একটি গন্তব্যে পুনঃনির্দেশিত করতে হবে যেখানে লগইন করার প্রয়োজন নেই, যেমনটি ছিল পূর্ববর্তী কোড উদাহরণ।