Flow — это асинхронный поток данных из библиотеки Kotlin Coroutines
. Он позволяет обрабатывать последовательности данных, которые могут изменяться со временем.
Flow
похож на LiveData
, но предоставляет больше гибкости и возможностей для работы с асинхронными данными.
- Асинхронность -
Flow
работает с корутинами, что делает его идеальным для асинхронных операций. - Гибкость -
Flow
поддерживает множество операторов (например,map
,filter
,flatMap
), которые позволяют трансформировать данные. - Холодный поток -
Flow
является "холодным" потоком, то есть данные начинают поступать только после вызоваcollect
.
/**
* Flow, хранящий текущее состояние пользователей.
*
* @see UserState Состояние, которое хранится в этом Flow.
*/
private val _state: MutableStateFlow<UserState> = MutableStateFlow(UserState())
/**
* Публичный Flow, который предоставляет доступ к текущему состоянию пользователей.
*
* @see UserState Состояние, которое предоставляется этим Flow.
*/
val state: StateFlow<UserState> = _state.asStateFlow()
/**
* Загружает пользователя по его идентификатору.
*
* @param userId Идентификатор пользователя, которого нужно загрузить.
*/
fun getUserById(userId: Long) {
_state.update { stateUser: UserState ->
stateUser.copy(
statusUser = StatusLoad.Loading
)
}
viewModelScope.launch {
try {
val user: UserData = repository.getUserById(userId = userId)
_state.update { stateUser: UserState ->
stateUser.copy(
statusUser = StatusLoad.Idle,
users = listOf(user)
)
}
} catch (e: Exception) {
_state.update { stateUser: UserState ->
stateUser.copy(
statusUser = StatusLoad.Error(exception = e)
)
}
}
}
}
/**
* Наблюдает за состоянием ViewModel пользователя и обновляет UI в зависимости от текущего состояния.
* Этот метод связывает состояние ViewModel с элементами интерфейса, такими как ProgressBar, SwipeRefreshLayout,
* TextView для ошибок, а также отображает данные пользователя (аватар, имя и т.д.).
*
* @param userViewModel Экземпляр [UserViewModel], который предоставляет состояние пользователя.
* @param binding Экземпляр [FragmentAccountBinding], используемый для доступа к элементам интерфейса.
* @param userId Идентификатор пользователя, используемый для проверки и отображения данных.
*
* @see UserState Состояние пользователя, которое содержит данные о загрузке, ошибках и информации о пользователе.
* @see FragmentAccountBinding Связывает элементы интерфейса с кодом.
* @see UserViewModel ViewModel, управляющая состоянием пользователя.
*
* @throws NullPointerException Если контекст или ресурсы недоступны.
*
* @property userState Текущее состояние пользователя, которое может быть:
* - [UserState.isEmptyLoading] — состояние загрузки.
* - [UserState.isRefreshing] — состояние обновления.
* - [UserState.isEmptyError] — состояние ошибки.
* - [UserState.users] — данные пользователя.
*/
private fun userViewModelState(
userViewModel: UserViewModel,
binding: FragmentAccountBinding,
userId: Long,
accountUserId: Long,
) {
userViewModel.state
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach { userState: UserState ->
binding.progressBar.isVisible = userState.isEmptyLoading
binding.swiperRefresh.isRefreshing = userState.isRefreshing
binding.errorGroup.isVisible = userState.isEmptyError
binding.avatarUser.isVisible = !userState.isEmptyError && !userState.isEmptyLoading
binding.initial.isVisible = !userState.isEmptyError && !userState.isEmptyLoading
binding.nameUser.isVisible = !userState.isEmptyError && !userState.isEmptyLoading
binding.tabLayout.isVisible = !userState.isEmptyError && !userState.isEmptyLoading
binding.viewPagerPostsAndEvents.isVisible =
!userState.isEmptyError && !userState.isEmptyLoading
val errorText: CharSequence? =
userState.statusUser.throwableOrNull?.getErrorText(requireContext())
binding.errorText.text = errorText
if (userState.isRefreshError && errorText == getString(R.string.network_error)) {
requireContext().toast(R.string.network_error)
userViewModel.consumerError()
} else if (userState.isRefreshError && errorText == getString(R.string.unknown_error)) {
requireContext().toast(R.string.unknown_error)
userViewModel.consumerError()
}
userState.users?.firstOrNull()?.let { user: UserData ->
binding.nameUser.text = user.name
binding.skeletonAttachment.showSkeleton()
if (!user.avatar.isNullOrEmpty()) {
Glide.with(binding.root)
.load(user.avatar)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
showPlaceholder(binding, user)
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
binding.skeletonAttachment.showOriginal()
binding.initial.isVisible = false
return false
}
})
.transition(DrawableTransitionOptions.withCrossFade(500))
.error(R.drawable.ic_404_24)
.thumbnail(
Glide.with(binding.root)
.load(user.avatar)
.override(50, 50)
.diskCacheStrategy(DiskCacheStrategy.ALL)
)
.into(binding.avatarUser)
} else {
showPlaceholder(binding, user)
}
if (userId != accountUserId) {
val toolbar = requireActivity().findViewById<Toolbar>(R.id.toolbar)
toolbar.title = user.login
}
}
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
LiveData — это компонент архитектуры Android
, который позволяет наблюдать за изменениями данных в реальном времени.
Он особенно полезен для работы с UI
, так как автоматически обновляет данные при изменении и учитывает жизненный цикл компонентов (например, Activity
или Fragment
).
- Наблюдение за данными -
LiveData
позволяет подписаться на изменения данных и автоматически обновлятьUI
. - Учет жизненного цикла -
LiveData
автоматически управляет подписками, учитывая жизненный цикл компонентов (например, отписывается при уничтоженииActivity
). - Потокобезопасность -
LiveData
гарантирует, что данные будут обновлены в главном потоке.
/**
* ViewModel для обмена данными между фрагментами.
*
* Этот ViewModel используется для хранения и передачи данных между фрагментами, которые не имеют прямого
* взаимодействия друг с другом. В частности, он используется для отслеживания текущей вкладки в `ViewPager2`
* и передачи этой информации в `BottomNavigationFragment` для управления поведением кнопки создания нового поста или события.
*
* @property currentTab LiveData, содержащая текущую выбранную вкладку в `ViewPager2`.
* Значение 0 соответствует вкладке с постами, а значение 1 — вкладке с событиями.
* По умолчанию значение не установлено.
*
* @see ViewModel Базовый класс для ViewModel, который сохраняет данные при изменении конфигурации.
* @see MutableLiveData Класс для хранения данных, которые могут изменяться и наблюдаться.
*
* @throws IllegalArgumentException Может быть выброшено, если передано некорректное значение для `currentTab`.
*/
class SharedViewModel : ViewModel() {
val currentTab = MutableLiveData<Int>()
}
private val sharedViewModel: SharedViewModel by activityViewModels()
sharedViewModel.currentTab.observe(viewLifecycleOwner) { tabPosition ->
when (tabPosition) {
0 -> binding.news.setOnClickListener(postsClickListener)
1 -> binding.news.setOnClickListener(eventsClickListener)
2 -> binding.news.setOnClickListener(jobsClickListener)
}
}
binding.viewPagerPostsAndEvents.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
sharedViewModel.currentTab.value = position
}
}
)
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> get() = _data
fun updateData(newData: String) {
_data.value = newData
}
}
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.data.observe(this, Observer { newData ->
// Обновление UI
textView.text = newData
})
// Обновление данных
viewModel.updateData("Hello, LiveData!")
}
}
State — это концепция, которая используется для управления состоянием UI
в Jetpack Compose
.
State
позволяет автоматически обновлять UI
при изменении данных.
- Реактивность -
State
автоматически обновляетUI
при изменении данных. - Интеграция с Compose -
State
используется вJetpack Compose
для управления состоянием компонентов. - Простота -
State
легко использовать для управления простыми состояниями.
- LiveData:
Работает только в главном потоке. Не поддерживает асинхронные операции напрямую. - Flow:
Полностью асинхронный, работает с корутинами. Подходит для сложных асинхронных операций.
- LiveData:
Автоматически учитывает жизненный цикл компонентов (например,Activity
илиFragment
). - Flow:
Требует использованияlifecycleScope
илиviewModelScope
для учета жизненного цикла.
- LiveData:
Ограничен в возможностях трансформации данных. - Flow:
Поддерживает множество операторов (например,map
,filter
,flatMap
), что делает его более гибким.
- LiveData:
Горячий поток — данные доступны сразу после создания. - Flow:
Холодный поток — данные начинают поступать только после вызоваcollect
.
- LiveData:
Не интегрируется напрямую сJetpack Compose
. - Flow:
Легко интегрируется сJetpack Compose
черезcollectAsState
.
- Для простых сценариев, где нужно обновлять
UI
при изменении данных. - Когда требуется автоматическое управление жизненным циклом.
- Для простых сценариев, где нужно обновлять UI при изменении данных.
- Когда требуется автоматическое управление жизненным циклом.
- Для сложных асинхронных операций.
- Когда требуется гибкость в трансформации данных.
- В
Jetpack Compose
для управления состоянием. - Для сложных асинхронных операций.
- Когда требуется гибкость в трансформации данных.
- В Jetpack Compose для управления состоянием.
Критерий | LiveData | Flow |
---|---|---|
Асинхронность | Работает только в главном потоке. | Полностью асинхронный, работает с корутинами. |
Учет жизненного цикла | Автоматически учитывает жизненный цикл (Activity, Fragment). | Требует использования lifecycleScope или viewModelScope . |
Гибкость | Ограничен в возможностях трансформации данных. | Поддерживает множество операторов (map , filter , flatMap и др.). |
Тип потока | Горячий поток (данные доступны сразу). | Холодный поток (данные начинают поступать после вызова collect ). |
Потокобезопасность | Гарантирует обновление данных в главном потоке. | Требует явного указания диспетчера (например, Dispatchers.Main ). |
Интеграция с Jetpack Compose | Не интегрируется напрямую. | Легко интегрируется через collectAsState . |
Использование | Подходит для простых сценариев с автоматическим учетом жизненного цикла. | Подходит для сложных асинхронных операций и интеграции с Jetpack Compose. |
- LiveData — Простой и удобный способ наблюдать за данными с учетом жизненного цикла.
- Flow — Гибкий и мощный инструмент для работы с асинхронными данными.
- State — Используется в
Jetpack Compose
для управления состояниемUI
. - Сравнение:
- LiveData: Подходит для простых сценариев с автоматическим учетом жизненного цикла.
- Flow: Подходит для сложных асинхронных операций и интеграции с
Jetpack Compose
.