Да, есть три основных случая:
1. Блокирующий код – если внутри корутины используется блокирующая операция (Thread.sleep(), while(true) {}), она не реагирует на отмену.
2. Отмена родительской корутины не отменяет launch(NonCancellable) – если корутина запущена с NonCancellable, она игнорирует отмену.
3. Отмена не срабатывает, если корутина не проверяет isActive или yield() – долгие вычисления без точек приостановки не дадут корутине завершиться.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Если нужно собирать данные из нескольких RSS-источников, можно реализовать это так:
Загрузить данные из нескольких RSS-лент (через
Retrofit, Jsoup, XmlPullParser). Парсить XML и извлекать статьи.
Объединять данные в общий список (
List<Article>). Сортировать по дате.
Сохранять в БД (Room) для офлайн-доступа.
Показывать в
RecyclerView или LazyColumn (Compose). В
build.gradle.ktsdependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0") // HTTP-клиент
implementation("com.squareup.retrofit2:converter-simplexml:2.9.0") // XML-парсер
implementation("org.jsoup:jsoup:1.16.1") // Альтернативный XML-парсер
implementation("androidx.room:room-runtime:2.6.1") // Room для кеша
kapt("androidx.room:room-compiler:2.6.1")
}RSS-лента содержит заголовок, ссылку, дату и описание
@Root(name = "item", strict = false)
data class RssItem(
@field:Element(name = "title") var title: String = "",
@field:Element(name = "link") var link: String = "",
@field:Element(name = "pubDate", required = false) var pubDate: String = "",
@field:Element(name = "description", required = false) var description: String = ""
)
Создаём API для загрузки RSS (Retrofit + XML)
interface RssService {
@GET("rss.xml")
fun getFeed(): Call<RssFeed>
}
@Root(name = "rss", strict = false)
data class RssFeed(
@field:Element(name = "channel") var channel: RssChannel = RssChannel()
)
@Root(name = "channel", strict = false)
data class RssChannel(
@field:ElementList(entry = "item", inline = true) var items: List<RssItem> = listOf()
)Создадим
Repository, который загружает и объединяет данные: class RssRepository(private val services: List<RssService>) {
fun fetchAllFeeds(): List<RssItem> {
val allItems = mutableListOf<RssItem>()
services.forEach { service ->
val response = service.getFeed().execute()
if (response.isSuccessful) {
response.body()?.channel?.items?.let { allItems.addAll(it) }
}
}
return allItems.sortedByDescending { parseDate(it.pubDate) }
}
private fun parseDate(dateString: String): Long {
return SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
.parse(dateString)?.time ?: 0
}
}Создаём Entity для БД
@Entity
data class ArticleEntity(
@PrimaryKey val link: String,
val title: String,
val pubDate: Long,
val description: String
)
Создаём DAO
@Dao
interface RssDao {
@Query("SELECT * FROM ArticleEntity ORDER BY pubDate DESC")
fun getArticles(): LiveData<List<ArticleEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertArticles(articles: List<ArticleEntity>)
}
Сохраняем в БД после загрузки
class RssRepository(private val dao: RssDao, private val services: List<RssService>) {
fun fetchAndCacheFeeds() {
val articles = fetchAllFeeds().map {
ArticleEntity(it.link, it.title, parseDate(it.pubDate), it.description)
}
dao.insertArticles(articles)
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Forwarded from easyoffer
🎉 Краудфандинг easyoffer 2.0 стартовал!
Друзья, с этого момента вы можете поддержать проект и получить существенный бонус:
🚀 PRO-тариф на 1 год, по цене месячной подписки на релизе.
➕ Доступ к закрытому бета-тесту easyoffer 2.0 (середина–конец мая)
Поддержать проект можно здесь:
https://planeta.ru/campaigns/easyoffer
📌 Если не получается оплатить через карту РФ — напишите мне @kivaiko, и мы найдём удобный способ
Друзья, с этого момента вы можете поддержать проект и получить существенный бонус:
🚀 PRO-тариф на 1 год, по цене месячной подписки на релизе.
➕ Доступ к закрытому бета-тесту easyoffer 2.0 (середина–конец мая)
Поддержать проект можно здесь:
https://planeta.ru/campaigns/easyoffer
📌 Если не получается оплатить через карту РФ — напишите мне @kivaiko, и мы найдём удобный способ
Forwarded from easyoffer
Я поставил целью сбора скромные 300 тыс. рублей, но ребята, вы накидали больше млн. всего за 1 день. Это просто невероятно!
Благодаря вашей поддержке, я смогу привлечь еще больше людей для разработки сайта и обработки собеседований. Ваш вклад сделает проект качественнее и ускорит его выход! Огромное вам спасибо!
Краудфандинг будет продолжаться еще 31 день и все кто поддержать проект сейчас, до его выхода, смогут получить:
🚀 PRO-тариф на 1 год, по цене месячной подписки на релизе.
➕ Доступ к закрытому бета-тесту easyoffer 2.0 (середина–конец мая)
Поддержать проект можно здесь:
https://planeta.ru/campaigns/easyoffer
Огромное спасибо за вашу поддержку! 🤝
Благодаря вашей поддержке, я смогу привлечь еще больше людей для разработки сайта и обработки собеседований. Ваш вклад сделает проект качественнее и ускорит его выход! Огромное вам спасибо!
Краудфандинг будет продолжаться еще 31 день и все кто поддержать проект сейчас, до его выхода, смогут получить:
🚀 PRO-тариф на 1 год, по цене месячной подписки на релизе.
➕ Доступ к закрытому бета-тесту easyoffer 2.0 (середина–конец мая)
Поддержать проект можно здесь:
https://planeta.ru/campaigns/easyoffer
Огромное спасибо за вашу поддержку! 🤝
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Код для работы с SQLite в Android не генерируется автоматически — разработчик сам пишет SQL-запросы и управляет БД.
Но если используется Room (ORM для SQLite), то код генерируется на этапе компиляции с помощью Annotation Processing (
kapt) или KSP. Если используешь
SQLiteOpenHelper, то код не генерируется, ты сам пишешь SQL-запросы: class MyDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "mydb", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS users")
onCreate(db)
}
}Если используешь Room, код автоматически генерируется во время компиляции с помощью Annotation Processing (
kapt) или KSP. Ты пишешь аннотации (
@Entity, @Dao, @Database). Room-аннотации анализируются при компиляции (
kapt / KSP). Генерируется SQL-код и DAO-методы.
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM User")
fun getAllUsers(): List<User>
@Insert
fun insert(user: User)
}
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Если используешь
kapt (Annotation Processor) или KSP, то код генерируется во время компиляции. dependencies {
implementation("androidx.room:room-runtime:2.6.1")
kapt("androidx.room:room-compiler:2.6.1") // Кодогенерация
}Пример с
KSP (быстрее kapt) dependencies {
implementation("androidx.room:room-runtime:2.6.1")
ksp("androidx.room:room-compiler:2.6.1") // Кодогенерация
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Это внутренний компонент Compose, отвечающий за управление состоянием и рендерингом. Через него Compose отслеживает изменения в UI и обновляет только те элементы, которые изменились. Он также управляет процессом "recomposition".
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Класс-наследник в Kotlin получает доступ к функционалу родительского класса через:
Наследование (
:) — доступ ко всем open методам и свойствам. Ключевое слово
super — вызов методов родителя. Переопределение (
override) — изменение поведения. Класс-наследник получает доступ ко всем
open методам и свойствам родителя. open class Animal(val name: String) {
fun eat() {
println("$name ест")
}
}
class Dog(name: String) : Animal(name)
fun main() {
val dog = Dog("Шарик")
dog.eat() // Шарик ест (метод унаследован)
}Пример
open class Animal {
open fun makeSound() {
println("Животное издаёт звук")
}
}
class Dog : Animal() {
override fun makeSound() {
super.makeSound() // Вызываем метод родителя
println("Гав-гав!")
}
}
fun main() {
val dog = Dog()
dog.makeSound()
}Вывод
Животное издаёт звук
Гав-гав!
Класс-наследник может изменить поведение родительского метода.
open class Animal {
open fun move() {
println("Животное двигается")
}
}
class Bird : Animal() {
override fun move() {
println("Птица летит") // Переопределяем метод
}
}
fun main() {
val bird = Bird()
bird.move() // Птица летит
}Пример
open class Animal(val name: String)
class Dog(name: String, val breed: String) : Animal(name) {
fun info() {
println("Имя: $name, Порода: $breed") // Используем `name` из родителя
}
}
fun main() {
val dog = Dog("Бобик", "Лабрадор")
dog.info() // Имя: Бобик, Порода: Лабрадор
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2😁1
В Java есть 4 модификатора доступа, которые контролируют видимость полей, методов и классов:
Поля почти всегда должны быть
private, чтобы инкапсулировать данные. Методы тоже лучше делать private, если они не должны использоваться извне. public class User {
private String name; // ✅ Доступен только внутри класса
public User(String name) {
this.name = name;
}
private void logUserAction() { // ✅ Только внутри класса
System.out.println(name + " совершил действие");
}
}Используется, если класс не должен быть доступен за пределами пакета. Полезно для вспомогательных классов.
class FileHelper { // ✅ Доступен только в этом пакете
static void readFile(String path) {
System.out.println("Читаем файл: " + path);
}
}protected лучше использовать только в abstract классах и для методов, которые должны быть переопределены. Не используй protected для полей → нарушает инкапсуляцию! abstract class Animal {
protected void makeSound() { // ✅ Наследники могут переопределять
System.out.println("Какой-то звук");
}
}
class Dog extends Animal {
@Override
protected void makeSound() {
System.out.println("Гав-гав!");
}
}Класс можно делать
public, только если он должен быть доступен во всех пакетах. Не делай поля public → нарушает инкапсуляцию! public class UserManager { // ✅ Доступен везде
public void createUser() { // ✅ Можно вызывать в любом месте
System.out.println("Создание пользователя...");
}
}final class → класс нельзя наследовать. final method → метод нельзя переопределить. final field → переменная неизменяема (константа). public final class MathUtils {
public static final double PI = 3.14159;
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Это аннотация в Compose, которая указывает, что объект стабилен и его свойства не изменяются спонтанно. Это помогает Compose эффективно определять, когда нужно перерисовывать UI, уменьшая количество лишних перерисовок.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Если удалился элемент из списка, нужно:
Удалить его из списка данных.
Сообщить
Adapter, чтобы он перерисовал только изменённые элементы. Этот метод обновляет только удалённый элемент, сохраняя анимации.
fun removeItem(position: Int) {
itemList.removeAt(position) // Удаляем из списка данных
notifyItemRemoved(position) // Сообщаем адаптеру
notifyItemRangeChanged(position, itemList.size) // Обновляем индексы
}Если изменился несколько элементов, лучше использовать
DiffUtil, чтобы обновить только нужные элементы. class MyAdapter : ListAdapter<Item, MyViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : 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
}
}
}
}Обновляем список через
submitList() fun removeItem(item: Item) {
val newList = currentList.toMutableList()
newList.remove(item)
submitList(newList) // `DiffUtil` сам вычислит, что обновить
}Использовать только если изменился весь список!
fun removeItem(position: Int) {
itemList.removeAt(position)
notifyDataSetChanged() // Перерисовывает весь список (медленно)
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Это асинхронный поток данных, который работает как List, но лениво.
Особенности:
- Выдает значения последовательно – одно за другим.
- Не блокирует поток – работает с suspend-функциями.
- Поддерживает cancel() – может быть остановлен в любой момент.
- Позволяет работать с бесконечными потоками – полезно для сетевых запросов, БД, UI-событий.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤1
В Android есть два основных типа потоков:
1. UI Thread (главный поток) → отвечает за интерфейс и обработку событий.
2. Worker Thread (фоновый поток) → используется для долгих операций (запросы в сеть, работа с БД).
- Отвечает за отрисовку интерфейса (Activity, Fragment, View).
- Обрабатывает нажатия, свайпы, анимации.
- Все
setText(), setImageResource() и другие UI-методы работают только здесь. Если делать тяжёлые операции в UI Thread, приложение зависнет (ANR - Application Not Responding)! val url = URL("https://example.com")
val connection = url.openConnection() as HttpURLConnection // ❌ Ошибка!- Выполняет долгие операции, не блокируя UI:
Запросы в сеть (Retrofit, OkHttp)
Чтение/запись в БД (Room, SQLite)
Обработку файлов, JSON, XML
Любые тяжёлые вычисления
CoroutineScope(Dispatchers.IO).launch {
val url = URL("https://example.com")
val connection = url.openConnection() as HttpURLConnection // ✅ Работает!
}Способ 1:
Coroutines (лучший вариант)CoroutineScope(Dispatchers.IO).launch {
val data = fetchData() // Работает в `Worker Thread`
withContext(Dispatchers.Main) {
textView.text = data // Назад в `UI Thread`
}
}Способ 2:
Handler + Thread (старый вариант) val handler = Handler(Looper.getMainLooper())
Thread {
val data = fetchData() // Работает в `Worker Thread`
handler.post {
textView.text = data // Назад в `UI Thread`
}
}.start()
Способ 3:
AsyncTask (УСТАРЕЛ, НЕ ИСПОЛЬЗОВАТЬ!) class MyTask : AsyncTask<Void, Void, String>() {
override fun doInBackground(vararg params: Void?): String {
return fetchData() // `Worker Thread`
}
override fun onPostExecute(result: String) {
textView.text = result // `UI Thread`
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁15👍5💊1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2🤯2👀1
Чтобы чат работал быстро и без лагов, нужно:
Оптимизировать
RecyclerView (ViewHolder, DiffUtil, Payloads). Минимизировать работу в
UI Thread (использовать Coroutines, Paging 3). Оптимизировать загрузку изображений (Glide, Coil).
Использовать Room + Flow / LiveData для офлайн-кеша.
Вместо
notifyDataSetChanged() лучше использовать ListAdapter, который сам обновляет только изменённые сообщения. class ChatAdapter : ListAdapter<Message, ChatViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Message>() {
override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem == newItem // data class сравнит автоматически
}
}
}Если, например, только статус сообщения изменился, не нужно перерисовывать всё сообщение!
override fun getChangePayload(oldItem: Message, newItem: Message): Any? {
return if (oldItem.status != newItem.status) "STATUS_CHANGED" else null
}Пример в
onBindViewHolder() override fun onBindViewHolder(holder: ChatViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains("STATUS_CHANGED")) {
holder.updateStatus() // Обновляем только статус
} else {
super.onBindViewHolder(holder, position, payloads)
}
}Запросы в БД в
Worker Thread (Coroutines + Room) fun loadMessages(): Flow<List<Message>> = messageDao.getMessages()
Используем
Paging 3 для подгрузки сообщений val pager = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { messageDao.getPagedMessages() }
).flow.cachedIn(viewModelScope)
Если чат содержит аватары или изображения, они должны загружаться лениво и в фоновом потоке.
Glide.with(context)
.load(user.avatarUrl)
.circleCrop()
.placeholder(R.drawable.placeholder)
.into(imageView)
Чтобы сообщения не загружались из сети при каждом запуске, сохраняем их в Room.
@Dao
interface MessageDao {
@Query("SELECT * FROM messages ORDER BY timestamp DESC")
fun getMessages(): Flow<List<Message>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMessages(messages: List<Message>)
}
Используем
setHasFixedSize(true), если сообщения не меняют размер recyclerView.setHasFixedSize(true)
Используем
setItemViewCacheSize(), чтобы кэшировать ViewHolder recyclerView.setItemViewCacheSize(20)
Отключаем анимации
RecyclerView (если лагает) (recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
По умолчанию нельзя, потому что дженерики стираются (
Type Erasure) во время компиляции. fun <T> printType(value: T) {
println(T::class.simpleName) // ❌ Ошибка! Нельзя использовать `T::class`
}Если используем
inline fun, можно сделать дженерик "реальным" (reified). inline fun <reified T> printType(value: T) {
println(T::class.simpleName) // ✅ Работает!
}
fun main() {
printType(123) // Int
printType("Hello") // String
printType(3.14) // Double
}Если дженерик используется в классе, то
reified не поможет. class MyGenericClass<T>(private val type: KClass<T>) {
fun printType() {
println(type.simpleName) // ✅ Работает
}
}
fun main() {
val obj = MyGenericClass(String::class)
obj.printType() // String
}Для сложных дженериков (
List<T>, Map<K, V>) используем typeOf<T>() (только с reified). import kotlin.reflect.typeOf
inline fun <reified T> printGenericType() {
val type = typeOf<T>()
println(type) // ✅ List<Int>, Map<String, Boolean> и т. д.
}
fun main() {
printGenericType<List<Int>>() // kotlin.collections.List<kotlin.Int>
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥1
Софткод (Softcode, Soft Coding) — это подход к программированию, при котором логика программы хранится в конфигурационных файлах, базе данных или других внешних источниках, а не жёстко (hardcoded) прописана в коде.
Пример Hardcode (жёстко зашито в коде)
val apiUrl = "https://api.example.com" // ❌ Если URL изменится, надо менять код
Пример Softcode (гибко через конфигурацию)
val apiUrl = Config.get("api_url") // ✅ Загружается из настроекКонфигурационные файлы (
config.json, .properties, .xml). База данных (логика, настройки, права пользователей).
API и сервер (получение UI-элементов, бизнес-логики с сервера).
Скриптовые языки (скрипты загружаются динамически).
Softcode через
SharedPreferences (конфигурация в памяти)val sharedPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val theme = sharedPrefs.getString("app_theme", "light") // ✅ Загружаем тему из настроекSoftcode через
remoteConfig (Firebase Remote Config)val minVersion = Firebase.remoteConfig.getInt("min_supported_version")Softcode через JSON-файл (читаем конфиг из assets)
fun getConfigValue(context: Context, key: String): String {
val json = context.assets.open("config.json").bufferedReader().use { it.readText() }
val jsonObject = JSONObject(json)
return jsonObject.getString(key) // ✅ Получаем значение из JSON
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4