Передача данных при навигации
Реализация передачи данных между фрагментами
Переход с одного фрагмента на другой осуществляется средствами Navigation Component.
Отправка данных между фрагментами происходит во время перехода по графу навигации, например, с помощью метода navigate.
public void navigate(@IdRes int resId, @Nullable Bundle args)
Разберем всю цепочку на примере перехода между двумя фрагментами, для активити принцип будет такой же.
Допустим, мы хотим перейти с FirstFragment на SecondFragment и передать userId.
Поскольку Bundle - это key-value хранилище, нам нужен секретный ключ для userId, который бы знал только фрагмент-получатель (SecondFragment), но сохранить по этому ключу userId мог и фрагмент-отправитель (FirstFragment).
Для этого создадим companion object в SecondFragment
companion object {
    private const val USER_ID_KEY = "userIdKey"
    fun createArguments(userIdKey: String): Bundle {
        return bundleOf(USER_ID_KEY to userIdKey)
    }
}
Когда нужно будет перейти c FirstFragment на SecondFragment вызовем следующий метод:
fun routeToSecondFragment(userIdKey: String) {
    findNavController().navigate(
        R.id.action_firstFragment_to_secondFragment,
        SecondFragment.createArguments(userIdKey = editText.text.toString())
    )
}
Получать данные из аргументов SecondFragment будет так:
private val userId: String
    get() = requireArguments().getString(USER_ID_KEY).let { requireNotNull(it) }
В случае отсутствии аргумента на инициализации фрагмента, произойдет ошибка:
E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.testsharingdata, PID: 26781
java.lang.IllegalArgumentException: Required value was null.
Также, можно добавить сообщение, которое отобразится при ошибке:
private val userId: String
    get() = requireArguments().getString(USER_ID_KEY).let { requireNotNull(it) { "argument $USER_ID_KEY should be not null" } }
В случае ошибки, увидим сообщение:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testsharingdata, PID: 26906
java.lang.IllegalArgumentException: argument userIdKey should be not null
Передача данных между активити
Для перехода между активити и отправки данных используется объект Intent.
Переходить будем с MainActivity на SecondActivity, будем также передавать userId
В SecondActivity создадим companion object:
companion object {
    private const val USER_ID_KEY = "user id key"
    fun createIntent(context: Context, userId: String): Intent {
        val intent = Intent(context, SecondActivity::class.java)
        intent.putExtra(USER_ID_KEY, userId)
        return intent
    }
}
Когда решим переходить на другую активити, вызовем метод
private fun routeToSecondActivity(userId: String) {
    val intent = SecondActivity.createIntent(context = requireContext(), userId)
    startActivity(intent)
}
Получение аргументов на активити:
private val userId: String
    get() = requireNotNull(intent.getStringExtra(USER_ID_KEY)) { "argument $USER_ID_KEY should be not null" }
Какие данные можно передавать?
Если данные, которые необходимы экрану, можно получить от какого-либо источника данных (база данных, сервер и пр.), то следует передавать идентификатор этих данных. Для использования такого подхода - передавать идентификатор данных, необходимо следующее требование к источнику данных: для каждого экрана приложения источник обязательно должен уметь получать все необходимые данные даже после полного пересоздания приложения.
Однако, бывают ситуации, когда какие-то простые данные уже появились, но их еще рано сохранять в базу данных или отправлять на сервер. В таком случае стоит передавать сами данные, потому что другой возможности получить их у нас не будет.
Например: текстовое поле на экране A, текст из которого отобразится в форме-заполнения на экране B. В этом случае нам нужно поддерживать следующее поведение:  
- если приложение будет закрыто непосредственно юзером, то данные должны пропасть при следующем открытии экрана 
A - если приложение закроется самостоятельно, например, из-за ошибки или отсутствия свободной памяти на устройстве, то введенные в поле данные пропасть не должны
 
Достигается такое поведение благодаря методам onSaveInstanceState и onRestoreInstanceState
Разберем ещё один пример: допустим, мы открыли экран - список книг. Загрузили все ссылки на книги из списка в оперативную память, хотим перейти на чтение конкретной - передали id этой книги при переходе на экран чтения. Внезапно, что-то пошло не так и наше приложение крашнулось. Приложение открывается заново, на детальном просмотре интересующей нас книги. Id нужной книги лежит у нас в аргументах, но в оперативной памяти нет никаких ссылок на книги, потому что это сохранение происходило на предыдущем экране. Об этом стоит помнить и учитывать это при проектировании репозитория. Данные можно хранить в оперативной памяти, однако нужно еще обрабатывать ситуации, когда этих данных там не будет - либо обращаться к серверу и получать то, что нас интересует, либо сохранять данные в локальную базу данных и работать уже с ней, а не оперативной памятью.
Таким образом, передача только идентификаторов и простых данных позволит вам:
- не поддерживать Parcelable у передаваемых данных
 - не бояться превышения объема хранилища Bundle. При пересоздании фрагмента/активити переданные аргументы сохраняются в 
Bundle, но он вмещает всего 1 МБ данных и содержит не только наши данные, но и системные. При превышении объема хранилища произойдет ошибка TransactionTooLargeException 
Выводы
Используя подход передачи идентификаторов данных и группировка всей логики передачи в получателе позволяет:
- не усложнять модели данных поддержкой 
Parcellable - не вылезем за пределы размера 
Bundle - скрыть детали перехода на экран от других классов
 - избавиться от дублирования. Теперь, для перехода на фрагмент/активити со значениями не нужно самостоятельно создавать объект 
Bundleи заполнять его - строгое API. Перейти на фрагмент/активити и передать туда данные, не используя дублирование и прочие костыли, можно только используя специальную функцию из компаньон объекта