Forwarded from easyoffer
Через час мы закроем краудфандинг easyoffer 2.0
Это последний шанс вписаться в самые выгодные условия.
👉 https://planeta.ru/campaigns/easyoffer
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2
Forwarded from Идущий к IT
Я смотрю на эту цифру и до сих пор не верю.
Когда я запускал этот проект, мне реально было страшно. Страшно, что ничего не получится. Что я и мой проект никому не нужен. Страшно, что все увидят, как я публично обосрался.
Я ставил планку в 300т рублей. В самом позитивном сценарии 1млн. Но про 5 миллионов… даже мысли не было. Уже в первые часы стало понятно, что кампания идет не по плану. Сайт краудфандинга не выдержал нашей нагрузки и лег 😁
Особенно в последние три дня — просто какой-то разрыв! Я ощущал, как будто ловлю попутный ветер. В последний час не хватало 50к до 5 млн, и я уже думал сам их докинуть, чтобы красиво закрыть 😁
Но финальная сумма это не так важно. Самое главное это как мы её собрали. Это не инвестиции, не чьи-то деньги под условия и контроль, не кредит. Это вы поверили и поддержали меня напрямую. Вы дали мне возможность оставить за собой полный контроль над easyoffer.
Я чувствую огромную ответственность и нервничаю из-за высоких ожиданий. А вдруг что-то пойдёт не так? А вдруг на релизе кому-то что-то не понравится? Именно поэтому я рад, что могу честно выйти на новый этап и без давления от левых инвесторов.
В такие моменты вспоминаю, с чего всё начиналось. Как 2 года назад я писал свои первые посты на 500 человек о том, как учу программирование. Как записывал первое видео на YouTube про поиск работы. Как пилил первую версию easyoffer, вообще без понимания, что из этого выйдет.
И сейчас я думаю — может, эта история вдохновит кого-то из вас. Может, кто-то запустит свой айтишный проект, найдёт поддержку и соберёт бабки на развитие. Было бы круто
Спасибо за невероятную и колосальную поддержку ❤️
О такой аудитории как вы я не мог мечтать
Когда я запускал этот проект, мне реально было страшно. Страшно, что ничего не получится. Что я и мой проект никому не нужен. Страшно, что все увидят, как я публично обосрался.
Я ставил планку в 300т рублей. В самом позитивном сценарии 1млн. Но про 5 миллионов… даже мысли не было. Уже в первые часы стало понятно, что кампания идет не по плану. Сайт краудфандинга не выдержал нашей нагрузки и лег 😁
Особенно в последние три дня — просто какой-то разрыв! Я ощущал, как будто ловлю попутный ветер. В последний час не хватало 50к до 5 млн, и я уже думал сам их докинуть, чтобы красиво закрыть 😁
Но финальная сумма это не так важно. Самое главное это как мы её собрали. Это не инвестиции, не чьи-то деньги под условия и контроль, не кредит. Это вы поверили и поддержали меня напрямую. Вы дали мне возможность оставить за собой полный контроль над easyoffer.
Я чувствую огромную ответственность и нервничаю из-за высоких ожиданий. А вдруг что-то пойдёт не так? А вдруг на релизе кому-то что-то не понравится? Именно поэтому я рад, что могу честно выйти на новый этап и без давления от левых инвесторов.
В такие моменты вспоминаю, с чего всё начиналось. Как 2 года назад я писал свои первые посты на 500 человек о том, как учу программирование. Как записывал первое видео на YouTube про поиск работы. Как пилил первую версию easyoffer, вообще без понимания, что из этого выйдет.
И сейчас я думаю — может, эта история вдохновит кого-то из вас. Может, кто-то запустит свой айтишный проект, найдёт поддержку и соберёт бабки на развитие. Было бы круто
Спасибо за невероятную и колосальную поддержку ❤️
О такой аудитории как вы я не мог мечтать
💊6
RxJava может вызывать утечки памяти, если подписки (
Disposable) не очищаются правильно. Утечки памяти происходят, когда RxJava-события продолжают выполняться после уничтожения
Activity/Fragment, удерживая ссылки на View или Context. CompositeDisposable собирает все подписки и очищает их при onDestroy(). class MainActivity : AppCompatActivity() {
private val disposables = CompositeDisposable() // Собираем подписки
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
disposables.add(disposable) // Добавляем подписку
}
override fun onDestroy() {
super.onDestroy()
disposables.clear() // ✅ Очищаем подписки, предотвращая утечку памяти
}
}Если используем Jetpack ViewModel или
LifecycleOwner, можно автоматически отвязывать подписки. class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Observable.interval(1, TimeUnit.SECONDS)
.autoDispose(viewLifecycleOwner) // ✅ Автоочистка при `onDestroyView()`
.subscribe { println("Tick: $it") }
}
}Если подписка не в
CompositeDisposable, её можно очистить вручную. private var disposable: Disposable? = null
fun startObserving() {
disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
}
fun stopObserving() {
disposable?.dispose() // ✅ Очищаем подписку
}
RxJava может удерживать
Activity/Fragment, вызывая утечки памяти. Лучше использовать
ViewModel + LiveData! class MyViewModel : ViewModel() {
private val _timer = MutableLiveData<Long>()
val timer: LiveData<Long> = _timer
init {
Observable.interval(1, TimeUnit.SECONDS)
.subscribe { _timer.postValue(it) }
}
}Подписка в
Activity (без утечек!) viewModel.timer.observe(this) { println("Tick: $it") }Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Desugaring — это процесс, при котором синтаксические "сладости" языка (например, лямбды, методы по умолчанию в интерфейсах) преобразуются в более простой код, понятный старым версиям Java или Android. Это позволяет использовать современные возможности языка даже на устройствах с устаревшей средой выполнения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Kotlin все классы неявно наследуются от
Any (аналог Object в Java). Это значит, что любой класс в Kotlin имеет 3 базовых метода:
По умолчанию сравнивает ссылки (как
===) class Person(val name: String)
fun main() {
val p1 = Person("Alice")
val p2 = Person("Alice")
println(p1 == p2) // false (разные объекты)
}
Как переопределить
equals() для сравнения по значениям? class Person(val name: String) {
override fun equals(other: Any?): Boolean {
return other is Person && this.name == other.name
}
}
fun main() {
val p1 = Person("Alice")
val p2 = Person("Alice")
println(p1 == p2) // true (теперь сравниваются значения)
}По умолчанию генерируется на основе ссылки
val p = Person("Alice")
println(p.hashCode()) // Разный для каждого объектаКак переопределить
hashCode()? class Person(val name: String) {
override fun hashCode(): Int {
return name.hashCode() // Генерируем хеш-код на основе имени
}
}По умолчанию печатает
ClassName@hashCodeval p = Person("Alice")
println(p.toString()) // Person@4e25154f (неудобный вывод)Как сделать красивый вывод?
class Person(val name: String) {
override fun toString(): String {
return "Person(name=$name)"
}
}
val p = Person("Alice")
println(p.toString()) // Person(name=Alice)data class User(val name: String)
fun main() {
val u1 = User("Alice")
val u2 = User("Alice")
println(u1 == u2) // ✅ true (по значениям)
println(u1.hashCode()) // ✅ Одинаковый для объектов с одинаковыми данными
println(u1.toString()) // ✅ User(name=Alice)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🤔1
1. Генерация данных.
2. Обработка данных через операторы.
3. Управление подпиской и отпиской.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2
В
data class могут возникнуть проблемы со списками при использовании: Методов
copy() – список не копируется, а передаётся по ссылке. Методов
equals() и hashCode() – List сравнивается по элементам, что может быть медленно. Mutable списков (
MutableList) – изменения внутри списка изменяют все копии объекта. data class User(val name: String, val tags: List<String>)
fun main() {
val original = User("Alice", listOf("Admin", "Editor"))
val copy = original.copy() // ✅ Поверхностное копирование
println(original.tags === copy.tags) // true (один и тот же объект)
}
Используем
toList(), чтобы создать новый неизменяемый список: fun main() {
val original = User("Alice", listOf("Admin", "Editor"))
val copy = original.copy(tags = original.tags.toList())
println(original.tags === copy.tags) // false (разные объекты)
}data class Message(val id: Int, val content: String, val tags: List<String>)
fun main() {
val message1 = Message(1, "Привет", listOf("important", "urgent"))
val message2 = Message(1, "Привет", listOf("important", "urgent"))
println(message1 == message2) // true (сравнивает элементы списка)
}
Если идентификатор (
id) уникален, сравнивать только его data class Message(val id: Int, val content: String, val tags: List<String>) {
override fun equals(other: Any?) = other is Message && this.id == other.id
override fun hashCode() = id.hashCode()
}data class Task(val name: String, val subtasks: MutableList<String>)
fun main() {
val original = Task("Купить продукты", mutableListOf("Хлеб", "Молоко"))
val copy = original.copy() // ✅ Поверхностная копия
copy.subtasks.add("Яйца") // ❌ Меняет список в обоих объектах!
println(original.subtasks) // [Хлеб, Молоко, Яйца]
println(copy.subtasks) // [Хлеб, Молоко, Яйца]
}
Создаём новый
MutableList внутри copy() data class Task(val name: String, val subtasks: List<String>) {
fun deepCopy() = Task(name, subtasks.toMutableList())
}
fun main() {
val original = Task("Купить продукты", mutableListOf("Хлеб", "Молоко"))
val copy = original.deepCopy()
copy.subtasks.toMutableList().add("Яйца") // ✅ Теперь изменения не влияют на оригинал
println(original.subtasks) // [Хлеб, Молоко]
println(copy.subtasks) // [Хлеб, Молоко, Яйца]
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6💊1
- Переходит в паузу, затем в onStop().
- Остаётся в стеке задач.
- Может быть уничтожена системой при нехватке памяти.
Если используешь finish() — она будет удалена немедленно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
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
👍3
Реализация может быть следующей:
- Загрузка на старте экрана: при запуске фрагмента/экрана уже начинать сетевой запрос.
- Фоновая загрузка при idle-состоянии: использовать WorkManager или CoroutineScope для подгрузки ночью, в фоне и т.п.
- RecyclerView prefetch: через RecyclerView.setItemViewCacheSize() и PrefetchingLayoutManager заранее подгружаются карточки.
- Кеширование: сохранить данные в БД или памяти при первом получении, и отображать в UI до завершения запроса.
Цель — снизить видимое пользователю ожидание.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Java и Kotlin существуют различные типы данных, которые можно разделить на две основные категории:
Примитивные типы данных (primitive types)
Ссылочные типы данных (reference types)
Ссылочные типы данных указывают на объекты. Они хранятся в куче (heap) и содержат ссылку (адрес) на объект.
Примеры ссылочных типов
String str = "Hello";MyClass obj = new MyClass();int[] arr = {1, 2, 3};List<Integer> list = new ArrayList<>();В Kotlin типы данных делятся на nullable и non-nullable (значения, которые могут быть
null, и те, которые не могут). Kotlin избегает примитивных типов напрямую, но на уровне JVM использует их для оптимизации. Kotlin предоставляет высокоуровневые обёртки для примитивных типов. Например:
Int вместо intDouble вместо doubleВ Kotlin все данные — объекты.
Сюда входят:
Строки (
String): val str: String = "Hello"Коллекции:
val list: List<Int> = listOf(1, 2, 3)Классы:
val obj = MyClass()Nullable-типизация: В Kotlin можно указать, что переменная может быть
null. Для этого используется ?. val nullableString: String? = null
val nonNullableString: String = "Hello"
Интерфейсы и абстракции: Kotlin поддерживает классы, интерфейсы и их реализации, например
interface Animal {
fun makeSound()
}
class Dog : Animal {
override fun makeSound() {
println("Woof!")
}
}
Kotlin добавляет уникальные типы, которые отсутствуют в Java:
Unit
Эквивалент
void в Java, но является объектом.Используется, когда функция ничего не возвращает:
fun printMessage(message: String): Unit {
println(message)
}
NothingПредставляет значение, которое никогда не будет существовать (например, функция всегда бросает исключение):
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
associateBy создаёт Map, где ключи берутся из заданной логики, а значениями становятся элементы коллекции. associateWith наоборот — элемент становится ключом, а значение задаётся отдельно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2
Когда используется deeplink, может возникнуть проблема, что одно и то же Activity может быть открыто несколько раз в стеке задач. Это происходит, если приложение запускается из внешнего источника (например, из браузера или другого приложения), и Android создает новую задачу или новую копию Activity вместо использования уже существующей. Чтобы справиться с этим, нужно правильно настроить
launchMode, интенты и флаги.При запуске приложения через deep link, система Android может:
1. Создать новый экземпляр вашего Activity (даже если оно уже существует в стеке задач).
2. Поместить новую задачу в стек задач.
Если это не контролировать, пользователь может увидеть много дубликатов одного и того же Activity, что плохо для UX и может вызвать утечку памяти.
В файле
AndroidManifest.xml можно настроить поведение Activity с помощью атрибута launchMode:singleTop: Если Activity уже находится на вершине стека, система не будет создавать новый экземпляр.singleTask: Убедитесь, что только один экземпляр Activity существует в задаче. Если Activity уже существует, система передаст интент в метод onNewIntent().singleInstance: Подходит для случаев, когда Activity должно быть абсолютно уникальным (используется редко).<activity
android:name=".MyActivity"
android:launchMode="singleTop" />
Если вы используете deep link или запускаете Activity вручную, можно добавить флаги, чтобы управлять созданием экземпляров:
val intent = Intent(this, MyActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(intent)
Если используется
singleTop или singleTask, система вызывает метод onNewIntent(Intent intent) вместо создания нового экземпляра Activity. Этот метод можно переопределить для обработки новых данных из интента override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка нового интента
val data = intent?.data
// Используйте данные deeplink
}
Если требуется, чтобы разные deep link открывали одну и ту же задачу, можно настроить
taskAffinity. Это нужно реже, но полезно, если нужно обрабатывать ссылки с разными контекстами.Дополнительно можно вручную проверять, существует ли нужное Activity в текущем состоянии приложения, например, используя LiveData или ViewModel.
Файл
AndroidManifest.xml<activity
android:name=".MyActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:path="/deeplink" />
</intent-filter>
</activity>
Файл
MyActivity.ktclass MyActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка данных из нового интента (deeplink)
val uri = intent?.data
uri?.let {
// Например, получить параметры из ссылки
val param = it.getQueryParameter("id")
Log.d("Deeplink", "Parameter id: $param")
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
- data class — для хранения и работы с данными, удобны при копировании, логическом сравнении, сериализации.
- sealed class — для ограниченного набора подтипов. Удобны при использовании when, так как все случаи должны быть обработаны — это повышает безопасность и читаемость.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Kotlin есть множество удобных функций для получения одного элемента или списка элементов из коллекций (
List, Set, Map). Какую функцию использовать — зависит от задачи. получение по индексу
Если индекс может выйти за границы массива, лучше использовать
getOrNull(). val list = listOf("A", "B", "C")
println(list[1]) // "B"
println(list.getOrNull(5)) // nullпервый и последний элементы
println(list.first()) // "A"
println(list.last()) // "C"
Используйте
firstOrNull() или lastOrNull(), если список может быть пустымprintln(emptyList<String>().firstOrNull()) // null
поиск по условию Возвращает первый или последний элемент, удовлетворяющий условию.
val numbers = listOf(10, 20, 30, 40)
println(numbers.find { it > 15 }) // 20
println(numbers.findLast { it > 15 }) // 40
println(numbers.find { it > 50 }) // null
если ожидается только один элемент
val oneElementList = listOf(42)
println(oneElementList.single()) // 42
println(oneElementList.singleOrNull()) // 42
val emptyList = emptyList<Int>()
println(emptyList.singleOrNull()) // null
single() бросает исключение, если элементов нет или их больше одного. filter {} – фильтрация элементов по условиюval numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // [2, 4]
take(n) и takeLast(n) – первые/последние n элементов val items = listOf("a", "b", "c", "d", "e")
println(items.take(3)) // [a, b, c]
println(items.takeLast(2)) // [d, e]slice(indices) – получение элементов по индексам val sliced = items.slice(listOf(0, 2, 4))
println(sliced) // [a, c, e]
map {} – преобразование списка val squared = numbers.map { it * it }
println(squared) // [1, 4, 9, 16, 25]Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4💊1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Нет, после
by нельзя вызывать функции или конструкторы. by ожидает готовый объект, который реализует интерфейс или делегирует свойство. В Kotlin
by используется в двух случаях: Делегирование интерфейсов (
class A : Interface by obj) Делегирование свойств (
val x by lazy {}) Но
by не может вызывать функции или конструкторы – ему нужен уже созданный объект. Правильный код (делегируем готовый объект)
interface Printer {
fun printMessage()
}
class RealPrinter : Printer {
override fun printMessage() = println("Печать...")
}
// ✅ Делегируем готовый объект `RealPrinter()`
class MyClass : Printer by RealPrinter()
fun main() {
MyClass().printMessage() // Печать...
}Ошибка, если после
by вызвать конструктор class MyClass : Printer by RealPrinter() // ✅ Работает
class MyClass2 : Printer by RealPrinter() { } // ❌ Ошибка!
Правильный код (
by lazy) val message: String by lazy { "Привет, мир!" }
println(message) // Привет, мир!Ошибка, если после
by вызвать функцию val message: String by getMessage() // ❌ Ошибка!
fun getMessage() = "Привет, мир!"
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊8👍2
- Через .equals() — логическое сравнение содержимого.
- При переопределении .equals() рекомендуется также переопределить .hashCode().
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1💊1
В Java все классы неявно наследуются от класса
Object, если явно не указано другое наследование. class MyClass {
// Неявно наследуется от Object
}
class MyClass2 extends Object {
// То же самое, просто указано явно
}Класс
Object содержит основные методы, доступные во всех классах: class Person {
String name;
Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("Alice");
System.out.println(p.toString()); // Person{name='Alice'}
System.out.println(p.hashCode()); // Хеш-код объекта
System.out.println(p.getClass()); // class Person
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
- Сделать приватный конструктор, чтобы запретить внешнее создание объекта.
- Используется в паттерне Singleton или Utility-классах (например, Math).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1💊1
В
interface можно определять свойства (val или var), но их реализация зависит от того, есть ли у них get и set. В интерфейсе можно объявить
val (только для чтения) или var (для чтения и записи), но без инициализации. interface User {
val name: String // Объявляем свойство, но не реализуем
var age: Int // Может изменяться, но без начального значения
}Использование в классе
class Person(override val name: String) : User {
override var age: Int = 25 // Обязательно реализовать
}Можно задать кастомный геттер прямо в интерфейсе.
interface User {
val name: String
val greeting: String
get() = "Привет, $name!" // Реализация внутри интерфейса
}Использование в классе
class Person(override val name: String) : User
fun main() {
val user = Person("Андрей")
println(user.greeting) // Привет, Андрей!
}
Если в интерфейсе объявить
var, то нельзя задать реализацию геттера и сеттера — их должен реализовать класс. interface User {
var age: Int // Только объявление, без реализации
}Использование в классе
class Person : User {
override var age: Int = 30 // Реализуем свойство
}В
interface нельзя использовать field (бэкинг-поле), потому что у интерфейсов нет состояния. interface User {
var age: Int
get() = field // Ошибка! Нельзя использовать `field` в интерфейсе
set(value) { field = value }
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5