การนำทางแบบมีเงื่อนไข

เมื่อออกแบบการนำทางสำหรับแอป คุณอาจต้องการให้ไปยัง กับปลายทางอื่นตามตรรกะตามเงื่อนไข ตัวอย่างเช่น ผู้ใช้ อาจติดตาม Deep Link ไปยังปลายทางที่กำหนดให้ผู้ใช้ต้องบันทึก หรือคุณอาจมีจุดหมายที่ต่างกันในเกม จะแพ้หรือชนะ

การเข้าสู่ระบบของผู้ใช้

ในตัวอย่างนี้ ผู้ใช้พยายามจะไปยังหน้าจอโปรไฟล์ที่ การตรวจสอบสิทธิ์ เนื่องจากการดำเนินการนี้ต้องมีการตรวจสอบสิทธิ์ ผู้ใช้ควร ระบบจะเปลี่ยนเส้นทางไปยังหน้าจอเข้าสู่ระบบหากยังไม่ได้ตรวจสอบสิทธิ์

กราฟการนำทางในตัวอย่างนี้อาจมีลักษณะดังนี้

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

การแยกความแตกต่างระหว่างกรณีการใช้งานเหล่านี้จะช่วยให้คุณหลีกเลี่ยงการถาม ให้ผู้ใช้เข้าสู่ระบบ คุณจะมีตรรกะทางธุรกิจสำหรับจัดการกับกรณีความล้มเหลว และอาจรวมถึงการแสดงโฆษณาซ้อนทับที่อธิบายเหตุผลที่ผู้ใช้ต้อง เข้าสู่ระบบ ทำกิจกรรมให้เสร็จ หรือเปลี่ยนเส้นทางผู้ใช้ไปยังปลายทาง ซึ่งไม่จำเป็นต้องเข้าสู่ระบบ เหมือนในตัวอย่างโค้ดก่อนหน้านี้