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