Nawigacja warunkowa

Podczas projektowania nawigacji w aplikacji warto wyświetlić lub inne miejsce docelowe w oparciu o logikę warunkową. Jeśli na przykład użytkownik może kliknąć precyzyjny link do miejsca docelowego, które wymaga zalogowania się lub też gracz może grać w różne miejsca, wygranych lub przegranych.

Logowanie użytkownika

W tym przykładzie użytkownik próbuje przejść do ekranu profilu, który wymaga uwierzytelnianie. Ponieważ to działanie wymaga uwierzytelnienia, użytkownik powinien zostanie przekierowany na ekran logowania, jeśli użytkownik nie został jeszcze uwierzytelniony.

W tym przykładzie wykres nawigacyjny może wyglądać tak:

proces logowania jest obsługiwany niezależnie od głównego
            podczas nawigacji.
Rysunek 1. Proces logowania jest obsługiwany niezależnie od w głównym procesie nawigacji w aplikacji.

Aby się uwierzytelnić, aplikacja musi przejść do sekcji login_fragment, gdzie użytkownik wpisz nazwę użytkownika i hasło do uwierzytelnienia. Jeśli użytkownik zostanie zaakceptowany, Odesłano na ekran profile_fragment. Jeśli użytkownik nie wyrazi zgody, poinformowano o tym, że ich dane logowania są nieprawidłowe za pomocą Snackbar Jeśli użytkownik wróci do ekranu profilu bez logowania się, wysłano na ekran main_fragment.

Oto wykres nawigacji dla tej aplikacji:

<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 zawiera przycisk, który umożliwia wyświetlenie profilu. Jeśli użytkownik chce zobaczyć ekran profilu, musi najpierw się uwierzytelnić. Ten interakcja jest modelowana za pomocą 2 osobnych fragmentów, ale zależy to od jako stan użytkownika. Odpowiedzialność za te informacje o stanie nie jest odpowiedzialna i są lepiej przechowywane we wspólnym UserViewModel. Ten obiekt ViewModel jest wspólny dla fragmentów przez określenie zakresu aktywności, , która implementuje ViewModelStoreOwner. W poniższym przykładzie requireActivity() określa wartość MainActivity, ponieważ MainActivity hosty 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);
        ...
    }
    ...
}

Dane użytkowników w organizacji UserViewModel są udostępniane przez usługę LiveData, więc aby ustalić, gdzie do nawigacji, należy obserwować dane. Po przejściu do ProfileFragment, jeśli dane użytkownika będą obecnie. Jeśli dane użytkownika to null, przejdziesz do strony LoginFragment, bo użytkownik musi przejść uwierzytelnianie, zanim wyświetli swój profil. Zdefiniuj w ProfileFragment, jak w tym przykładzie:

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() {
        ...
    }
}

Jeśli w momencie osiągnięcia ProfileFragment dane użytkownika to null, oznacza to, że są przekierowany do LoginFragment.

Za pomocą NavController.getPreviousBackStackEntry() aby pobrać NavBackStackEntry dla poprzedniego miejsca docelowego, które zawiera parametr NavController dla miejsca docelowego. Funkcja LoginFragment używa funkcji SavedStateHandle z poprzedni NavBackStackEntry, by ustawić wartość początkową wskazującą, czy użytkownik zalogował się. To jest stan, który chcemy zwrócić, użytkownik miał natychmiast nacisnąć przycisk Wstecz. Ustawianie tego stanu za pomocą funkcji SavedStateHandle zapewnia to, że stan utrzymuje się przez śmierć procesu.

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);
    }
}

Gdy użytkownik wpisze nazwę użytkownika i hasło, zostanie przekierowany do UserViewModel do uwierzytelnienia. Jeśli uwierzytelnienie się powiedzie, UserViewModel przechowuje dane użytkownika. Następnie LoginFragment aktualizuje LOGIN_SUCCESSFUL na wartość SavedStateHandle i wyskakuje z ze stosu z tyłu.

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
    }
}

Wszystkie zasady dotyczące uwierzytelniania opierają się na UserViewModel To ważne, ponieważ to nie jest odpowiedzialność LoginFragment lub ProfileFragment, by określić, jak użytkownicy uwierzytelniono. Uwzględnienie Twojej logiki w elemencie ViewModel sprawia, że nie tylko udostępnianie, ale też łatwiejsze do testowania. Jeśli logika nawigacji jest skomplikowana, należy w szczególności sprawdzić tę logikę, przeprowadzając testy. Zobacz Przewodnik po architekturze aplikacji zawiera więcej informacji: Opracować strukturę architektury aplikacji z użyciem komponentów, które można przetestować.

W ProfileFragment, wartość LOGIN_SUCCESSFUL przechowywana w SavedStateHandle można zaobserwować w onCreate() . Gdy użytkownik wróci do ProfileFragment, LOGIN_SUCCESSFUL zostanie sprawdzona. Jeśli wartością jest false, użytkownik może zostać przekierowany z powrotem. do: 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);
                    }
                });
    }

    ...
}

Jeśli użytkownik się zalogował, ProfileFragment wyświetla wiadomość powitalną.

Zastosowana tutaj technika sprawdzania wyników pozwala rozróżnić między 2 różnymi sprawami:

  • Przypadek początkowy, w którym użytkownik nie jest zalogowany i powinien zostać o to poproszony dane logowania.
  • Użytkownik nie jest zalogowany, ponieważ nie zdecydował się na logowanie (w wyniku false).

Rozróżniając te przypadki użycia, możesz uniknąć wielokrotnego zadawania pytań aby się zalogować. Logika biznesowa postępowania w przypadku awarii należy do Ciebie Może też dodać nakładkę z wyjaśnieniem, dlaczego logowanie, zakończenie całej aktywności lub przekierowywanie użytkownika do miejsca docelowego. które nie wymagają logowania, tak jak w poprzednim przykładzie kodu.