TL; DR Оберніть navigate
дзвінки try-catch
(простим способом) або переконайтесь, що буде лише один дзвінокnavigate
короткий проміжок часу. Ця проблема, ймовірно, не зникне. Скопіюйте більший фрагмент коду у свою програму та спробуйте.
Привіт. Спираючись на пару корисних відповідей, я хотів би поділитися своїм рішенням, яке можна розширити.
Ось код, який спричинив цей збій у моїй програмі:
@Override
public void onListItemClicked(ListItem item) {
Bundle bundle = new Bundle();
bundle.putParcelable(SomeFragment.LIST_KEY, item);
Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}
Спосіб легко відтворити помилку - натискання кількома пальцями на список елементів, де натискання кожного елемента вирішується в навігації на новий екран (в основному те саме, що відмітили люди - два чи більше кліків за дуже короткий проміжок часу ). Я помітив що:
- Перша
navigate
виклик завжди працює добре;
- Другий та всі інші виклики
navigate
методу вирішуються в IllegalArgumentException
.
З моєї точки зору, така ситуація може виникати дуже часто. Оскільки повторення коду є поганою практикою, і завжди добре мати одну точку впливу, я подумав про наступне рішення:
public class NavigationHandler {
public static void navigate(View view, @IdRes int destination) {
navigate(view, destination, /* args */null);
}
/**
* Performs a navigation to given destination using {@link androidx.navigation.NavController}
* found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
* multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
* The navigation must work as intended.
*
* @param view the view to search from
* @param destination destination id
* @param args arguments to pass to the destination
*/
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
try {
Navigation.findNavController(view).navigate(destination, args);
} catch (IllegalArgumentException e) {
Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
}
}
}
І таким чином код вище змінюється лише в одному рядку від цього:
Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
до цього:
NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);
Це навіть стало трохи коротше. Код був протестований у тому місці, де стався збій. Більше цього не відчували, і буде використовувати те саме рішення для інших навігацій, щоб уникнути тієї самої помилки.
Будь-які думки вітаються!
Що саме викликає збій
Пам'ятайте, що тут ми працюємо з тим самим графіком навігації, контролером навігації та резервним стеком, коли використовуємо метод Navigation.findNavController
.
Тут ми завжди отримуємо один і той же контролер і графік. Коли navigate(R.id.my_next_destination)
викликається графік і зворотний стек змінюється майже миттєво, поки інтерфейс користувача ще не оновлюється. Просто не досить швидко, але це нормально. Після зміни резервного стека навігаційна система отримує другий navigate(R.id.my_next_destination)
дзвінок. Оскільки бек-стек змінився, тепер ми працюємо відносно верхнього фрагмента стека. Верхній фрагмент - це фрагмент, до якого ви переходите, використовуючи R.id.my_next_destination
, але він не містить наступних пунктів призначення з ідентифікатором R.id.my_next_destination
. Таким чином ви отримуєте IllegalArgumentException
через ідентифікатор, про який фрагмент нічого не знає.
Цю точну помилку можна знайти в NavController.java
методі findDestination
.