Какие знаешь способы передачи данных между экранами в Jetpack Compose Navigation?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы передачи данных между экранами в 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)
}
}
}
Рекомендации по выбору подхода:
- Для простых данных - используйте аргументы навигации
- Для сложных объектов - рассмотрите общий ViewModel или DI
- Для конфигурационных данных - используйте SavedStateHandle
- Для глобального состояния - применяйте CompositionLocal или DI
- Избегайте прямых ссылок на экраны - это нарушает принципы декларативного программирования
Важное замечание: При передаче данных между экранами следует помнить о конфигурационных изменениях (поворот экрана) и процессной смерти. Для сохранения состояния используйте rememberSaveable с Saver-объектами или ViewModel с SavedStateHandle.
Каждый метод имеет свои компромиссы между простотой использования, безопасностью типа и сохранением состояния. В современных приложениях Jetpack Compose часто комбинируют несколько подходов, используя аргументы для идентификаторов и ViewModel/DI для загрузки полных данных.