Разработка
Объединяем Compose и View: бесшовное взаимодействие с помощью CompositionLocal
Такой подход не только упростил взаимодействие между Compose и традиционными View, но и ускорил разработку множества экранов в приложении.
При переходе на Compose наша команда столкнулась с рядом проблем, связанных с совместимостью, поскольку мы интегрировали новые композабл в существующую кодовую базу. Одним из ключевых препятствий было определение того, как вызывать события из Compose и отправлять их основному держателю представления, такому как Фрагмент или Активити.
После некоторых исследований мы нашли эффективное решение с использованием CompositionLocal
. Вот как мы его реализовали! 🚀
Что такое CompositionLocal
?
Думайте о CompositionLocal
как о локализованной системе инъекции зависимостей, встроенной непосредственно в модель композиции Compose. Вы создаете экземпляр CompositionLocal
, который выступает в качестве ключа для определенного типа данных. Затем, используя CompositionLocalProvider
, вы определяете область видимости в дереве UI и связываете значение с этим ключом CompositionLocal
. Любой компонент, находящийся в этой области, может получить доступ к предоставленному значению с помощью свойства CompositionLocal.current
, при этом не нужно знать, откуда взялось это значение. Это не только упрощает код, но и делает его более удобным для сопровождения и многократного использования.
Кроме того, значения CompositionLocal
могут быть динамическими, вызывая перекомпозицию при их изменении, и они потокобезопасны, что делает их мощным инструментом для управления общим состоянием в вашем пользовательском интерфейсе Compose.
Взаимодействие с CompositionLocal
В следующем коде LocalFeedbackInterop
— это staticCompositionLocalOf
, который будет содержать реализацию интерфейса FeedbackMessageInterop
. Этот интерфейс определяет методы отображения предупреждений, а staticCompositionLocalOf
гарантирует, что реализация вряд ли изменится, так как ее нельзя будет пересоздать. Это позволяет любому компоненту в пределах скоупа получить доступ к реализации FeedbackMessageInterop
и вызывать алерты без явных зависимостей.
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf
val LocalFeedbackInterop: ProvidableCompositionLocal<FeedbackMessageInterop?> = staticCompositionLocalOf { null }
interface FeedbackMessageInterop {
fun showError(message: String)
fun showSuccess(message: String)
fun showInfo(message: String)
}
Из нашего кода Compose мы можем получить экземпляр CompositionLocal
для доступа к реализации FeedbackMessageInterop
и запустить наш компонент с алертами на основе View, который полагается на состояние ошибки, полученное из ViewModel.
xxxxxxxxxx
fun Content() {
val feedbackHost = LocalFeedbackInterop.current
LaunchedEffect(feedbackHost) {
viewModel.error.collect {
feedbackHost?.showError(
message = "My error message",
)
}
}
}
На стороне Фрагмента или Активити мы можем легко объявить локальную реализацию FeedbackMessageInterop
, которая будет предоставлена нашему CompositionLocalProvider
для связи с нашим кодом Compose. Вот и все!
xxxxxxxxxx
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
CompositionLocalProvider(
LocalFeedbackInterop provides localFeedback,
) {
TractorTheme {
CurrentScreen()
}
}
}
}
}
private val localFeedback: FeedbackMessageInterop = object : FeedbackMessageInterop {
override fun showError(message: String) {
lifecycleScope.launch {
parentFragment?.view?.findViewById<View>(R.id.rootBottomSheetModalView)?.let { root ->
showErrorFeedbackMessage(root.parent as ViewGroup) {
this.message = message
offsetPositionTop = R.dimen.feedbackOffsetPositionTopSmall
}
}
}
}
}
Возможен и другой вариант: вы можете вызвать код Compose, взаимодействуя с реализацией интерфейса из кода, основанного на представлении! Одним из возможных решений было бы выставление StateFlow
или SharedFlow
из реализации, предоставленной CompositionLocalProvider
, чтобы его можно было получить на стороне Compose.
xxxxxxxxxx
// On the view side
private val localBottomSheet = object : BottomSheetInterop {
private val rightAction: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST)
override val onRightActionClicked: SharedFlow<Unit> = rightAction
override fun someFun() {
setOnRightActionClickListener { rightAction.tryEmit(Unit) }
}
}
// On the Compose side
val bottomSheet = LocalBottomSheetInterop.currentOrThrow
LaunchedEffect(bottomSheet) {
bottomSheet.onRightActionClicked.collect {
viewModel.doSomething()
}
}
Изучив возможности CompositionLocal
в Compose, мы смогли без проблем улучшить взаимодействие с существующими компонентами на основе представлений. Такой подход не только упростил взаимодействие между Compose и традиционными View, но и ускорил разработку множества экранов в приложении.
Не стесняйтесь писать мне на LinkedIn, если у вас есть вопросы.
-
Программирование3 недели назад
Конец программирования в том виде, в котором мы его знаем
-
Видео и подкасты для разработчиков6 дней назад
Как устроена мобильная архитектура. Интервью с тех. лидером юнита «Mobile Architecture» из AvitoTech
-
Магазины приложений3 недели назад
Магазин игр Aptoide запустился на iOS в Европе
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.8