Как использовал sealed interface в своих проектах
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование Sealed Interface и Sealed Class в Android/Kotlin проектах
В своих проектах я активно использую sealed interface (а ранее, до Kotlin 1.5 - sealed class) как мощный инструмент для моделирования ограниченных иерархий типов, особенно при работе с state management, event handling и API response handling. Это один из ключевых паттернов для создания безопасного и выразительного кода.
Основные области применения
1. Моделирование состояний UI (State Management)
Чаще всего sealed интерфейсы используются для представления состояния экрана или компонента.
sealed interface UserProfileState {
data class Loading(val progress: Int? = null) : UserProfileState
data class Success(val user: User, val avatarUrl: String?) : UserProfileState
data class Error(val message: String, val code: Int) : UserProfileState
object Empty : UserProfileState
}
// Использование в ViewModel или StateFlow
class ProfileViewModel {
private val _state = MutableStateFlow<UserProfileState>(UserProfileState.Empty)
val state: StateFlow<UserProfileState> = _state.asStateFlow()
fun loadUser() {
_state.value = UserProfileState.Loading()
// ... загрузка данных
}
}
// Безопасная обработка в UI (Compose или View-based)
when(state) {
is UserProfileState.Loading -> ShowProgressBar(progress)
is UserProfileState.Success -> ShowUserData(state.user)
is UserProfileState.Error -> ShowErrorMessage(state.message)
UserProfileState.Empty -> ShowPlaceholder()
}
Преимущества: компилятор гарантирует, что мы покрыли все возможные состояния, что исключает ошибки и делает код более надежным.
2. Обработка событий и действий (Event Handling)
Для одноразовых событий (например, навигация, показ снеков) я использую sealed интерфейсы в сочетании с SharedFlow или Channel.
sealed interface ProfileEvent {
data class ShowToast(val message: String) : ProfileEvent
data class NavigateToSettings(val userId: String) : ProfileEvent
data class OpenDialog(val type: DialogType) : ProfileEvent
object LogoutConfirmed : ProfileEvent
}
class ProfileEventDispatcher {
private val _events = MutableSharedFlow<ProfileEvent>()
val events = _events.asSharedFlow()
fun emitEvent(event: ProfileEvent) {
viewModelScope.launch {
_events.emit(event)
}
}
}
3. Результаты операций и ответы API
При работе с репозиториями и источниками данных sealed интерфейсы идеально подходят для представления результатов.
sealed interface DataResult<out T> {
data class Success<T>(val data: T) : DataResult<T>
data class Error(val exception: Throwable, val userMessage: String? = null) : DataResult<Nothing>
object NetworkUnavailable : DataResult<Nothing>
}
// Использование в репозитории
class UserRepository {
suspend fun fetchUser(id: String): DataResult<User> {
return try {
val response = apiService.getUser(id)
DataResult.Success(response.toUser())
} catch (e: IOException) {
DataResult.NetworkUnavailable
} catch (e: Exception) {
DataResult.Error(e, "Failed to load user")
}
}
}
4. Конфигурация и параметры компонентов
Для сложных компонентов с различными режимами работы sealed интерфейсы помогают четко определить допустимые конфигурации.
sealed interface CardConfiguration {
data class Standard(val title: String, val subtitle: String?) : CardConfiguration
data class Compact(val iconRes: Int, val text: String) : CardConfiguration
data class Interactive(
val primaryAction: Action,
val secondaryActions: List<Action>
) : CardConfiguration
object Placeholder : CardConfiguration
}
Ключевые преимущества и best practices
- Безопасность типов: Компилятор Kotlin знает все возможные подтипы, что делает
whenвыражения exhaustive без необходимостиelseветки. - Выразительность: Каждый подтип может содержать специфичные данные (используя
data class) или быть маркером (используяobject). - Совместимость с корутинами и Flow: Sealed интерфейсы идеально сочетаются с современными асинхронными паттернами Kotlin.
- Эволюция кода: Добавление нового подтипа требует явной обработки во всех
whenвыражениях, что предотвращает забытые случаи.
Переход от Sealed Class к Sealed Interface
После выхода Kotlin 1.5 с поддержкой sealed interface, я начал迁移цию в случаях, где:
- Необходима возможность реализации несколькими независимыми классами
- Нужно создавать более сложные иерархии (например, комбинация нескольких sealed интерфейсов)
- Классы уже имеют другого родителя, но должны быть частью ограниченной иерархии
// Пример комбинации sealed интерфейсов
sealed interface NetworkResult
sealed interface ValidationResult
data class UserData(
val name: String,
val email: String
) : NetworkResult, ValidationResult
// Это невозможно с sealed class, но возможно с sealed interface
В итоге, sealed interface стал неотъемлемой частью моей архитектуры Android приложений, значительно повысив читаемость, безопасность и поддерживаемость кода, особенно в сочетании с реактивными паттернами (MVVM, MVI) и современными Android компонентами (Jetpack Compose).