Шаблон MVVM по-простому. LiveData.

แชร์
ฝัง
  • เผยแพร่เมื่อ 1 ต.ค. 2024
  • Приветствую 🖐 друзья, в данных видео уроках мы с вами изучим компонент LiveData
    Ссылка на полный курс по MVVM
    market-...
    🤝 Поддержка канала:
    ✅ ВКонтакте: donate_...
    ✅ Donationalerts www.donational....
    ❗️ Важные ссылки ❗️
    1️⃣ Презентацию можно скачать в группе ВКонтакте: mobile_...
    2️⃣ LiveData: developer.andr...
    3️⃣ Корутины: github.com/kot...
    📚 Рекомендуемое бесплатное изучение языка Kotlin
    1️⃣ Приложение для запоминания ключевых слов Kotlin: play.google.co....
    2️⃣ Мини курс по Kotlin в картинках: • #1. Kotlin в картинках...
    √ Тэги для поиска:
    #kotlin #mvvm #livedata #androidstudio #firebase #telegram #создатьприложение

ความคิดเห็น • 127

  • @mr.developer
    @mr.developer  3 ปีที่แล้ว +2

    Можно поддержать автора и канал 🤝 купив полный курс по MVVM здесь:
    boosty.to/mr.developer/posts/fe32632b-1f7e-4c82-9a8e-d2a4e2cb2146?share=post_link
    Список уроков:
    1. Создание проекта.
    2. Заполнение activity_main.xml.
    3. Заполнение fragment_main.xml. Добавление note_item.xml
    4. Заполнение макетов. Добавление кнопок на тулбар
    5. Инициализация объектов в MainActivity
    6. Инициализация StartFragment.kt, создание модели AppNote.kt
    7. Создание DatabaseRepository.kt, реализация репозитория Room
    8. Создание базы данных AppRoomDatabase.kt
    9. Переход с MainFragment.kt на AddNewNoteFragment.kt
    10. Создание новой заметки
    11. Заполнение RecyclerView. Отображение списка заметок
    12. Переход в NoteFragment.kt, удаление заметки из БД
    13. Настройка поведения навигационного графа
    14. Создание с нуля проекта в Firebase
    15. Подключение к Firebase
    16. Макет для выбора базы данных
    17. Быстрая авторизация в Firebase
    18. Создание LiveData для работы с Firebase
    19. Создание новой заметки в Firebase
    20. Удаление заметки из Firebase
    21. Реализация функции выхода из аккаунта Firebase
    22. Добавление анимации в навигацию
    23. Сохранение настроек в SharedPreference.
    Где применим стек технологий:
    -Android SDK;
    -Kotlin;
    -MVVM;
    -LiveData;
    -ViewModel;
    -Room (SQLite);
    -Navigation;
    -Kotlin Coroutines;
    -Firebase SDK;
    -RecyclerView.

  • @Nidvoraich
    @Nidvoraich 3 ปีที่แล้ว +4

    Про медиатор. Прежде, чем объяснять, как оно работает - нужно сначала объяснить, зачем оно нужно.
    Ну отслеживает медиатор сразу две даты. А толку от этого?
    Насколько я понимаю - слово медиатор как бы подсказывает, что таким образом можно реализовать взаимообмен данными между лайв датами. Но это только предположение.
    Резюме: чтоб было проще объяснить - сначала ставьте перед учеником проблему. А потом рассказывайте, как её можно решить.
    А то знание об инструменте получено, а на какую полку этот инструмент положить - непонятно.

  • @cryptoman7743
    @cryptoman7743 4 ปีที่แล้ว +8

    В видео, когда прописали корутину значение hello world! не изменилось. И никто этого не заметил((

    • @alexeymurnikov1413
      @alexeymurnikov1413 4 ปีที่แล้ว +3

      Тоже это смутило, как только это увидел. Но если посмотреть на код автора, то измениться ничего и не должно было, т.к. он изменял значения лайвдаты в фоновом потоке, а значение текствью не трогал и оно осталось по умолчанию. Если цель была в том, чтобы показать, что в фоновом потоке нужно использовать постВэлью, чтобы избежать ошибок, то все было сделано верно)

    • @shepelavka
      @shepelavka 3 ปีที่แล้ว +1

      Тоже заметил. Потом обмотал по видео и увидел - он удалил строчку, где строковое значение из лайвдаты присваивал текстовой вьюхе на активити...))

    • @МаксимТолстобров-э9х
      @МаксимТолстобров-э9х 3 ปีที่แล้ว +2

      я заметил вот хочу тоже написать про это

  • @KirillAndreevich-c9w
    @KirillAndreevich-c9w 3 ปีที่แล้ว +4

    Как на 4:56 обратиться к test_text1.text из MainActivity? У меня тупо не видит такого id-шника :(

    • @KirillAndreevich-c9w
      @KirillAndreevich-c9w 3 ปีที่แล้ว +2

      Если кому актуально, то в build.gradel module нужно было добавить несколько зависимостей (отдельно от основного всего. В самом начале программы)
      apply plugin: 'com.android.application'
      apply plugin: 'kotlin-android'
      apply plugin: 'kotlin-android-extensions'
      И после этого станет доступен импорт в MainActivity.kt:
      import kotlinx.android.synthetic.main.activity_main.*

    • @KirillAndreevich-c9w
      @KirillAndreevich-c9w 3 ปีที่แล้ว +7

      p.s. в самых новых версиях нужно просто добавить строчку "id 'kotlin-android-extensions'" в build.gradel module -> plugins

  • @ЮрійБережний-с7ъ
    @ЮрійБережний-с7ъ 4 ปีที่แล้ว +9

    Уроки супер ! Друг, сделай пожалуйста про корутины, коль ты их здесь упомянул

  • @inex550
    @inex550 3 ปีที่แล้ว +15

    Не понятно зачем мне MediatorLiveData, если я могу просто создать две MutableLiveData, установить им по Observer-у и затем дёргать нужную

    • @behtold
      @behtold 3 ปีที่แล้ว +1

      Абсолютно согласен. Автор не объяснил... Полагаю, что данный метод вызывается при каждом обновлении любой из добавленных MutableLiveData и может взаимодействовать между ними.

    • @kairatkudaibergenov7255
      @kairatkudaibergenov7255 3 ปีที่แล้ว

      пересмотри там всё понятно

  • @РобертБ-ъ9т
    @РобертБ-ъ9т 3 ปีที่แล้ว +12

    7:26 "все отлично, ошибок нет". Но текст "Hello world", а должен быть "Hello live data", разве нет? В чем проблема?

    • @Nidvoraich
      @Nidvoraich 3 ปีที่แล้ว +2

      Не должен. Посмотрите код

    • @halulkin
      @halulkin 3 ปีที่แล้ว +1

      Он передал значение в liveDateString, но не присвоил его для test_text1. Поэтому текст в TextView остался старым.

  • @elpirato5428
    @elpirato5428 4 ปีที่แล้ว +6

    А покажете на простом примере как тесты проводить, как отдельно тестировать модель, вью, вьюмодель?

  • @itcat8369
    @itcat8369 4 ปีที่แล้ว +13

    Отличное объяснение, спасибо за ваш труд, надеюсь не остановитесь, так как очень интересно вас слушать. И не думали ли вы также затронуть такие темы, как: dagger/koin, rxJava/corutines

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว +7

      Спасибо за отзыв, я подумаю)

    • @annasakharova922
      @annasakharova922 4 ปีที่แล้ว +3

      @@mr.developer как постоянным Вашим подписчикам, именно от Вас такие видео было бы просто чудесно изучить)

  • @annasakharova922
    @annasakharova922 4 ปีที่แล้ว +9

    Один из лучших каналов по мобильной разработке на ютубе. Просто супер, все доступно.

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว +1

      Благодарю за отзыв)

  • @abuiman5251
    @abuiman5251 3 ปีที่แล้ว +2

    На 7:27 когда вы передали через postValue данные в LiveData, то как эту строку установить в test_text?? На экране Hello World

    • @-Alexey-
      @-Alexey- 3 ปีที่แล้ว +1

      Обсервер повесить

    • @cinderellarouge
      @cinderellarouge 2 ปีที่แล้ว +1

      Я тоже заметила

  • @mikhaillazarev5378
    @mikhaillazarev5378 9 หลายเดือนก่อน +1

    Про mediatorLiveData как-то с примером не дошло, правильно ли я понял, что по сути эти две LiveData можно спокойно было сделать и без этого mediatora. Лично я не понял его смысл.. Поправить если я не прав..
    А так спасибо за видео 😊

    • @mr.developer
      @mr.developer  9 หลายเดือนก่อน +1

      К сожалению уже не помню ), давно это было. Может поменялось апи

  • @adventuretrip1440
    @adventuretrip1440 ปีที่แล้ว +1

    На 7:25 минуте не отображается "Hello live data" там "Hello World"

  • @МихаилАкулов-ю7ж
    @МихаилАкулов-ю7ж ปีที่แล้ว +2

    Всё отлично, ошибок нет: Hello world. ))))

  • @sergeykrivenkov6436
    @sergeykrivenkov6436 3 ปีที่แล้ว +1

    Откуда столько восторженных отзывов, при том что в коде косяк на косяке?

  • @КонстантинЪЪЪ
    @КонстантинЪЪЪ 3 ปีที่แล้ว +2

    18:40 не понял в чем прикол, весь каеф подписки лайф даты в том, что ну нежно задумываться о жизненном цикле, все это под капотом делается...

  • @NevilPlay
    @NevilPlay 4 ปีที่แล้ว +2

    Прекрасное объяснение работы LiveData, перечислены множество способов работать с БД. Пока новичок в AS Kotlin, немного сложно изучать данный паттерн, думаю, что со временем все пойму. Спасибо!

  • @iqmaxima
    @iqmaxima 3 ปีที่แล้ว +1

    Каким образом без findById находить id test_text1 ?

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว +1

      Kotlin extension, но сейчас устарели. Используйте VBinding

  • @KekLolChill
    @KekLolChill 4 ปีที่แล้ว +3

    При каждом клике на кнопку вешать обсервер так себе идея)

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว

      Вы правы, но это лишь демонстрация возможностей а не руководство к действию.

  • @sanek_180st
    @sanek_180st 3 ปีที่แล้ว +3

    Спасибо, объясняете доступно. LiveData всё понятно. но зачем нужен медиатор (MediatorLiveData) ? мы можем и лайвдате прописать observer ... что без медиатора два раза обсервер, что с медиатором два раза ... суть только в том что появится еще один общий обьединяющий observer ?

    • @mlazebny
      @mlazebny 3 ปีที่แล้ว

      Более структуризированно и понятно.В программировании ценится код который структуризирован, понятен, сжат.

    • @sanek_180st
      @sanek_180st 3 ปีที่แล้ว +2

      @@mlazebny
      первый код без медиатора:
      val mLiveData1 = MyLiveDate()
      val mLiveData2 = MyLiveDate()
      ...
      mLiveData1.observe(this, {...})
      mLiveData2.observe(this, {...})
      -------------------
      второй код с медиатором:
      val mLiveData1 = MyLiveDate()
      val mLiveData2 = MyLiveDate()
      val mediatorLiveData = MediatorLiveData()
      ...
      mediatorLiveData.addSource(mLiveData1, {...})
      mediatorLiveData.addSource(mLiveData2, {...})
      ---------------
      чем он стал структурирован или сжат ?) я вижу разницу только в том что появится общий observer, в этом суть ? или я чтото упустил еще ?

    • @mlazebny
      @mlazebny 3 ปีที่แล้ว

      @@sanek_180st когда испоользуется 2 разных источника данных лучше использовать mediator, легче взаимодействоваьть с элементами

    • @sanek_180st
      @sanek_180st 3 ปีที่แล้ว +3

      @@mlazebny ну вот я в примере привел две лайв даты в чем разница, мы что так что так задаём два обсерва ... Единственное что в медиаторе можно сделать общий обсервер, вы это имеете ввиду ?

    • @mlazebny
      @mlazebny 3 ปีที่แล้ว

      @@sanek_180st да

  • @trunker2010
    @trunker2010 3 ปีที่แล้ว +1

    Не очень понял как работает observer, на 24:30 устанавливается observer после изменения, в моем представлении он должен реагировать на изменения только после того, как был установлен

  • @tilt8215
    @tilt8215 6 หลายเดือนก่อน

    Как ты регистрируешь свой макет в мейн Активити ? Через что ? Нет бандинга и регистрации вьюшек, как ты это делаешь ?

  • @azimjon0910
    @azimjon0910 ปีที่แล้ว +1

    А в чем разница между подходами
    liveData.observe(this, Observer) и mediatorLD.addSource(liveData, Observer)?

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      🤷‍♂️

  • @sno-oze
    @sno-oze 4 ปีที่แล้ว +6

    Годно, ну, ребята, годно же, а? Даже деда спросил: дед тоже сказал, мол "чётко". Ждём следующего видео, глядишь скоро и до Hilt дойдём.

  • @quietplayer7733
    @quietplayer7733 4 ปีที่แล้ว +2

    1 из немногих ютуберов кто внятно объясняет новичкам простыми словами и примерами кода-MVVM, спасибо за контент , надеюсь что это не последнее видео на эту тему и хотелось бы увидеть простенкое приложение на этом патерне, чтобы понять как создавать приложения без лишнего кода))

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว +1

      Благодарю за отзыв, приложение-пример будет

  • @spornov91
    @spornov91 ปีที่แล้ว +1

    можно утонуть от такого колличества лишних слов.

  • @elpirato5428
    @elpirato5428 4 ปีที่แล้ว +2

    спасибо

  • @ЕвгенийЗайкин-ы9х
    @ЕвгенийЗайкин-ы9х 3 ปีที่แล้ว +1

    Немного не понял, а почему мы не можем создавать для каждой LiveData свой observe. Он же также отработает. В чем преимущество и тем более использование медиатора если у нас несколько LiveDat??

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      Да наверно можно

  • @8Sapphire8
    @8Sapphire8 3 ปีที่แล้ว +1

    Ты крут, подача бомба, я такого пока не встречал. Очень полезно.

  • @Valerii07
    @Valerii07 3 ปีที่แล้ว +1

    Вопрос: правильно понимаю, что Лайвдата всегда автоматом срабатывает и выдает список в вью, если в Комнату вставить или обновить, удалить? А как быть если надо в фоновом режиме подкачивать пополнять те вставлять еще данные из Сервера например из Файербейс, то список в вью будет обновляться что не нужно, или вообще какие то фоновые работы выполняются?

    • @Valerii07
      @Valerii07 3 ปีที่แล้ว

      Не пользоваться Лайвдата, а сделать мьютоблЛист + обсервер с очисткой его в онДестройВьюМодел и в онСтарт проверять пуст не пуст, если пуст запрос в Комнату далее срабатывает обсервер и список в Вью обновляется. Может так сделать, А ЛайвДате по идее должен быть выкл автооновлений?

  • @kovanodevelop
    @kovanodevelop 3 ปีที่แล้ว +2

    Доброго дня! Весьма интересное видео. А как "провалиться"?

  • @youandpsycho625
    @youandpsycho625 ปีที่แล้ว +1

    Спасибо!

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      Благодарю вас за отзыв, очень приятно ☺️🤝

  • @NeSpor6
    @NeSpor6 2 ปีที่แล้ว +1

    спасибо

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Благодарю за отзыв 🤝

  • @АлександрЖданов-н4с
    @АлександрЖданов-н4с 2 ปีที่แล้ว

    А что нормально то с корутинами, там же не тот текст был. Там был просто Hello Word пересмотри сам видео

  • @АрнольдСергеевич
    @АрнольдСергеевич 2 ปีที่แล้ว +5

    Спасибо тебе за твой труд. Самое понятное объяснение. Если б ты сделал видеокурс по андроид разработке в целом, купил бы даже за деньги.

  • @qonahx9743
    @qonahx9743 4 ปีที่แล้ว +1

    Зачем в onStart() и onStop() подписываться и отписываться от данных, если класс в принципе был создан для того что бы делать это самостоятельно?

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว +1

      Здравствуйте, это не отписка а удаление наблюдателя. И это я просто показал, что бы явно было видно , что с объектом можно делать.

    • @sergeykrivenkov6436
      @sergeykrivenkov6436 3 ปีที่แล้ว +6

      Автор просто не разобрался в теме которой пытается учить других. Это не может быть "бест практишь", потому что противоречит принципу LiveData - а именно автоматизация. Как раз для того что бы не заниматься подпиской/отпиской на каждый start/stop, в LiveData реализован механизм active/inactive. Тем самым вы не создаете лишних обьектов (observer, observer wrapper), не нагружаете GC, не делаете лишних вычислений. И удалится observer тоже автоматически при переходе Lifecycle в состояние DESTROYED.

  • @agp1444
    @agp1444 3 ปีที่แล้ว +1

    помоему это не лайвдата подписывается на активити, а активити - на лайвдату

  • @Nidvoraich
    @Nidvoraich 3 ปีที่แล้ว

    Если кому станет намного скучно слушать - просто отвечайте "да" каждый раз, как автор спрашивает это ;)

  • @хечпеч
    @хечпеч 3 ปีที่แล้ว

    Самые годные уроки в русскоязычном сегменте. Хотя, когда автор пишет большие программы, то на мой взгляд, слишком много функционально стиля, и если посмотреть на диаграмму классов, то встречаются ошибки при построении архитектуры

  • @openfrom03
    @openfrom03 3 ปีที่แล้ว

    Урок понравился, лайк поставил, подписался.
    Было бы здорово, если бы соблюдали стилистику кода - ставили отступы.
    Рефакторинг кода можно сделать сочетанием клавиш CTRL + ALT + L (Win) /OPTION + CMD + L (Mac)

  • @Arman_127
    @Arman_127 2 ปีที่แล้ว +1

    Большое спасибо всё оказывается просто

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Благодарю за отзыв 🤝

  • @abuiman5251
    @abuiman5251 3 ปีที่แล้ว

    👍🏻👍🏻👍🏻

  • @cinderellarouge
    @cinderellarouge 2 ปีที่แล้ว

    Здравствуйте, на минуте 32:58, когда вы поменяли значение postavalue " "hello world " вышло , но должно было "hello there world".

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      🤷‍♂️ я уж не помню)

  • @МихаилСторожевых
    @МихаилСторожевых 3 ปีที่แล้ว

    Не понятно, зачем нужен Transformations.map. Можно ведь сделать просто: ldStrind.value = ldInt.value.toString().

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Конечно можно, можно вообще ничего не использовать, а писать функциональный код)

  • @ЮлияЛ-ш9е
    @ЮлияЛ-ш9е 2 ปีที่แล้ว +1

    Спасибо большое! Отличное видео!

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Благодарю за отзыв 🤝

  • @kuzd4niil
    @kuzd4niil 3 ปีที่แล้ว

    Отличный видеоурок. Большое спасибо

  • @shantarampampam
    @shantarampampam 3 ปีที่แล้ว

    18:30

  • @GriNAME
    @GriNAME 3 ปีที่แล้ว

    Получается медиатор лайвдаты делает так, чтобы обзёрвер был один на все лайвдаты? Если простым языком

  • @fiodar_vasilets
    @fiodar_vasilets 2 ปีที่แล้ว +1

    так зачем нам медиатор не опнятно ведь и так можно observe подписать на сами LiveData, сделали пустой observe у медиатара, и оставить его пустым ? для чего?

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      👍

    • @fiodar_vasilets
      @fiodar_vasilets 11 หลายเดือนก่อน

      @@mr.developer И что это значит?)

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      Можно по разному)

    • @fiodar_vasilets
      @fiodar_vasilets 11 หลายเดือนก่อน

      Да это понятно) что может быть несколько решений у задачи, но они как бы должны предоставлять свою логику с + и - от других вариантов, а тут я просто хотел бы узнать для чего нам это тут вообще и не лишнее ли это)@@mr.developer

    • @mr.developer
      @mr.developer  11 หลายเดือนก่อน

      @fiodar_vasilets ох, я к сожелению сейчас не вспомню нюансов. Давно это было 🤷‍♂️

  • @АлексейПерцух
    @АлексейПерцух 2 ปีที่แล้ว

    Спасибо! Отличный ролик, после просмотра стало понятно, как работает LiveData.

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Благодарю вас за отзыв 🤝

  • @ЕвгенЗадко
    @ЕвгенЗадко 3 ปีที่แล้ว

    Добрый день! Я б купил курс по MVVM, но не через вк,есть варианты?

    • @mr.developer
      @mr.developer  3 ปีที่แล้ว +1

      Здравствуйте, в Телеграм
      t.me/mobile_developing/71

  • @serioussem-l2
    @serioussem-l2 2 ปีที่แล้ว

    А какие плагины вы используете в студии? нравиться подсветка кода

    • @mr.developer
      @mr.developer  2 ปีที่แล้ว

      Это своя тема

  • @Nidvoraich
    @Nidvoraich 3 ปีที่แล้ว

    Да

  • @aiwarkzn6728
    @aiwarkzn6728 3 ปีที่แล้ว

    Здравствуйте, а как сделать так чтобы к view можно было обращаться без ссылки через R.id. как это сделано у вас? Это расширение какое то?

    • @mr.developer
      @mr.developer  3 ปีที่แล้ว +1

      Да это котлин-расширения. Но сейчас они устарели, используйте ViewBindind

    • @aiwarkzn6728
      @aiwarkzn6728 3 ปีที่แล้ว

      @@mr.developer Понял, спасибо за ответ 🤝

  • @asp424
    @asp424 3 ปีที่แล้ว

    А если какие-то переменные не изменяются, например, состояние авторизации, то лучше использовать константы или LiveData?

    • @mr.developer
      @mr.developer  3 ปีที่แล้ว

      Здравствуйте, переменные не бывают неизменяемые, на то они и переменные. Если есть свойства класса которые не изменяется то это константы.

    • @asp424
      @asp424 3 ปีที่แล้ว

      @@mr.developer понятно, спасибо. Туплю ещё. Всё же, когда лучше константы, а когда LiveData?

  • @КахарманБалтабаев-б2о
    @КахарманБалтабаев-б2о 3 ปีที่แล้ว

    Шикарные уроки! Ты лучшииииий

  • @cubuanic
    @cubuanic 4 ปีที่แล้ว

    В описании линк на Котлин обрезан.
    Вот нормальный линк: th-cam.com/play/PLY8G5DMG6TiNVIy6sZ0RHb14nKDFG7uQc.html

  • @АртемДорожкин-п9г
    @АртемДорожкин-п9г 3 ปีที่แล้ว

    А что это за прикол в 19 строке на 5:13 , когда обращение из кода к текст вью идет просто по айди, без findViewById, без viewBinding?

  • @ipvoodoo
    @ipvoodoo 4 ปีที่แล้ว

    Спасибо, отлично объяснил!

    • @mr.developer
      @mr.developer  4 ปีที่แล้ว

      Благодарю за отзыв

  • @AlekseyKleon
    @AlekseyKleon 4 ปีที่แล้ว +1

    Сорян за ламерский вопрос: пытаюсь сделать то же самое в джаве, там нету корутин((( Через Thread не получается - при операции postValue исключения не возникает, но и визуальных изменений нет. Пожалуйста, не советуйте юзать котлин, мне нужно сделать на джаве.

  • @andrewdobosh2153
    @andrewdobosh2153 3 ปีที่แล้ว

    А как вы в котлин коде обращаетесь к элементу UI без его инициализации методом findViewById? 14:50 здесь например вы ставите клик листенер на кнопку используя ее id? Для этого плагин какой-то нужен?

    • @mr.developer
      @mr.developer  3 ปีที่แล้ว +2

      Это extension Kotlin. Но сейчас он deprecated. Используйте binding

    • @Кирилл-к6к7с
      @Кирилл-к6к7с 7 หลายเดือนก่อน

      @@mr.developer А как в этом случае избежать огромного количества слова "binging" в коде?)