Обновить
3

Пользователь

Отправить сообщение

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. Никаких собеседований вообще. Причина, нет коммерческого опыта. И плевать они хотели на фрилансы, пет проекты и. др.

Но на работу таки устроился и именно с тестового задания. Вообще впринципе на собеседования попадал только с тестовых заданий. А взяли работать со второго собеса.

В общем. Если вам нужна работа, то вы и тестовое сделаете.

После некоторых раздумий над отправкой данных и обработок ошибок при отправке решил немного доработать. Всего то заменить.

    // добавить
    private data class Input(
        val data: String,
        val statusReceiver: (SendStatus) -> Unit
    )

..............
   // заменить
   private val input = Channel<Input>()
..............
  // заменить
  input.consumeEach { income ->
      runCatching {
          send(Frame.Text(income.data))
          workScope.launch { income.statusReceiver(SendStatus.OK) }
      }.onFailure {
          workScope.launch { income.statusReceiver(SendStatus.Error(it)) }
      }
  }
....................
    // заменить и лишний удалить.
    fun send(
        obj: O,
        statusHandler: (SendStatus) -> Unit = {}
    ) = workScope.launch {
        input.send(Input(encoder(obj), statusHandler))
    }

Так отправляющий сможет не только отправить данные, но и обработать ошибку

Сегодня и я задался такой задачкой. И как истинный джун не полез искать костыли, а сделал свой.

Сам сокет выглядит вот так:

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
}

1

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность