Kotlin + Spring = боль. У меня есть пара проектов kotlin + spring и пара kotlin + ktor. Последние по скорости обработки запросов просто разрывают. И лично для меня приятнее во второй паре писать, а с первыми все на костылях как то сживается.
lifecycleScope еще живет некоторое время после onDestroyView. Корутина может попытаться вызвать изменение Ui которого уже нет и уронить приложение. Отменяйте джобы при покидании экрана. При возврате на экран по кнопке назад onViewCreated вызывается снова и можно подписаться на flow несколько раз. Не забываем отменять подписки.
VieModelScope работает в Main потоке. Не делайте запросы в сеть в мэйн потоке, а лучше вообще откажитесь от использования viewModelScope, а создайте на основе его контекста скоуп с диспетчером io или default. Новый и умрет при уничтожении вьюмодельки и постоянно диспетчер прокидывать не придется.
Еще есть поддержка страниц 16 кб для гугл плэя. Обновляться еще прикольнее. Когда нужно пересобрать нативные заброшенные под лет так 10 назад библиотеки.
Так и представляю мир где все миллиардеры. Где обычных людей нет, а основные потребители услуг твоего бизнеса должны быть такие же миллиардеры, которым твой бизнес не нужен. Потому что у них есть свой сгенерированный через ai.
Мне уже переводят видосы на английский. В настройках стоит английский. Переключаешь на русский и видео на русском. Переводит AI. На слух пока очень не очень.
Просто задержка перед повтором операции. Если есть желание повесить поток, то можно убрать.
В целом для холодного потока ограничение во флоу через delay нормальная практика.
Если мы посмотрим на метод debounce во флоу, то он позволит пропускать изменения, только если они не менялись в течении определенного времени. Не всегда же обновлять UI , так можно и привиснуть.
Так же в документации есть замечательные методы для соединения потоков данных (combine и zip), так же для переключения потоков данных flatMapLatest и другие. Возможности выделить из цепочки преобразований данные onEach.
При желании из холодного потока можно сделать горячий через stateIn. (Каждый подписчик на холодный поток вызывает всю цепочку преобразований) Но если перед передачей к подписчикам сделать его горячим, то холодный будет только один до горячего, а дальше расходиться к каждому подписчику.
В целом тема обширная и дает очень много возможностей.
Интересные сравнения. В году так 14 писали еще на 7 жабе. А на седьмой без лямд, без стримов естественно было больше кода. Лучше бы с Котлином сравнили
В обычном Андройде на котлине пишу юзкейсы обновления. В них данные обновляются в RoomDatabase. А из нее через corutine Flow данные летят к экрану. Из минусов, что больше кода нужно писать, и много маперов.
Если делать миграции в базе данных, то добавление полей не большая проблема, ведь можно и дефолтное задать.
Так как это база данных, то можно делать различные джоины и запрашивать, комбинировать, только то что нужно для э4рана.
Чтобы внести изменение в код нужно понять этот код.
А теперь представляем как разработчику дают новый проект. И он несколько дней может только разбираться что уже есть, что нужно сделать и как не сломать то что работает.
Новый человек не всегда может придерживаться архитектуры и сделать как то не так.
Если постоянно менять разработчиков, то код начинает вонять, а поддержка кода становится непосильной.
На своей первой работе был я наладчиком оборудования. У меня был свой кабинет с кучей приборов, и соответственно еще и рабочими столами, где я мог разобрать прибор, все разложить и ничего не потерять, на это требуется пространство. И в один прекрасный день начальник такой.
" Нам нужен стол, у тебя их много, поэтому заберём у тебя"
Естественно мне стало неудобно работать.
Прошло немного времени, в офис купили новый стол другому сотруднику, а старый захотели выбросить на свалку, с трудом отвоевал его себе. Смешно то, что выносить на свалку доверили мне, и он естественно поехал ко мне в подсобку, потому что он был мне нужен.
Потом прошло еще немного времени, и тут начальство опять пришло ко мне за столом и забрали тот стол, который я спасал от уничтожения, для нового сотрудника. И тут я сгорел.
Как же они паниковали, когда я просто не продлил контракт. На мое место им пришлось нанять двоих, и чтобы второй человек смог работать в моей подсобке, однозначно притащить туда стол.
Я наоборот не хочу логиниться ни пальцем ни лицом. Считаю эти функции крайне не безопасными и при утечке биометрии получить проблемы похуже одной взломанной учетки.
Я из тех кого около трех лет пинали hr. Никаких собеседований вообще. Причина, нет коммерческого опыта. И плевать они хотели на фрилансы, пет проекты и. др.
Но на работу таки устроился и именно с тестового задания. Вообще впринципе на собеседования попадал только с тестовых заданий. А взяли работать со второго собеса.
В общем. Если вам нужна работа, то вы и тестовое сделаете.
Сегодня и я задался такой задачкой. И как истинный джун не полез искать костыли, а сделал свой.
Сам сокет выглядит вот так:
package org.example
import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
abstract class WebSocketStream<I : Any, O>(
private val client: HttpClient,
private val workScope: CoroutineScope,
private val urlString: String,
val encoder: (O) -> String,
private val decoder: (String) -> I,
) {
companion object {
inline operator fun <reified I : Any, reified O> invoke(
client: HttpClient,
workScope: CoroutineScope,
urlString: String,
noinline encoder: (O) -> String = { defaultSerializer.encodeToString(it) },
noinline decoder: (String) -> I = { defaultSerializer.decodeFromString(it) }
): WebSocketStream<I, O> {
return object : WebSocketStream<I, O>(
client = client,
workScope = workScope,
urlString = urlString,
encoder = encoder,
decoder = decoder
){}
}
val defaultSerializer = Json {
ignoreUnknownKeys = true
allowSpecialFloatingPointValues = true
}
}
sealed interface Output<out O> {
data class Income<T : Any>(val data: T) : Output<T>
data class Something<T : Any>(val frame: Frame) : Output<T>
data class Error(val e: Throwable) : Output<Nothing>
}
sealed interface Status {
data object Connect : Status
data object Connected : Status
data object Disconnect : Status
}
private val _stream = MutableSharedFlow<Output<I>>()
val output = _stream.asSharedFlow()
private val input = Channel<String>()
private val _status = MutableStateFlow<Status>(Status.Disconnect)
val status = _status.asStateFlow()
private var job: Job? = null
fun connectWhile(
keepOpen: () -> Boolean
) {
job?.cancel()
job = workScope.launch {
while (keepOpen()) {
runCatching {
_status.value = Status.Connect
connectSuspend(keepOpen)
_status.value = Status.Disconnect
}.onFailure {
_stream.emit(Output.Error(it))
}
if (keepOpen()) {
delay(1000)
}
}
}
}
private suspend fun connectSuspend(keepOpen: () -> Boolean) {
client.webSocket(urlString) {
val connectJob = launch {
incoming.receiveAsFlow()
.onStart {
_status.value = Status.Connected
}.onEach { message ->
when (message) {
is Frame.Text -> {
val rad = message.readText()
try {
_stream.emit(Output.Income(decoder(rad)))
} catch (e: Exception) {
_stream.emit(Output.Error(e))
}
}
else -> {
_stream.emit(Output.Something(message))
}
}
}.launchIn(this)
input.consumeEach {
runCatching {
send(Frame.Text(it))
}
}
}
while (keepOpen()) delay(1000)
_status.value = Status.Disconnect
connectJob.cancel()
close(reason = CloseReason(CloseReason.Codes.NORMAL, "leave"))
}
}
private suspend fun send(data: String) {
return input.send(data)
}
fun send(
obj: O
) = workScope.launch { send(encoder(obj)) }
}
А пользоваться этим примерно так:
// Есть какой то класс оболочка на прием, а можно и без, всего то заменяем decoder
@Serializable
data class Data(
@SerialName("data")
val data: String
) : SerializedAny()
// Есть какой то класс оболочка на отправку, а можно и без, всего то заменяем encoder
@Serializable
data class Output(
@SerialName("data")
val data: String
)
private suspend fun open(client: HttpClient, text: String) {
// Создаем наш сокет
val stream = WebSocketStream<Data, Output>(
client = client,
workScope = CoroutineScope(Dispatchers.IO),
urlString = "ws://localhost:8078/ws"
)
// вешаем слушателей
CoroutineScope(Dispatchers.IO).launch {
stream.output
.onEach {
println("$text $it")
}.launchIn(this)
stream.status
.onEach {
println(it)
}.launchIn(this)
}
// флаг, что нужно продолжать работу
var keep = true
// сокет с некоторой переодчностью сам спрашивает, нужно ли ему работать дальше
// если связь оборвется, то будет пробовать переподключться
stream.connectWhile { keep }
// Тут мы отправляем данные, в ответ получаем Job. Можно ждать завершения посылки, а можно и не ждать
repeat(5) {
// job создается от workScope. Если workScope закрыть, то и производные закроются.
stream.send(Output(text)).join()
delay(1000)
}
// В конце говорим что продолжать не нужно и сокет сам отключится.
keep = false
}
Kotlin + Spring = боль. У меня есть пара проектов kotlin + spring и пара kotlin + ktor. Последние по скорости обработки запросов просто разрывают. И лично для меня приятнее во второй паре писать, а с первыми все на костылях как то сживается.
Судя по буферу, диск с черепичной записью. Не рекомендовал бы.
Предположу что каждый генерирует пару RSA ключей. А передается публичный ключ которым шифруют данные. Но им нельзя расшифровать.
У меня вон гуглы доходят до страницы ввода телефона.
Не вводят номер телефона который предоставили для тестирования.
Присылают скрин где номер не введен и они якобы не могут войти.
Расписали более подробно как номер вводить на английском языке. Не помогло.
Сделали специальную сборку где номер введется сам. А они его стерли. И снова прислали скрин.
Сделали чтобы поле было неактивно. Номер вводился за 500 миллисекунд. И они не ждали эти 500 миллисекунд, а сделали скрин что не могут войти.
Вот такая вот у них модерация)
lifecycleScope еще живет некоторое время после onDestroyView. Корутина может попытаться вызвать изменение Ui которого уже нет и уронить приложение. Отменяйте джобы при покидании экрана. При возврате на экран по кнопке назад onViewCreated вызывается снова и можно подписаться на flow несколько раз. Не забываем отменять подписки.
VieModelScope работает в Main потоке. Не делайте запросы в сеть в мэйн потоке, а лучше вообще откажитесь от использования viewModelScope, а создайте на основе его контекста скоуп с диспетчером io или default. Новый и умрет при уничтожении вьюмодельки и постоянно диспетчер прокидывать не придется.
Вейп не спалил микросхему, а вот толпа с хабра да =)
Еще есть поддержка страниц 16 кб для гугл плэя. Обновляться еще прикольнее. Когда нужно пересобрать нативные заброшенные под лет так 10 назад библиотеки.
Так и представляю мир где все миллиардеры. Где обычных людей нет, а основные потребители услуг твоего бизнеса должны быть такие же миллиардеры, которым твой бизнес не нужен. Потому что у них есть свой сгенерированный через ai.
Тот момент когда упор идет на диск, а тут еще лог пишется.
Мне уже переводят видосы на английский.
В настройках стоит английский. Переключаешь на русский и видео на русском.
Переводит AI. На слух пока очень не очень.
Просто задержка перед повтором операции. Если есть желание повесить поток, то можно убрать.
В целом для холодного потока ограничение во флоу через delay нормальная практика.
Если мы посмотрим на метод debounce во флоу, то он позволит пропускать изменения, только если они не менялись в течении определенного времени. Не всегда же обновлять UI , так можно и привиснуть.
Так же в документации есть замечательные методы для соединения потоков данных (combine и zip), так же для переключения потоков данных flatMapLatest и другие. Возможности выделить из цепочки преобразований данные onEach.
При желании из холодного потока можно сделать горячий через stateIn. (Каждый подписчик на холодный поток вызывает всю цепочку преобразований) Но если перед передачей к подписчикам сделать его горячим, то холодный будет только один до горячего, а дальше расходиться к каждому подписчику.
В целом тема обширная и дает очень много возможностей.
На данный момент лучший выбор для архитектуры UDF
Интересные сравнения. В году так 14 писали еще на 7 жабе. А на седьмой без лямд, без стримов естественно было больше кода. Лучше бы с Котлином сравнили
В обычном Андройде на котлине пишу юзкейсы обновления. В них данные обновляются в RoomDatabase. А из нее через corutine Flow данные летят к экрану. Из минусов, что больше кода нужно писать, и много маперов.
Если делать миграции в базе данных, то добавление полей не большая проблема, ведь можно и дефолтное задать.
Так как это база данных, то можно делать различные джоины и запрашивать, комбинировать, только то что нужно для э4рана.
Чтобы внести изменение в код нужно понять этот код.
А теперь представляем как разработчику дают новый проект. И он несколько дней может только разбираться что уже есть, что нужно сделать и как не сломать то что работает.
Новый человек не всегда может придерживаться архитектуры и сделать как то не так.
Если постоянно менять разработчиков, то код начинает вонять, а поддержка кода становится непосильной.
Выбор без выбора. Дайте денег и куплю даже хрущевку. Зарплаты такие, что дети еще будут за это рассчитываться. А вы тут про какой то стиль жизни.
На своей первой работе был я наладчиком оборудования. У меня был свой кабинет с кучей приборов, и соответственно еще и рабочими столами, где я мог разобрать прибор, все разложить и ничего не потерять, на это требуется пространство. И в один прекрасный день начальник такой.
" Нам нужен стол, у тебя их много, поэтому заберём у тебя"
Естественно мне стало неудобно работать.
Прошло немного времени, в офис купили новый стол другому сотруднику, а старый захотели выбросить на свалку, с трудом отвоевал его себе. Смешно то, что выносить на свалку доверили мне, и он естественно поехал ко мне в подсобку, потому что он был мне нужен.
Потом прошло еще немного времени, и тут начальство опять пришло ко мне за столом и забрали тот стол, который я спасал от уничтожения, для нового сотрудника. И тут я сгорел.
Как же они паниковали, когда я просто не продлил контракт. На мое место им пришлось нанять двоих, и чтобы второй человек смог работать в моей подсобке, однозначно притащить туда стол.
Я наоборот не хочу логиниться ни пальцем ни лицом. Считаю эти функции крайне не безопасными и при утечке биометрии получить проблемы похуже одной взломанной учетки.
Я из тех кого около трех лет пинали hr. Никаких собеседований вообще. Причина, нет коммерческого опыта. И плевать они хотели на фрилансы, пет проекты и. др.
Но на работу таки устроился и именно с тестового задания. Вообще впринципе на собеседования попадал только с тестовых заданий. А взяли работать со второго собеса.
В общем. Если вам нужна работа, то вы и тестовое сделаете.
После некоторых раздумий над отправкой данных и обработок ошибок при отправке решил немного доработать. Всего то заменить.
Так отправляющий сможет не только отправить данные, но и обработать ошибку
Сегодня и я задался такой задачкой. И как истинный джун не полез искать костыли, а сделал свой.
Сам сокет выглядит вот так:
А пользоваться этим примерно так: