При разработке навигации для вашего приложения вы можете захотеть перейти к одному пункту назначения вместо другого на основе условной логики. Например, пользователь может перейти по глубокой ссылке к месту назначения, требующему от пользователя входа в систему, или в игре могут быть разные места назначения, когда игрок выигрывает или проигрывает.
Вход пользователя
В этом примере пользователь пытается перейти на экран профиля, требующий аутентификации. Поскольку это действие требует аутентификации, пользователь должен быть перенаправлен на экран входа в систему, если он еще не прошел аутентификацию.
Граф навигации для этого примера может выглядеть примерно так:

Для аутентификации приложение должно перейти к 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
.
Вы можете использовать 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
).
Различая эти варианты использования, вы можете избежать повторного запроса пользователя на вход в систему. Бизнес-логика для обработки случаев сбоя остается за вами и может включать в себя отображение наложения, объясняющего, почему пользователю необходимо войти в систему, завершение всего действия или перенаправление пользователя в пункт назначения, не требующий входа в систему, как это было в случае с предыдущий пример кода.
,При разработке навигации для вашего приложения вы можете захотеть перейти к одному пункту назначения вместо другого на основе условной логики. Например, пользователь может перейти по глубокой ссылке к месту назначения, требующему от пользователя входа в систему, или в игре могут быть разные места назначения, когда игрок выигрывает или проигрывает.
Вход пользователя
В этом примере пользователь пытается перейти на экран профиля, требующий аутентификации. Поскольку это действие требует аутентификации, пользователь должен быть перенаправлен на экран входа в систему, если он еще не прошел аутентификацию.
Граф навигации для этого примера может выглядеть примерно так:

Для аутентификации приложение должно перейти к 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
.
Вы можете использовать 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
).
Различая эти варианты использования, вы можете избежать повторного запроса пользователя на вход в систему. Бизнес-логика для обработки случаев сбоя остается за вами и может включать в себя отображение наложения, объясняющего, почему пользователю необходимо войти в систему, завершение всего действия или перенаправление пользователя в пункт назначения, не требующий входа в систему, как это было в случае с предыдущий пример кода.