Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Навигация с архитектурными компонентами от Goog...

Навигация с архитектурными компонентами от Google: взгляд прагматика

Александр Блинов, HeadHunter – MOSDROID 10 #NEON

Одной из проблем, с которыми сталкивается разработчик немного подразросшегося приложения — навигация между экранами. Когда сценарии становятся нелинейными, уже тяжело обойтись стандартными startActivity и changeFragment. Эту проблему каждый решал по-своему: делал какое-то свое решение для навигации, использовал стороннее (к примеру, Cicerone) или же оставлял все как есть и городил кучу флагов и if else. Это очень огорчало инженеров Google, и вот уже на Google I/O 2018 появилось решение Navigation, входящее в состав набирающего хайпа JetPack-а.

Что мы вообще хотим от фреймворка для навигации? Как устроено решение Google изнутри? Закрыт ли вопрос навигации навсегда? В своем докладе я постараюсь ответить на эти и другие насущные вопросы.

MOSDROID

August 25, 2018
Tweet

More Decks by MOSDROID

Other Decks in Programming

Transcript

  1. Обо мне Руководитель Android практики Соавтор Moxy Соведущий подкастов Админ

    чата по архитектуре Android Выступаю на конференциях Александр Блинов [email protected]
  2. Затравка Затравка 3 % 8 % 12 % 69 %

    8 % Что это? Слышал, не пробовал Смотрел в эдиторе Использую в своём петпроджекте Использую в живом проект Кто работал с новой навигацией от Google? Убийца Чичероне?
  3. 1 Требования к presentation слою Содержание 2 Устройство 3 Принцип

    работы 4 5 Подведение итогов Слайды Пример работы
  4. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 15 16
  5. Принципы навигации Фиксированная точка старта Навигация работает в виде стека

    LIFO Кнопки Back и Up работают одинаково* Диплинки в приложение порождают стек экранов, аналогичный тому, если бы мы дошли до экрана самостоятельно Постулаты 1 2 = 1 2 3
  6. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 15 16 14
  7. Navigation Editor <?xml version="1.0" encoding="utf-8"?> <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" app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment" android:name="com.github.xanderblinov.nav.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home"> <action android:id="@+id/action_homeFragment_to_introFragment" app:destination="@id/introFragment" app:launchSingleTop="true" /> </fragment> <fragment android:id="@+id/introFragment" android:name="com.github.xanderblinov.nav.IntroFragment" android:label="fragment_intro" tools:layout="@layout/fragment_intro"> <deepLink android:id="@+id/deepLink" app:uri="intro" /> <argument android:name="amount" android:defaultValue="1" app:argType="integer" /> </fragment> </navigation> Text mode
  8. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 4 15 14 15 12 15 16
  9. Ключевые сущности Коллекция узлов NavDestination NavGraph • Destination достаются по

    ID • Имеется стартовый экран • Можно добавить целый граф • Создается механизмом аналогичным view • NavInflater из Resources • Динамически из кода
  10. Ключевые сущности Контейнер для навигации при помощи NavController NavHostFragment :

    NavHost • Управляет сохранением и восстановлением состояния NavController • Устанавливает NavController во View public interface NavHost { @NonNull NavController getNavController(); }
  11. Ключевые сущности Мозги навигации NavController • Осуществляет переходы по стеку

    вперед(navigate) и назад ( popBackStack / navigateUp() ) состояния NavController • Предоставляет возможность наблюдать за навигацией • Умеет сохранять состояние в Bundle и восстанавливать его обратно
  12. Ключевые сущности Руки навигации Navigator • Реализует переходы по стеку

    вперед(navigate) и назад ( popBackStack / navigateUp() ) состояния NavController • Предоставляет возможность наблюдать за навигацией • Умеет сохранять состояние в Bundle и восстанавливать его обратно
  13. NavOptions Устройство /** * NavOptions stores special options for navigate

    actions */ public class NavOptions { static final int LAUNCH_SINGLE_TOP = 0x1; static final int LAUNCH_DOCUMENT = 0x2; static final int LAUNCH_CLEAR_TASK = 0x4; private int mLaunchMode; @IdRes private int mPopUpTo; private boolean mPopUpToInclusive; @AnimRes @AnimatorRes private int mEnterAnim; @AnimRes @AnimatorRes private int mExitAnim; @AnimRes @AnimatorRes private int mPopEnterAnim; @AnimRes @AnimatorRes private int mPopExitAnim; /** some code**/ }
  14. Уровень Fragment / Activity Container Fragment/Activity NavHostFragment Navigator NavGraph Destination

    #1 Destination #2 Destination #3 NavController Устройство Сохранение состояний, инстанциирование Транзакции фрагментов Логика переходов Хранение карты переходов
  15. Механика работы Инстанциирование Container Fragment/Activity NavHostFragment NavGraph Destination #1 Destination

    #2 Destination #3 NavController Inflate Fragment 1 Create 3 Inflate 6 Create 2 Set navigator 4 Set graph id / Restore state 5 Navigator
  16. Задание графа из кода Инстанциирование public class NavController { /*

    … */ public void setGraph(@NavigationRes int graphResId) { mGraph = getNavInflater().inflate(graphResId); mGraphId = graphResId; onGraphCreated(); } public void setGraph(@NonNull NavGraph graph) { mGraph = graph; mGraphId = 0; onGraphCreated(); } /* … */ } Победа?
  17. Задание графа из кода Корректировка val navController = findNavController(requireActivity(), R.id.nav_host_fragment_home)

    with(navController) { // Создаем Destination val destination = FragmentNavigator.Destination(navigatorProvider) destination.id = NOTIFICATION_DETAILS_ID destination.setFragmentClass(NotificationDetailsFragment::class.java) // Добавляем Destination в граф graph.addDestination(destination) // Добавляем Action на открытие добавленного Destination val notificationDestination = graph.findNode(R.id.notificationFragment) notificationDestination?.putAction(OPEN_NOTIFICATION_DETAILS_ACTION_ID, NOTIFICATION_DETAILS_ID) }
  18. val navController = findNavController(requireActivity(), R.id.nav_host_fragment_home) with(navController) { // Создаем Destination

    val destination = FragmentNavigator.Destination(navigatorProvider) destination.id = NOTIFICATION_DETAILS_ID destination.setFragmentClass(NotificationDetailsFragment::class.java) // Добавляем Destination в граф graph.addDestination(destination) // Добавляем Action на открытие добавленного Destination val notificationDestination = graph.findNode(R.id.notificationFragment) notificationDestination?.putAction(OPEN_NOTIFICATION_DETAILS_ACTION_ID, NOTIFICATION_DETAILS_ID) } Задание графа из кода Корректировка
  19. /** some code */ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction;

    @Navigator.Name("fragment") public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> { /** some code */ @NonNull public Destination setFragmentClass(@NonNull Class<? extends Fragment> clazz) { mFragmentClass = clazz; return this; } /** some code */ } Jetifier
  20. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 7 8 Приоритет 1 6 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 15 16 6 4 14 15 16 15 12
  21. Механика работы Навигация Container Fragment/Activity NavHostFragment NavGraph Destination #1 Destination

    #2 Destination #3 NavController navigate(args) 1 Find Destination 2 navigate(args) 3 Navigator Transaction 5 navigate(args) 4
  22. public class NavController { public NavController(@NonNull Context context) { mContext

    = context; while (context instanceof ContextWrapper) { if (context instanceof Activity) { mActivity = (Activity) context; break; } context = ((ContextWrapper) context).getBaseContext(); } mNavigatorProvider.addNavigator(new NavGraphNavigator(mContext)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext)); /** some code*/ } /** some code*/ } Navigator Использование public class NavHostFragment extends Fragment implements NavHost { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext(); mNavController = new NavController(context); mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator()); /** some code*/ } /** some code*/ }
  23. Механика работы Навигация Container Fragment/Activity NavHostFragment NavGraph Destination #1 Destination

    #2 Destination #3 NavController navigate(args) 1 Find Destination 2 navigate(args) 3 NavigatonProvider Navigator #3 Navigator #2 Navigator Transaction 5 navigate(args) 4
  24. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 15 16 6 4 14 15 16 15 12 10
  25. Передача аргументов Подключение buildscript { ext.kotlin_version = ‘1.2.61' repositories {

    google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0-alpha06' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha05' } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'androidx.navigation.safeargs'
  26. Передача аргументов Имплементация <fragment android:id="@+id/introFragment" android:name="com.github.xanderblinov.nav.IntroFragment" android:label="fragment_intro" tools:layout="@layout/fragment_intro"> <argument android:name="amount"

    android:defaultValue="1" app:argType="integer" /> </fragment> val introFragmentArgs: IntroFragmentArgs = IntroFragmentArgs .Builder() .setAmount(3) .build() val introFragmentDirection: NavDirections = HomeFragmentDirections .actionHomeFragmentToIntroFragment() .setAmount(3) // Навигация при помощи Args navController.navigate(R.id.introFragment, introFragmentArgs.toBundle()) // Навигация при помощи Direction navController.navigate(introFragmentDirection)
  27. Передача аргументов Имплементация public static class Builder { private int

    amount = 1; public Builder(IntroFragmentArgs original) { this.amount = original.amount; } public Builder() { } @NonNull public IntroFragmentArgs build() { IntroFragmentArgs result = new IntroFragmentArgs(); result.amount = this.amount; return result; } @NonNull public Builder setAmount(int amount) { this.amount = amount; return this; } public int getAmount() { return amount; } } }
  28. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 16 6 5 15 15 14 6 4 14 15 16 15 12 10
  29. Передача аргументов Shared element /** * Used with custom Transitions

    to map a View from a removed or hidden * Fragment to a View from a shown or added Fragment. * <var>sharedElement</var> must have a unique transitionName in the View hierarchy. * * @param sharedElement A View in a disappearing Fragment to match with a View in an * appearing Fragment. * @param name The transitionName for a View in an appearing Fragment to match to the shared * element. * @see Fragment#setSharedElementReturnTransition(Object) * @see Fragment#setSharedElementEnterTransition(Object) */ @NonNull public abstract FragmentTransaction addSharedElement(@NonNull View sharedElement, @NonNull String name);
  30. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 15 16 6 5 7 6 4 14 15 15 15 16 15 12 10
  31. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 5 7 6 4 6 2 15 15 14 15 16 15 12 10 6 3
  32. Жизненный цикл public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> { @Override public

    void navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions) { if (mFragmentManager.isStateSaved()) { Log.i(TAG, "Ignoring navigate() call: FragmentManager has already" + " saved its state"); return; } final Fragment frag = destination.createFragment(args); final FragmentTransaction ft = mFragmentManager.beginTransaction(); /* some code*/ } /* some code*/ }
  33. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 8 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 5 7 6 4 6 2 6 3 15 15 14 15 16 15 12 10 1
  34. Сравнение с Cicerone public interface Navigator { /** * Performs

    transition described by the navigation command * * @param commands the navigation command array to apply * per single transaction */ void applyCommands(Command[] commands); } /** * Navigation command describes screens transition. * that can be processed by {@link ru.terrakok.cicerone.Navigator}. */ public interface Command {}
  35. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 5 7 6 4 6 2 6 3 15 15 8 8 14 15 16 15 12 10 13 1
  36. Deeplinks private fun initDeepLink(navController: NavController) { val deepLinkBuilder = navController.createDeepLink()

    deepLinkBuilder.setDestination(R.id.introFragment) // для аргументов deepLinkBuilder.setArguments(Bundle()) // если задаем без NavController c NavDeepLinkBuilder(@NonNull Context context) deepLinkBuilder.setGraph(R.navigation.nav_graph) val createPendingIntent = deepLinkBuilder.createPendingIntent() Notifier.createNotificationChannel(baseContext) Notifier.notify(createPendingIntent, Notifier.HOME_DESTINATION_NOTIFICATION_ID, baseContext) } Способ задания
  37. Deeplinks public boolean onHandleDeepLink(@Nullable Intent intent) { /* some code*/

    if (!mBackStack.isEmpty()) { navigate(mGraph.getStartDestination(), bundle, new NavOptions.Builder() .setPopUpTo(mGraph.getId(), true) .setEnterAnim(0).setExitAnim(0).build()); } int index = 0; while (index < deepLink.length) { int destinationId = deepLink[index++]; NavDestination node = findDestination(destinationId); if (node == null) { throw new IllegalStateException("unknown destination during deep link: " + NavDestination.getDisplayName(mContext, destinationId)); } node.navigate(bundle, new NavOptions.Builder().setEnterAnim(0).setExitAnim(0).build()); } /* some code*/ } Способ работы
  38. Deeplinks Выводы 1 Не открывает вложенную навигацию [улучшение] 2 Если

    на экране по пути deepLink есть вложенный граф, то падает [баг] 3 Нет хуков для условной навигации [нереализуемо]
  39. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 5 7 6 4 6 2 6 3 15 15 8 8 14 15 16 15 12 9 10 13 1
  40. Тестирование навигации public class NavController { /**some code*/ private final

    SimpleNavigatorProvider mNavigatorProvider = new SimpleNavigatorProvider() { @Nullable @Override public Navigator<? extends NavDestination> addNavigator(@NonNull String name, @NonNull Navigator<? extends NavDestination> navigator) { /**some code*/ } }; /**some code*/ }
  41. Требования к навигации 1 Отвязка от жизненного цикла Возможность осуществлять

    вложенную навигацию Автооткрытие предыдущего экран при команде «Назад» Возможность добавить анимацию смены экранов Передача аргументов Возможность перестраивать навигацию в рантайме Поддержка Shared element Открытие и закрытие цепочки экранов 2 3 4 5 6 7 Приоритет 1 6 9 Удобный механизм работы с deeplink Возможность имплементации на Activity, Fragment, View Тестирование навигации Гибкость при изменениях Возможность навигации из бизнес-логики Возможность подменять экраны, в навигационном стеке Проверка аргументов во время компиляции Визуальное представление для проектирования 10 Приоритет 2 11 12 13 14 16 6 5 7 6 4 6 2 6 3 15 15 8 8 14 11 15 16 15 12 9 10 13 1
  42. Google I/O '18 May 8, 2018 Mosdroid. Neon August 25,

    2018 August 10, 2018 July 19, 2018 July 12, 2018 June 7, 2018 New Library: Navigation Navigation provides a framework for building in-app navigation. This initial release is 1.0.0-alpha01. 1.0.0-alpha02 • FragmentNavigator now uses setReorderingAllowed(true) • NavDeepLink fixes • Lifecycle fixes • Support Library 27.1.1 • Nested graph and fragment fixes • Overlapping Fragments launchSingleTop issue fixes. • NavigationUI and MenuItem fixes • Deprecated clearTask , launchDocument 1.0.0-alpha03 • A NavigationUI supports Toolbar and CollapsingToolbarLayout • Safe Args types and nullability fixes • Safe Args plugin in modules and other plugin fixes • Lifecycle behavior changes and fixes • Nested graphs fixes • Logic and memory leak fixes • NavHostFragment will always set the current Fragment as the primary navigation fragment, ensuring that child fragment managers are popped before the outer NavController is popped • Fixed conflicts with ConstraintLayout and Ko • Safe Args error message clickable • Safe Args types and nullability fixes • back after deep linking issue fix • Backstack behavior fixes • Safe Arg fixes • Add missing nullability annotations. 1.0.0-alpha04 1.0.0-alpha05 1.0.0-alpha01 Timeline изменений
  43. Можно ли использовать? Для чего подходит 1 Как часть навигации

    (на одном из экранов) [Помним про Jetifier] 2 Проекты с минимальным числом программистов 3 Для проектов - лендингов 4 Пет-проекты
  44. Выводы Библиотека в разработке и не готова для продакшена Для

    полной навигации нужны надстройки Решение для “Быстрого старта” Копайте исходники — это захватывающе
  45. О чем мы говорили? Выводы Библиотека в разработке и не

    готова для продакшена Для полной навигации нужны надстройки Решение для “Быстрого старта” Копайте исходники — это захватывающе 3 % 8 % 12 % 69 % 8 % Что это? Слышал, не пробовал Смотрел в эдиторе Использую в своём петпроджекте Использую в живом проект Кто работал с новой навигацией от Google?