Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
В MVI (Model-View-Intent) есть проблема: события, которые не нужно хранить в
State, могут повторно отобразиться при пересоздании экрана (например, при повороте экрана или навигации назад). Показ
Toast / Snackbar Навигация (
openScreen()) Ошибки, которые нужно показать один раз
Это
LiveData, которая отправляет событие только один раз и не пересылает его новым подписчикам. class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { value ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(value)
}
}
}
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
}ViewModel с
SingleLiveEvent class MyViewModel : ViewModel() {
val eventShowToast = SingleLiveEvent<String>()
fun onButtonClicked() {
eventShowToast.value = "Привет, это Toast!"
}
}Activity/Fragment подписывается
viewModel.eventShowToast.observe(viewLifecycleOwner) { message ->
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}В
StateFlow все события хранятся в State (что не подходит для одноразовых событий). Вместо этого используем
SharedFlow с replay = 0, чтобы событие не повторялось. ViewModel с
SharedFlow class MyViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun onButtonClicked() {
viewModelScope.launch {
_events.emit(UiEvent.ShowToast("Привет, это Toast!"))
}
}
}
sealed class UiEvent {
data class ShowToast(val message: String) : UiEvent()
object NavigateToNextScreen : UiEvent()
}Подписка в UI:
lifecycleScope.launchWhenStarted {
viewModel.events.collect { event ->
when (event) {
is UiEvent.ShowToast -> Toast.makeText(context, event.iss.onessage, Toast.LENGTH_SHORT).show()
is UiEvent.NavigateToNextScreen -> findNavController().navigate(R.id.nextFragment)
}
}
}Если в
State всё-таки нужно хранить событие, но не показывать его повторно, можно использовать EventWrapper. class EventWrapper<out T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) null else {
hasBeenHandled = true
content
}
}
}ViewModel с
StateFlow: data class UiState(val message: EventWrapper<String>? = null)
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
fun onButtonClicked() {
_state.value = UiState(message = EventWrapper("Привет, это Toast!"))
}
}
UI подписывается и проверяет
EventWrapper: viewModel.state.collect { state ->
state.message?.getContentIfNotHandled()?.let { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Да, есть.
- В launch {} ошибка автоматически передается вверх и может быть обработана CoroutineExceptionHandler.
- В async {} ошибки НЕ передаются автоматически, они остаются внутри Deferred<T>.
- Чтобы поймать ошибку в async {}, нужно вызвать await() внутри try-catch.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Проблемы с элементами списка в Android-приложениях могут быть разнообразными. Давайте рассмотрим некоторые из наиболее распространённых проблем и способы их решения.
// Пример использования Glide для загрузки изображений
Glide.with(context)
.load(imageUrl)
.into(imageView)
class MyAdapter(private val itemList: List<Item>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.textView)
val imageView: ImageView = itemView.findViewById(R.id.imageView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.textView.text = item.text
Glide.with(holder.itemView.context).load(item.imageUrl).into(holder.imageView)
}
override fun getItemCount() = itemList.size
} class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
// Использование в адаптере
val diffCallback = ItemDiffCallback()
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(myAdapter)val liveData = MutableLiveData<List<Item>>()
liveData.observe(viewLifecycleOwner, Observer { items ->
myAdapter.submitList(items)
})
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
DiffUtil используется в RecyclerView.Adapter для эффективного обновления списка:
- Вычисляет разницу между старым и новым списком.
- Обновляет только те элементы, которые реально изменились.
- Повышает производительность и визуальную плавность.
Незаменим при работе с динамическими данными в списках.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Чтобы начать рисовать пользовательский интерфейс (UI) на экране в Android-проекте, необходимо выполнить несколько шагов, которые включают настройку проекта, создание макета и взаимодействие с основными компонентами Android.
Первый шаг — создание Android-проекта в Android Studio. Это можно сделать, выбрав шаблон «Empty Activity», который предоставляет минимальный набор для разработки приложения.
Укажите имя проекта.
Выберите язык программирования (Java или Kotlin).
Убедитесь, что минимальная версия SDK подходит для вашей целевой аудитории.
Android UI в основном создаётся с использованием XML-файлов, которые определяют структуру интерфейса.
По умолчанию файл макета находится в каталоге:
res/layout/activity_main.xml
#### Пример простого макета:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Привет, мир!"
android:textSize="24sp"
android:padding="16dp"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Нажми меня"/>
</LinearLayout>
Макет нужно связать с логикой приложения в Java или Kotlin. Это делается с помощью метода
setContentView() в Activity.class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Указываем, какой XML-файл использовать для интерфейса
setContentView(R.layout.activity_main)
// Найдём элементы интерфейса и добавим логику
val textView = findViewById<TextView>(R.id.textView)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
textView.text = "Кнопка нажата!"
}
}
}Если нужно нарисовать что-то вручную (например, графику, линии, или кастомные фигуры), можно создать свой собственный класс, унаследованный от
View, и переопределить метод onDraw().class CustomView(context: Context) : View(context) {
private val paint = Paint().apply {
color = Color.RED
strokeWidth = 10f
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// Рисуем линию
canvas?.drawLine(100f, 100f, 400f, 400f, paint)
}
}Чтобы использовать этот класс, можно добавить его в макет или программно
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Устанавливаем кастомный View вместо макета
setContentView(CustomView(this))
}
}Теперь можно запустить приложение на эмуляторе или реальном устройстве:
Нажмите Run в Android Studio.
Убедитесь, что устройство подключено или выбран эмулятор.
После запуска вы увидите созданный интерфейс.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Nothing — это специальный bottom type (нижний тип), который означает: Функция никогда не возвращает результат (
throw, error()). Код после
Nothing недостижим. fun fail(): Nothing {
throw IllegalStateException("Ошибка!") // Никогда не возвращает значение
}Все классы в Kotlin могут иметь инстансы (объекты), кроме
Nothing. Nothing нельзя создать (instantiate), потому что он не имеет конструктора. Любая переменная типа
Nothing просто не существует. val x: Nothing = Nothing() // ❌ Ошибка: у Nothing нет конструктора
Используется в
throwfun fail(): Nothing = throw IllegalArgumentException("Ошибка")Используется в
TODO()fun getData(): String {
TODO("Функция ещё не реализована")
}Используется в
if-else, где один вариант throwfun getValue(x: Int): String {
return if (x > 0) "Позитивное число" else throw IllegalArgumentException("Отрицательное!")
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2