Jetpack Compose — лучшая практика условного TopAppBar для всего приложенияAndroid

Форум для тех, кто программирует под Android
Ответить Пред. темаСлед. тема
Anonymous
 Jetpack Compose — лучшая практика условного TopAppBar для всего приложения

Сообщение Anonymous »


У меня есть приложение Android Jetpack Compose, которое использует составные элементы BottomNavigation и TopAppBar. На вкладке, открытой с помощью BottomNavigation, пользователи могут глубже проникнуть в граф навигации.
Проблема
Компонуемый элемент TopAppBar должен представлять текущий экран, например отображать его имя, реализовывать некоторые параметры, специфичные для открытого экрана, кнопку «Назад», если экран высокого уровня. Однако у Jetpack Compose, похоже, нет готового решения для этой проблемы, и разработчикам приходится реализовывать его самостоятельно.

Итак, у очевидных идей есть очевидные недостатки: некоторые идеи лучше других.

Базовым уровнем для отслеживания навигации, предложенным Google (по крайней мере, для BottomNavigation), является запечатанный класс, содержащий объекты, которые представляют текущий активный экран. Конкретно для моего проекта это так:

sealed class AppTab(val Route: String, @StringRes val resourcesId: Int, val icon: ImageVector) { События объекта: AppTab("events_tab", R.string.events, Icons.Default.EventNote) Объектные проекты: AppTab("projects_tab", R.string.projects, Icons.Default.Widgets) объектные устройства: AppTab("devices_tab", R.string.devices, Icons.Default.DevicesOther) объект Сотрудники: AppTab("employees_tab", R.string.employees, Icons.Default.People) Профиль объекта: AppTab("profile_tab", R.string.profile, Icons.Default.AccountCircle) } Теперь TopAppBar может знать, какая вкладка открыта, при условии, что мы помним объект AppTab, но как он узнает, открыт ли экран? открыт из данной вкладки?
Решение 1 — очевидное и заведомо неверное
Мы предоставляем каждому экрану собственный TopAppBar и позволяем ему обрабатывать всю необходимую логику. Помимо большого количества дублирования кода, TopAppBar каждого экрана будет меняться при открытии экрана и, как описано в этом посте, будет мерцать.
Решение 2 – не совсем элегантное
С этого момента я решил иметь один TopAppBar в составном элементе верхнего уровня моего проекта, который будет зависеть от состояния с сохраненным текущим экраном. Теперь мы можем легко реализовать логику для вкладок.

Чтобы решить проблему экранов, открываемых из вкладки, я расширил идею Google и реализовал общий класс AppScreen, который представляет каждый экран, который можно открыть:

// Этот класс представляет любой экран — вкладки и их подэкраны. // Необходимо соответствующим образом изменить поведение верхней панели приложения запечатанный класс AppScreen(@StringRes val screenNameResource: Int) { // Связанные с сотрудниками Объект Сотрудники: AppScreen(R.string.employees) объект СотрудникиДеталис: AppScreen(R.string.profile) // Связанные с событиями События объекта: AppScreen(R.string.events) объект EventDetails: AppScreen(R.string.event) объект EventNew: AppScreen(R.string.event_new) // Связанные с проектами Объектные проекты: AppScreen(R.string.projects) // Связанные с устройствами объектные устройства: AppScreen(R.string.devices) // Связанный с профилем Профиль объекта: AppScreen(R.string.profile) } Затем я сохраняю его в state в компонуемом элементе верхнего уровня в области TopAppBar и передаю currentScreenHandler как Аргумент onNavigate для моих компонуемых вкладок:

var currentScreen по памяти {mutableStateOf(defaultTab.asScreen()) } val currentScreenHandler: (AppScreen) -> Unit = {navigatedScreen -> currentScreen = NavigationScreen} // Где-то в bodyContent Scaffold когда (currentTab) { AppTab.Employees → СотрудникиTab(currentScreenHandler) // И другие вкладки // ... } И изнутри составной вкладки:

val navController = RememberNavController() NavHost(navController, startDestination = "сотрудники") { composable("сотрудники") { onNavigate(AppScreen.Сотрудники) Сотрудники (it.hiltViewModel(), navController) } composable("сотрудник/{userId}") { onNavigate(AppScreen.EmployeeDetails) Сотрудник (it.hiltViewModel()) } } Теперь TopAppBar в корневом компоненте знает об экранах более высокого уровня и может реализовать необходимую логику. Но делать это для каждого подэкрана приложения? Значительное количество дублирования кода и архитектура связи между этой панелью приложения и составным объектом, который он представляет (как составной объект реагирует на действия, выполняемые на панели приложения), еще предстоит составить (каламбур).
Решение 3 — лучшее?
Я реализовал viewModel для обработки необходимой логики, поскольку это казалось наиболее элегантным решением:

@HiltViewModel класс AppBarViewModel @Inject конструктор() : ViewModel() { частный val defaultTab = AppTab.Events частный val _currentScreen = MutableStateFlow(defaultTab.asScreen()) val currentScreen: StateFlow = _currentScreen весело onNavigate(экран: AppScreen) { _currentScreen.value = экран } } Компонуемый корень:

val currentScreen от appBarViewModel.currentScreen.collectAsState() Но это не решило проблему дублирования кода второго решения. Прежде всего, мне пришлось передать этот viewModel в корневой компонуемый объект из MainActivity, поскольку, похоже, другого способа доступа к нему изнутри компонуемого объекта не существует. Итак, теперь вместо передачи currentScreenHandler компонентам Tab, я передаю им viewModel и вместо вызова обработчика события навигации я вызываю viewModel. onNavigate(AppScreen), поэтому кода становится еще больше! По крайней мере, я, возможно, смогу реализовать механизм связи, упомянутый в предыдущем решении.
Вопрос
На данный момент второе решение кажется лучшим с точки зрения объема кода, но третье обеспечивает взаимодействие и большую гибкость в дальнейшем для некоторых еще не запрошенных функций. Возможно, мне не хватает чего-то очевидного и элегантного. Какую из моих реализаций вы считаете лучшей, а если нет, что бы вы сделали, чтобы решить эту проблему?

Спасибо.
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Jetpack Compose — итоговая строка TopAppBar
    Anonymous » » в форуме Android
    0 Ответы
    24 Просмотры
    Последнее сообщение Anonymous
  • Как управлять поведением прокрутки для каждого экрана при использовании общего TopAppBar в Jetpack Compose?
    Anonymous » » в форуме Android
    0 Ответы
    26 Просмотры
    Последнее сообщение Anonymous
  • Как отобразить TopAppBar в местах назначения, которые не принадлежат TopLevelDestinations, с помощью Jetpack Compose?
    Anonymous » » в форуме Android
    0 Ответы
    27 Просмотры
    Последнее сообщение Anonymous
  • Сворачивание TopAppBar в Jetpack Compose Material 3
    Anonymous » » в форуме Android
    0 Ответы
    27 Просмотры
    Последнее сообщение Anonymous
  • Сворачивание TopAppBar в Jetpack Compose Material 3
    Anonymous » » в форуме Android
    0 Ответы
    18 Просмотры
    Последнее сообщение Anonymous

Вернуться в «Android»