← Назад к вопросам

Какие знаешь способы передачи данных между экранами в Jetpack Compose Navigation?

1.2 Junior🔥 121 комментариев
#UI и вёрстка#Жизненный цикл и навигация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы передачи данных между экранами в Jetpack Compose Navigation

В Jetpack Compose Navigation существует несколько основных подходов для передачи данных между экранами, каждый со своей областью применения и особенностями. Выбор конкретного способа зависит от типа данных, их объема и требуемого времени жизни.

1. Аргументы навигации (Navigation Arguments)

Самый простой и распространенный способ для передачи примитивных типов данных через deep links или прямой навигации.

Настройка в NavGraph:

// Объявление маршрута с аргументами
composable(
    route = "details/{itemId}/{itemName}",
    arguments = listOf(
        navArgument("itemId") { type = NavType.IntType },
        navArgument("itemName") { type = NavType.StringType }
    )
) { backStackEntry ->
    val itemId = backStackEntry.arguments?.getInt("itemId")
    val itemName = backStackEntry.arguments?.getString("itemName")
    DetailsScreen(itemId, itemName)
}

Использование:

// Навигация с передачей аргументов
navController.navigate("details/$id/$name")

// Или с помощью метода расширения
navController.navigate("details/${item.id}/${item.name}")

Преимущества:

  • Встроенная валидация типов данных
  • Поддержка безопасных аргументов через плагин safe-args
  • Прозрачность в deep linking

Ограничения:

  • Подходит только для простых типов (String, Int, Float, Boolean, Reference)
  • Нельзя передавать сложные объекты

2. Передача сложных объектов через SavedStateHandle

Для передачи Parcelable или Serializable объектов можно использовать SavedStateHandle, но с учетом ограничений.

Создание custom NavType:

// Для Parcelable объектов
val MyParcelableType = object : NavType<MyParcelable>(
    isNullableAllowed = false
) {
    override fun get(bundle: Bundle, key: String): MyParcelable? {
        return bundle.getParcelable(key)
    }
    
    override fun put(bundle: Bundle, key: String, value: MyParcelable) {
        bundle.putParcelable(key, value)
    }
    
    override fun parseValue(value: String): MyParcelable {
        // Десериализация из строки
        return Gson().fromJson(value, MyParcelable::class.java)
    }
}

// Регистрация в NavGraph
composable(
    route = "details?item={item}",
    arguments = listOf(
        navArgument("item") {
            type = MyParcelableType
            nullable = true
        }
    )
)

3. ViewModel с общим ViewModelStoreOwner

Для передачи данных между экранами, которые принадлежат одному графу или имеют общего предка.

// Общий ViewModel для нескольких экранов
class SharedViewModel : ViewModel() {
    val sharedData = MutableStateFlow<String?>(null)
}

// Использование в родительском компоненте
val sharedViewModel: SharedViewModel = viewModel()

// В дочерних экранах
val sharedViewModel: SharedViewModel = viewModel(
    viewModelStoreOwner = NavBackStackEntry(/* ссылка на общий owner */)
)

4. Состояние в Navigation Graph

Использование состояния на уровне всего графа навигации через rememberSaveable или постоянное хранилище.

// Создание менеджера состояния
class NavigationStateManager {
    private val _sharedData = mutableStateOf<SharedData?>(null)
    val sharedData: State<SharedData?> get() = _sharedData
    
    fun updateData(data: SharedData) {
        _sharedData.value = data
    }
}

// Включение через CompositionLocal
val LocalNavigationState = staticCompositionLocalOf<NavigationStateManager> { 
    error("NavigationState not provided") 
}

// Использование в экранах
val stateManager = LocalNavigationState.current
stateManager.updateData(myData)

5. Использование Dependency Injection (DI)

Инъекция зависимостей через Hilt/Dagger или Koin для доступа к общим репозиториям или источникам данных.

// Общий репозиторий
class SharedRepository @Inject constructor() {
    private val _selectedItem = MutableStateFlow<Item?>(null)
    val selectedItem: StateFlow<Item?> = _selectedItem.asStateFlow()
    
    fun selectItem(item: Item) {
        _selectedItem.value = item
    }
}

// Использование в экранах
@HiltViewModel
class DetailsViewModel @Inject constructor(
    private val repository: SharedRepository
) : ViewModel() {
    val selectedItem = repository.selectedItem.collectAsState()
}

6. Использование Callbacks и Event Bus (не рекомендуется)

// Создание общего канала событий
object NavigationEventBus {
    private val _events = MutableSharedFlow<NavigationEvent>()
    val events = _events.asSharedFlow()
    
    suspend fun sendEvent(event: NavigationEvent) {
        _events.emit(event)
    }
}

// Подписка в целевом экране
LaunchedEffect(Unit) {
    NavigationEventBus.events.collect { event ->
        when (event) {
            is DataEvent -> handleData(event.data)
        }
    }
}

Рекомендации по выбору подхода:

  1. Для простых данных - используйте аргументы навигации
  2. Для сложных объектов - рассмотрите общий ViewModel или DI
  3. Для конфигурационных данных - используйте SavedStateHandle
  4. Для глобального состояния - применяйте CompositionLocal или DI
  5. Избегайте прямых ссылок на экраны - это нарушает принципы декларативного программирования

Важное замечание: При передаче данных между экранами следует помнить о конфигурационных изменениях (поворот экрана) и процессной смерти. Для сохранения состояния используйте rememberSaveable с Saver-объектами или ViewModel с SavedStateHandle.

Каждый метод имеет свои компромиссы между простотой использования, безопасностью типа и сохранением состояния. В современных приложениях Jetpack Compose часто комбинируют несколько подходов, используя аргументы для идентификаторов и ViewModel/DI для загрузки полных данных.