<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>@easy_dev991</title><subtitle>Делюсь интересными находками/лайфхаками в процессе разработки под iOS, и возможно ты найдешь что-то полезное для себя!</subtitle><author><name>@easy_dev991</name></author><id>https://teletype.in/atom/easy_dev991</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/easy_dev991?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/easy_dev991?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-08T13:43:44.731Z</updated><entry><id>easy_dev991:hlx_HA24Cgy</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/hlx_HA24Cgy?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>Какой макбук выбрать в 2026 году</title><published>2026-03-28T10:19:10.347Z</published><updated>2026-03-28T10:22:52.957Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/7c/ca/7cca7d4a-d86e-4dce-acee-14c2ddc59bca.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/16/2f/162f3954-4a8e-41c5-9496-7cad859f3172.png&quot;&gt;Предыдущая статья про 2025 год доступна по ссылке - большинство рекомендаций из нее все еще актуальны, кроме цен и версии Xcode, конечно.</summary><content type="html">
  &lt;p id=&quot;GWNq&quot;&gt;Предыдущая статья про 2025 год доступна по &lt;a href=&quot;https://telegra.ph/115-Kakoj-makbuk-vybrat-v-2025-godu-04-16&quot; target=&quot;_blank&quot;&gt;ссылке&lt;/a&gt; - большинство рекомендаций из нее все еще актуальны, кроме цен и версии &lt;code&gt;Xcode&lt;/code&gt;, конечно.&lt;/p&gt;
  &lt;p id=&quot;ZOyD&quot;&gt;За год изменилось немного: появился чип &lt;strong&gt;m5 &lt;/strong&gt;и вышел &lt;strong&gt;Xcode 26...&lt;/strong&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Макбук-для-работы&quot;&gt;Макбук для работы&lt;/h2&gt;
  &lt;p id=&quot;RFz4&quot;&gt;Рекомендации по поводу рабочего макбука двигаем на &lt;strong&gt;1&lt;/strong&gt; цифру вперед, то есть на работе заказываем &lt;strong&gt;MacBook Pro m5 pro &lt;/strong&gt;- как обычно, даже в базовой конфигурации это будет отличный вариант на несколько лет вперед.&lt;/p&gt;
  &lt;p id=&quot;Ulxm&quot;&gt;Если компания не выдает технику, то есть смысл взять то, на что хватает денег из линейки &lt;strong&gt;m3&lt;/strong&gt;-&lt;strong&gt;m5&lt;/strong&gt;. &lt;strong&gt;m1&lt;/strong&gt;-&lt;strong&gt;m2&lt;/strong&gt; не рассматриваем, потому что они уже прилично состарились, а &lt;strong&gt;m2&lt;/strong&gt; и вовсе проиграл в некоторых тестах предшественнику.&lt;/p&gt;
  &lt;h2 id=&quot;Макбук-не-для-работы&quot;&gt;Макбук не для работы&lt;/h2&gt;
  &lt;p id=&quot;tB2h&quot;&gt;В 2020 году я купил себе &lt;strong&gt;MacBook Pro 13&amp;#x27;&amp;#x27; m1 &lt;/strong&gt;за&lt;strong&gt; 150к &lt;/strong&gt;(на официальном сайте с доставкой на дом, чудеса вообще), и он отлично прослужил мне до второй половины 2025 года, когда я его продал за &lt;strong&gt;64к&lt;/strong&gt; - это была отличная продажа и я очень доволен таким успехом, и покупатель будет еще долго доволен, т.к. ему макбук нужен был не для программирования, а для повседневного использования.&lt;/p&gt;
  &lt;p id=&quot;wQLY&quot;&gt;В 2026 не могу рекомендовать макбуки из линейки &lt;strong&gt;m1-m2&lt;/strong&gt;, так что находим выгодные варианты &lt;strong&gt;m3&lt;/strong&gt;-&lt;strong&gt;m5&lt;/strong&gt; (как и для работы) на маркетплейсах и радуемся запасу производительности на ближайшие 3-5 лет.&lt;/p&gt;
  &lt;p id=&quot;rAkm&quot;&gt;Очевидно, &lt;strong&gt;m5&lt;/strong&gt; прослужит лучше в течение 5 лет, чем &lt;strong&gt;m3&lt;/strong&gt;, но для рядового пользователя, полагаю, разницы не будет вообще - не всем же нужно запускать &lt;code&gt;Xcode&lt;/code&gt;/&lt;code&gt;Android Studio&lt;/code&gt; каждый день по несколько раз и ждать выполнение unit/ui-тестов 😁&lt;/p&gt;
  &lt;h2 id=&quot;yaz9&quot;&gt;Macbook Neo&lt;/h2&gt;
  &lt;p id=&quot;Lriu&quot;&gt;Сделал отдельный раздел специально для этой модели, потому что в личку мне уже написали гениальную идею купить этот макбук для ios-разработки.&lt;/p&gt;
  &lt;p id=&quot;nfIW&quot;&gt;Этот раздел статьи не для слабонервных!&lt;/p&gt;
  &lt;p id=&quot;I6kO&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;xc0g&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;rM6S&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;Dc1f&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;ljUM&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;ILCT&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;uSq4&quot;&gt;Я серьезно, если вы фанат Apple или просто ярый поклонник всех макбуков, то лучше не читать дальше!&lt;/p&gt;
  &lt;p id=&quot;VjEX&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;G5Hd&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;AHif&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;I9uX&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;Xx1J&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;ulIb&quot;&gt;.&lt;/p&gt;
  &lt;p id=&quot;7gTg&quot;&gt;Вы еще тут? Окей, поехали)&lt;/p&gt;
  &lt;p id=&quot;hWNE&quot;&gt;Специально для этой модели я покажу скриншоты цен (актуально на конец марта - начало апреля 2026) без рекламы конкретного маркетплейса, и потом мы вместе сравним с ценами на другие модели на том же маркетплейсе, опять же не реклама:&lt;/p&gt;
  &lt;figure id=&quot;pnBQ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/16/2f/162f3954-4a8e-41c5-9496-7cad859f3172.png&quot; width=&quot;2268&quot; /&gt;
    &lt;figcaption&gt;Цены на безымянном (популярном) маркетплейсе на Macbook Neo - от 66к руб с учетом пошлины&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;E9Xj&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b8/32/b832a1ec-d3a0-420d-a268-01144b1c17ea.png&quot; width=&quot;2318&quot; /&gt;
    &lt;figcaption&gt;Цены на том же маркетплейсе на Macbook Air M4 - от 89к руб с учетом пошлины&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Ajp4&quot;&gt;&lt;strong&gt;Важная деталь&lt;/strong&gt;: это сравнение цен на новые ноутбуки, т.е. не б/у, не паленые, а прямо новые.&lt;/p&gt;
  &lt;p id=&quot;CFKc&quot;&gt;&lt;strong&gt;Air m4&lt;/strong&gt; дороже на &lt;strong&gt;23к&lt;/strong&gt; руб (на этом маркетплейсе), но и по большинству параметров он намного лучше.&lt;/p&gt;
  &lt;p id=&quot;9vhE&quot;&gt;У мелких перекупщиков на других популярных сайтах можно найти тот же &lt;strong&gt;air m4&lt;/strong&gt; за &lt;strong&gt;69к&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;hHov&quot;&gt;&lt;u&gt;Внимательный&lt;/u&gt; читатель может спросить: &amp;quot;&lt;em&gt;Зачем покупать дорогой air m4, если можно купить новый модный и молодежный neo дешевле?&lt;/em&gt;&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;jsVf&quot;&gt;На это я &lt;em&gt;отвечу&lt;/em&gt; внимательному читателю: &amp;quot;&lt;em&gt;Если не получается найти разницу при беглом сравнении параметров обеих моделей под свои потребности, то не нужно платить больше&lt;/em&gt;&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;n66a&quot;&gt;Потом &lt;u&gt;сообразительный&lt;/u&gt; читатель может спросить: &amp;quot;&lt;em&gt;Зачем вообще покупать макбук в 2026 году за такие деньги не для ios-разработки, когда можно купить ноутбук на виндовс под любые потребности и по любой цене, в том числе от 15к и до 60к?&lt;/em&gt;&amp;quot;&lt;/p&gt;
  &lt;p id=&quot;GInl&quot;&gt;На что я отвечу сообразительному читателю: &amp;quot;&lt;em&gt;Отличный вопрос, мой дорогой читатель! Нет никакого смысла тратить деньги на макбук, если нет конкретных задач, для которых нужен именно макбук, и с которыми не справятся другие ноутбуки&lt;/em&gt;&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;eLf5&quot;&gt;И пока другие блогеры и СМИ активно пиарят новый &lt;strong&gt;macbook neo&lt;/strong&gt; как убийцу доступных ноутбуков на винде, мы с вами как самые сообразительные понимаем, что это пустой трёп, который нацелен только на отработку рекламного ТЗ, или на сбор хайпа (или и то, и другое разом).&lt;/p&gt;
  &lt;figure id=&quot;fIlM&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/92/09/920960e0-5641-4551-bb55-18da1fbb2857.png&quot; width=&quot;2266&quot; /&gt;
    &lt;figcaption&gt;Вот пример реальных убийц macbook neo на том же маркетплейсе дешевле 25к руб. В карточках товара кринж, но это не так важно.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;gY7l&quot;&gt;И прежде чем набегут желающие защитить божественный бренд Apple от сравнения с безымянными ноутбуками, сначала вспомним, что этот раздел был не для слабонервных и не для фанатов Apple!&lt;/p&gt;
  &lt;p id=&quot;srx3&quot;&gt;А теперь можно вместе подумать, что еще можно купить на оставшиеся 45к руб после покупки ноутбука на винде:&lt;/p&gt;
  &lt;ul id=&quot;0rEu&quot;&gt;
    &lt;li id=&quot;xx5Z&quot;&gt;Отличный новый андроид-смартфон&lt;/li&gt;
    &lt;li id=&quot;2qoL&quot;&gt;Два хороших новых андроид-смартфона&lt;/li&gt;
    &lt;li id=&quot;oxUm&quot;&gt;Хороший андроид-смартфон и еще один ноутбук на винде&lt;/li&gt;
    &lt;li id=&quot;nnbN&quot;&gt;Два дополнительных ноутбука на винде&lt;/li&gt;
    &lt;li id=&quot;1ABD&quot;&gt;45 пицц по 1000 рублей каждая&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;z2uI&quot;&gt;Ну вы поняли, надеюсь 😉&lt;/p&gt;
  &lt;h2 id=&quot;oBfa&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;AYFS&quot;&gt;Итоговый выбор как всегда зависит от ваших конкретных задач (но не покупайте, пожалуйста, &lt;strong&gt;macbook neo&lt;/strong&gt;), для которых выбирается макбук, и в большинстве случаев для разработки подойдет любая модель с чипом &lt;strong&gt;m4&lt;/strong&gt;-&lt;strong&gt;m5&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;gfix&quot;&gt;Удачных покупок!&lt;/p&gt;

</content></entry><entry><id>easy_dev991:baZfVYrGA5H</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/baZfVYrGA5H?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>Загружаем фото в андроид-эмулятор</title><published>2026-03-20T19:18:24.928Z</published><updated>2026-03-20T19:19:44.680Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/1e/19/1e199205-20aa-4772-9b17-0e91fcd114fa.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/51/55/5155b125-26a8-44dc-8192-92da8d2af2a6.png&quot;&gt;В процессе разработки андроид-приложения бывает нужно протестировать функционал добавления фотографий из галереи в приложение.</summary><content type="html">
  &lt;p id=&quot;DZbY&quot;&gt;В процессе разработки андроид-приложения бывает нужно протестировать функционал добавления фотографий из галереи в приложение.&lt;/p&gt;
  &lt;p id=&quot;uvnt&quot;&gt;Есть разные способы загрузки фоток в эмулятор, например:&lt;/p&gt;
  &lt;ul id=&quot;qJML&quot;&gt;
    &lt;li id=&quot;iMIh&quot;&gt;перетащить файлы с фотками на экран эмулятора&lt;/li&gt;
    &lt;li id=&quot;mVuf&quot;&gt;перетащить файлы с фотками в открытое приложение &amp;quot;файлы&amp;quot; на экране эмулятора&lt;/li&gt;
    &lt;li id=&quot;xIBT&quot;&gt;открыть Device Explorer и добавить фотки в папку sdcard/DCIM&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;LOpN&quot;&gt;Но что бы вы ни сделали, все эти способы могут не привести к желаемому результату...&lt;/p&gt;
  &lt;p id=&quot;SB53&quot;&gt;А вот кстати и желаемый результат:&lt;/p&gt;
  &lt;figure id=&quot;1xXu&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/51/55/5155b125-26a8-44dc-8192-92da8d2af2a6.png&quot; width=&quot;1080&quot; /&gt;
    &lt;figcaption&gt;В приложении можно открыть галерею фотографий&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;ddko&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/a8/0ea83121-a09d-4551-ad5c-df2c3961292e.png&quot; width=&quot;1080&quot; /&gt;
    &lt;figcaption&gt;Фото из галереи можно выгрузить в приложение&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;KUrD&quot;&gt;Проблема&lt;/h2&gt;
  &lt;p id=&quot;8TnF&quot;&gt;Все перечисленные ранее способы не сработали:&lt;/p&gt;
  &lt;figure id=&quot;pslV&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0f/95/0f9536df-c57a-4c4c-bac2-35b650a96af1.png&quot; width=&quot;1080&quot; /&gt;
    &lt;figcaption&gt;Фотографии добавлены в эмулятор каждым из перечисленных ранее способов, но приложение их не видит (как и сам эмулятор)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;9Tk4&quot;&gt;Решение&lt;/h2&gt;
  &lt;p id=&quot;cRx5&quot;&gt;После копирования фотографий на эмулятор любым из ранее перечисленных способов выключаем эмулятор и жмем &lt;code&gt;Cold Boot&lt;/code&gt;:&lt;/p&gt;
  &lt;figure id=&quot;qK93&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fb/e3/fbe3b5a9-9508-4f3a-bb0e-e78475f5c01c.png&quot; width=&quot;1080&quot; /&gt;
    &lt;figcaption&gt;Чудо-кнопка, решающая проблему с обнаружением фотографий в эмуляторе&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;AMrX&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;lQAa&quot;&gt;Перед написанием этой статьи я пробовал добавить фотографии в эмулятор всеми перечисленными способами, даже стирал данные эмулятора и перезагружал его - ничего не помогало.&lt;/p&gt;
  &lt;p id=&quot;VheN&quot;&gt;И только чудо-кнопка &lt;code&gt;Cold Boot&lt;/code&gt; помогла.&lt;/p&gt;
  &lt;p id=&quot;Ox3a&quot;&gt;Будет здорово, если кому-то эта статья поможет сэкономить время в подобной ситуации &lt;/p&gt;

</content></entry><entry><id>easy_dev991:BJryrCj0DMr</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/BJryrCj0DMr?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>Xcode 26.3 Coding Assistant</title><published>2026-02-28T09:19:07.158Z</published><updated>2026-02-28T09:19:07.158Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/db/6f/db6f9ffb-be0f-404b-96bb-c8b0f1216126.png"></media:thumbnail><category term="i-os" label="iOS"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/53/eb/53ebfbf1-2440-4b77-ae0e-5e1cf7028697.png&quot;&gt;Вчера активно тестировал режим агента в Xcode 26.3, чтобы вам не пришлось</summary><content type="html">
  &lt;p id=&quot;pk0p&quot;&gt;Вчера активно тестировал режим агента в &lt;code&gt;Xcode 26.3&lt;/code&gt;, чтобы вам не пришлось  &lt;/p&gt;
  &lt;h2 id=&quot;O4du&quot;&gt;С чем имеем дело&lt;/h2&gt;
  &lt;p id=&quot;B51q&quot;&gt;В &lt;code&gt;Xcode 26.3&lt;/code&gt; нельзя подключить любую свою модель кроме предусмотренных из коробки&lt;/p&gt;
  &lt;figure id=&quot;Uxb2&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/53/eb/53ebfbf1-2440-4b77-ae0e-5e1cf7028697.png&quot; width=&quot;1644&quot; /&gt;
    &lt;figcaption&gt;Это обман, не ведитесь на него зря!&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bZ2Y&quot;&gt;Да, есть настройка для добавления кастомного провайдера моделей, но она не совместима с &lt;code&gt;Z.AI&lt;/code&gt; (провайдер &lt;code&gt;GLM&lt;/code&gt;-моделей), например.&lt;/p&gt;
  &lt;p id=&quot;SYFt&quot;&gt;Чтобы подключить своего провайдера, нужно подключать специальный прокси (при желании можно даже найти бесплатные варианты), так я и сделал.&lt;/p&gt;
  &lt;p id=&quot;xmwv&quot;&gt;Я не использовал &lt;code&gt;ChatGPT&lt;/code&gt;/&lt;code&gt;Claude&lt;/code&gt; в &lt;code&gt;Xcode&lt;/code&gt;, потому что у меня куплены другие подписки (&lt;code&gt;cursor&lt;/code&gt; + &lt;code&gt;Z.AI&lt;/code&gt;).&lt;/p&gt;
  &lt;h2 id=&quot;mmgr&quot;&gt;Сразу к результатам&lt;/h2&gt;
  &lt;h3 id=&quot;5vCg&quot;&gt;Положительные моменты&lt;/h3&gt;
  &lt;ul id=&quot;K28j&quot;&gt;
    &lt;li id=&quot;a52i&quot;&gt;Чат работает? - чек&lt;/li&gt;
    &lt;li id=&quot;FphR&quot;&gt;Анимации в чате работают? - чек&lt;/li&gt;
    &lt;li id=&quot;hSAc&quot;&gt;Градиенты в &lt;code&gt;Xcode&lt;/code&gt; отображаются? - чек&lt;/li&gt;
    &lt;li id=&quot;kGZk&quot;&gt;Код генерируется? - чек&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;xsXv&quot;&gt;Отрицательные моменты&lt;/h3&gt;
  &lt;ul id=&quot;PNE7&quot;&gt;
    &lt;li id=&quot;cBnr&quot;&gt;Модель может выполнять команды в терминале? - нет&lt;/li&gt;
    &lt;li id=&quot;YGTd&quot;&gt;Модель может писать код в разных файлах, которые не открыты в &lt;code&gt;Xcode&lt;/code&gt;? - нет&lt;/li&gt;
    &lt;li id=&quot;3GXZ&quot;&gt;При включении/выключении прокси &lt;code&gt;Xcode&lt;/code&gt; обновляет статус в окне с чатом? - нет&lt;/li&gt;
    &lt;li id=&quot;WCpn&quot;&gt;Модель может взаимодействовать с тестами или симулятором? - нет&lt;/li&gt;
    &lt;li id=&quot;rCx9&quot;&gt;При начале нового диалога &lt;code&gt;Xcode&lt;/code&gt; запоминает выбранную ранее модель? - нет&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;pgbc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/77/a8/77a86ba0-ff2c-40fe-9c11-857754776a7c.png&quot; width=&quot;784&quot; /&gt;
    &lt;figcaption&gt;Стрём, который мы заслужили от Apple (1)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;Mw6g&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d4/72/d472a71a-aeba-43ac-abf2-34aee293323e.png&quot; width=&quot;758&quot; /&gt;
    &lt;figcaption&gt;Стрём, который мы заслужили от Apple (2)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;sd1q&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2a/07/2a078e47-98fc-4477-8899-7b2dfaa511da.png&quot; width=&quot;934&quot; /&gt;
    &lt;figcaption&gt;Пруф, что тесты в проекте есть&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7bdC&quot;&gt;Больше всего меня удивил момент, когда модель написал код в одном из двух нужных файлов, а вместо изменения второго файла создала новый, что привело к ошибке компиляции, и эту ошибку комплияции она не смогла исправить, потому что не удалось, видите ли, сделать изменения в нужном файле.&lt;/p&gt;
  &lt;p id=&quot;mxAF&quot;&gt;Полагаю, дело в объеме второго файла - там больше тысячи строк кода, но в других инструментах (&lt;code&gt;qwen-code&lt;/code&gt;, &lt;code&gt;opencode&lt;/code&gt;, &lt;code&gt;cursor&lt;/code&gt;) такой проблемы ни разу не было.&lt;/p&gt;
  &lt;h2 id=&quot;hzOt&quot;&gt;Сравнение с Android Studio&lt;/h2&gt;
  &lt;figure id=&quot;4bSw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ab/03/ab0316ba-58f8-4cf6-b18b-9a8f54ee0594.png&quot; width=&quot;3402&quot; /&gt;
    &lt;figcaption&gt;Все отлично - любую модель можно открыть в терминале и все будет отображаться в одном окне, очень удобно&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;rTAr&quot;&gt;Вывод&lt;/h2&gt;
  &lt;p id=&quot;uZUe&quot;&gt;То, что Apple нам рекламируют, по уже сложившейся традиции оказывается полной ерундой.&lt;/p&gt;
  &lt;p id=&quot;VfE8&quot;&gt;Нет никакого оправдания, почему в 2026 году &lt;code&gt;Xcode&lt;/code&gt; не может дать доступ к терминалу подключенным нейросетям, или хотя бы дать дефолтный &lt;code&gt;mcp&lt;/code&gt; для работы с тестами и симулятором.&lt;/p&gt;
  &lt;p id=&quot;wKMs&quot;&gt;Ну и отсутствие терминала уже ни в какие рамки не идет. Хоть я и не знаю почти никаких команд в терминале, и вообще не фанат модных движений типа TUI, но отсутствие терминала в &lt;code&gt;Xcode&lt;/code&gt; - это показатель их провала.&lt;/p&gt;
  &lt;figure id=&quot;Ene0&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/52/c1/52c1eb17-e02b-4297-8dd9-bd64a1690a34.png&quot; width=&quot;1022&quot; /&gt;
    &lt;figcaption&gt;Да, тоггл есть, но толку от него нет. Так держать, Apple.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;19wc&quot;&gt;Но вдруг у меня слишком высокие требования к инструментам?&lt;/p&gt;
  &lt;p id=&quot;12XD&quot;&gt;Если вам нравится использовать &lt;code&gt;Coding Assistant&lt;/code&gt; в &lt;code&gt;Xcode&lt;/code&gt;, поделитесь обратной связью в комментариях, пожалуйста. Вдруг я чего-то не знаю, и там все отлично работает, если использовать это по-другому.&lt;/p&gt;

</content></entry><entry><id>easy_dev991:zlfw_fKJvh9</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/zlfw_fKJvh9?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>Приложение для Android с нуля до релиза за 3 недели</title><published>2026-01-25T15:06:37.639Z</published><updated>2026-01-25T15:06:37.639Z</updated><category term="android" label="Android"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/64/51/6451d1a1-8c87-4c66-b17e-922425af20e2.png&quot;&gt;Все больше классных смартфонов на андроиде выпускают, все сильнее стирается граница между iOS/Android, в особенности из-за санкций и ограничений на важные и нужные для жизни сервисы, и я решил сделать андроид-версию своего счётчика дней в новогодние праздники (которые у меня объединились с отпуском).</summary><content type="html">
  &lt;p id=&quot;t5Ee&quot;&gt;Все больше классных смартфонов на андроиде выпускают, все сильнее стирается граница между iOS/Android, в особенности из-за санкций и ограничений на важные и нужные для жизни сервисы, и я решил сделать андроид-версию своего &lt;a href=&quot;https://github.com/easydev991/SwiftUI-Days&quot; target=&quot;_blank&quot;&gt;счётчика дней&lt;/a&gt; в новогодние праздники (которые у меня объединились с отпуском).&lt;/p&gt;
  &lt;h2 id=&quot;OIx6&quot;&gt;Предыстория и о чем статья&lt;/h2&gt;
  &lt;p id=&quot;lSZ1&quot;&gt;Чуть больше двух лет назад я начинал изучать верстку с использованием Jetpack Compose, потому что это похожая штука на SwiftUI, и даже успел за пару месяцев сверстать большинство компонентов дизайн-системы для приложения с площадками для тренировок (&lt;a href=&quot;https://github.com/easydev991/SwiftUI-WorkoutApp&quot; target=&quot;_blank&quot;&gt;вот iOS-версия&lt;/a&gt;), но потом все же забросил это дело, потому что:&lt;/p&gt;
  &lt;ul id=&quot;VOCw&quot;&gt;
    &lt;li id=&quot;zeMi&quot;&gt;хотелось развивать iOS-приложения, и я это успешно делал&lt;/li&gt;
    &lt;li id=&quot;N4e8&quot;&gt;не хотелось тратить время на изучение андроид-разработки с нуля, когда я уже умею делать приложения для iOS&lt;/li&gt;
    &lt;li id=&quot;cbKV&quot;&gt;в то время мне еще совсем не казалось, что есть смысл смотреть в сторону смены айфона на андроид (а теперь ситуация постепенно меняется)&lt;/li&gt;
    &lt;li id=&quot;glL7&quot;&gt;не пользовался нейросетями для разработки &lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;582s&quot;&gt;Насчёт счётчика дней - именно с идеи этого приложения я и начинал свою карьеру в iOS-разработке, при этом сам же пользуюсь этим приложением на айфоне, и поэтому решил именно с него начать обучение андроид-разработке в этот раз.&lt;/p&gt;
  &lt;p id=&quot;EaKB&quot;&gt;В этой статье расскажу самое интересное и главное на мой взгляд про андроид-разработку с нуля и до публикации в русторе. И пара слов будет про рустор.&lt;/p&gt;
  &lt;h2 id=&quot;rDWF&quot;&gt;Что нужно знать и уметь&lt;/h2&gt;
  &lt;h3 id=&quot;60ya&quot;&gt;Язык программирования&lt;/h3&gt;
  &lt;p id=&quot;V3Gk&quot;&gt;Как ни странно, не нужно знать язык программирования, чтобы сделать андроид-приложение. Т.е. ни Kotlin, ни Java мне не пригодились. Первый я могу очень поверхностно читать (он похож на Swift), а второй вообще не знаю и не хочу знать . В разработке мне это не понадобилось совсем.&lt;/p&gt;
  &lt;h3 id=&quot;FUzC&quot;&gt;Софты&lt;/h3&gt;
  &lt;p id=&quot;wude&quot;&gt;Нужно уметь формулировать свои мысли в слова, чтобы писать запросы в нейросеть.&lt;/p&gt;
  &lt;p id=&quot;fGFK&quot;&gt;Нужно уметь излагать свои мысли простым и понятным языком, и в достаточной степени детализировать постановку задачи, чтобы получить результат как можно ближе к желаемому.&lt;/p&gt;
  &lt;h3 id=&quot;8Lxh&quot;&gt;Планирование&lt;/h3&gt;
  &lt;p id=&quot;VWoM&quot;&gt;Для разработки андроид-приложения мне пригодились навыки планирования и деления задач на подзадачи (декомпозиция).&lt;/p&gt;
  &lt;p id=&quot;qe4V&quot;&gt;Абсолютно все задачи для нейросети я провожу через планирование. Нельзя просто сказать &amp;quot;Хочу такое приложение, чтобы в нем можно было запоминать события и считать дни&amp;quot; - получится полная фигня, хотя что-то может быть даже будет работать &lt;/p&gt;
  &lt;p id=&quot;AT8B&quot;&gt;Нужно уметь составить план по разработке приложения для начала у себя в голове, а потом перенести его в текстовый формат для последующей обработки нейросетью.&lt;/p&gt;
  &lt;h3 id=&quot;urFz&quot;&gt;Принципы разработки&lt;/h3&gt;
  &lt;p id=&quot;I9HT&quot;&gt;В процессе разработки iOS-приложений я привык обращать внимание на разные моменты, а отдельно хочу выделить эти:&lt;/p&gt;
  &lt;ol id=&quot;Z8Oi&quot;&gt;
    &lt;li id=&quot;wWHc&quot;&gt;Пользовательский опыт (&lt;strong&gt;UX&lt;/strong&gt;) и интерфейс (&lt;strong&gt;UI&lt;/strong&gt;): дизайн должен быть интуитивным, простым и удобным для использования одной рукой; важна консистентность элементов управления и предсказуемое поведение.&lt;/li&gt;
    &lt;li id=&quot;VRA6&quot;&gt;&lt;strong&gt;Адаптивность:&lt;/strong&gt; интерфейс обязан корректно отображаться на экранах разного размера и разрешения&lt;/li&gt;
    &lt;li id=&quot;iwZp&quot;&gt;&lt;strong&gt;Следование гайдлайнам:&lt;/strong&gt; использование принципов &lt;u&gt;Material Design&lt;/u&gt; для Android и &lt;u&gt;Human Interface Guidelines&lt;/u&gt; для iOS для обеспечения привычного пользователям опыта&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;P4KI&quot;&gt;Формулировки взял из интернета, надеюсь что звучат понятно.&lt;/p&gt;
  &lt;p id=&quot;H3NL&quot;&gt;Это все не означает, что я знаю наизусть гайдлайны Android/iOS, или что я всегда планирую разработку для смартфонов и планшетов сразу, но я осознанно стремлюсь делать приложение так, чтобы пользователю было удобно и привычно, и чтобы пользователю не пришлось сталкиваться с ошибками, которые портят впечатление от использования приложения.&lt;/p&gt;
  &lt;h3 id=&quot;oXkP&quot;&gt;Система контроля версий (Git)&lt;/h3&gt;
  &lt;p id=&quot;qt4Q&quot;&gt;Про гит я писал в отдельной статье (&lt;a href=&quot;https://telegra.ph/Pro-rabotu-s-Git-02-23&quot; target=&quot;_blank&quot;&gt;ссылка&lt;/a&gt;), и если вам эта статья пригодилась, то призываю поставить лайк в &lt;a href=&quot;https://t.me/easy_dev991/79&quot; target=&quot;_blank&quot;&gt;телеграм&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;sV5O&quot;&gt;Гит позволяет вести разработку с высокой степенью уверенности в сохранении результатов работы. Поскольку нейросеть регулярно ломает код, то при помощи гита можно вернуться на предыдущий этап, когда все работало.&lt;/p&gt;
  &lt;h2 id=&quot;py7l&quot;&gt;Инструменты&lt;/h2&gt;
  &lt;p id=&quot;QnvP&quot;&gt;Я заранее определил идею для приложения, у меня был готовый дизайн в iOS-приложении, поэтому переходим к выбору инструментов.&lt;/p&gt;
  &lt;h3 id=&quot;r9xL&quot;&gt;&lt;strong&gt;Android Studio&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;Ig07&quot;&gt;Тут запускаю приложение на эмуляторе и нажимаю иногда кнопку для форматирования кода, фича кстати классная, вот демо:&lt;/p&gt;
  &lt;figure id=&quot;PJVW&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/eb/41/eb4178a1-2e13-4d83-8d93-30bbc86cc3d3.png&quot; width=&quot;1364&quot; /&gt;
    &lt;figcaption&gt;Правык клик на папке с файлами -&amp;gt; Reformat Code&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;MGRu&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/18/75/18750d7f-de41-4a4b-991d-053dc700a5fd.png&quot; width=&quot;1516&quot; /&gt;
    &lt;figcaption&gt;Ставлю три галки и жму &amp;quot;Run&amp;quot;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;fwKm&quot;&gt;Cursor&lt;/h3&gt;
  &lt;p id=&quot;5Erf&quot;&gt;Тут происходит основная разработка.&lt;/p&gt;
  &lt;p id=&quot;8V4Q&quot;&gt;Часто после генерации кода в курсоре мне приходится вручную исправлять пропущенные импорты в Android Studio для каких-нибудь кнопок или отступов, но это не требует знания языка программирования:&lt;/p&gt;
  &lt;figure id=&quot;pF3w&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/28/6d/286dd40e-0707-4b0a-b9f8-db51c96091ea.png&quot; width=&quot;1524&quot; /&gt;
    &lt;figcaption&gt;Навожу курсор на слово выделенное красным, жду пока появится всплывающее окно со словами Unresolved reference, и жму на кнопку Import class ...&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;Nom6&quot;&gt;Qwen Code CLI&lt;/h3&gt;
  &lt;p id=&quot;W7pr&quot;&gt;Крутой бесплатный (на данный момент) инструмент для разработки (и не только), всем рекомендую попробовать, &lt;a href=&quot;https://github.com/QwenLM/qwen-code&quot; target=&quot;_blank&quot;&gt;вот ссылка на гитхаб&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;1tH7&quot;&gt;Использовал для составления планов, исправления мелких ошибок, больших (но простых) рутинных задач типа перемещения файлов в проекте и т.д. - может работать часами и не упирается в лимит, в отличие от курсора. Ну и отвечает менее качественно, так что компромисс.&lt;/p&gt;
  &lt;h3 id=&quot;u7Qr&quot;&gt;Perplexity&lt;/h3&gt;
  &lt;p id=&quot;2RJM&quot;&gt;Бывает, что ни курсор, ни квен не дают нормальный ответ и предлагают плохие решения, которые очевидно не работают (судя по поведению приложения в эмуляторе), и я закидываю вопрос в перплексити, где внезапно получаю рабочее решение - просто пересылаю это решение в курсор/квен и говорю применить. Иногда помогает сразу, иногда нужно поискать обходные решения, но это тоже крутой инструмент.&lt;/p&gt;
  &lt;h2 id=&quot;sENU&quot;&gt;Сроки&lt;/h2&gt;
  &lt;p id=&quot;DTWx&quot;&gt;Начал разработку 29 декабря 2025 года, релиз в русторе опубликовал через 19 дней - 17 января 2026 года. Код приложения в &lt;a href=&quot;https://github.com/easydev991/Jetpack-Days&quot; target=&quot;_blank&quot;&gt;гитхабе&lt;/a&gt; - там можно посмотреть историю коммитов (первый коммит и коммит с тегом 1.0).&lt;/p&gt;
  &lt;p id=&quot;m9L8&quot;&gt;19 дней я округлил до трех недель просто для красоты. Если считать прямо по 8 часов разработки, то выйдет около 12 дней. В отпуске я сделал 90% работы, а после отпуска за несколько вечеров все доделал и выпустил релиз.&lt;/p&gt;
  &lt;h2 id=&quot;P4sE&quot;&gt;Что мне понравилось&lt;/h2&gt;
  &lt;h3 id=&quot;VaND&quot;&gt;Работа с Android Studio&lt;/h3&gt;
  &lt;p id=&quot;A7o5&quot;&gt;После Xcode это показалось просто прорывом во многих аспектах, кроме разве что тестов. Запускать тесты в студии очень неудобно, ну или я еще не научился делать это удобно: в Xcode есть одно меню для запуска нужных тестов, а в студии я такое не нашел 🫣&lt;/p&gt;
  &lt;p id=&quot;1M3Y&quot;&gt;Больше всего понравилось:&lt;/p&gt;
  &lt;ul id=&quot;ntfN&quot;&gt;
    &lt;li id=&quot;CgBm&quot;&gt;форматирование кода, о котором раньше уже написал и показал скриншоты&lt;/li&gt;
    &lt;li id=&quot;F6Qt&quot;&gt;превью Jetpack Compose (запускается быстро, никаких крашей в фоне, удобнее чем в Xcode)&lt;/li&gt;
    &lt;li id=&quot;YHXh&quot;&gt;наличие терминала и плагинов, например, для работы с Makefile&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;0avA&quot;&gt;В терминале я и квен запускал, и команды из Makefile (пока не нашел плагин для работы с Makefile) - этого очень не хватает в Xcode:&lt;/p&gt;
  &lt;figure id=&quot;Bjpp&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fe/49/fe49045f-7ce0-4fce-ac48-01e9dc3fa0e6.png&quot; width=&quot;1344&quot; /&gt;
    &lt;figcaption&gt;Кнопки для терминала и плагина Makefile&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;dmTE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f2/0c/f20c1634-5edc-4125-8556-49fefecb8da6.png&quot; width=&quot;3504&quot; /&gt;
    &lt;figcaption&gt;Слева код, справа тесты в терминале, и все это в одном месте (Android Studio) - чудеса!&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;g5T3&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/30/4d/304dd16f-0ef5-4191-bb5d-2c3e445ce02f.png&quot; width=&quot;820&quot; /&gt;
    &lt;figcaption&gt;Плагин для Makefile в действии - все команды из Makefile можно запускать прямо по кнопке &amp;quot;play&amp;quot;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;dewW&quot;&gt;Вес приложения&lt;/h3&gt;
  &lt;p id=&quot;x7x6&quot;&gt;Андроид приложение значительно легче iOS-версии. Опубликованное в русторе приложение весит всего &lt;strong&gt;2.6 мб&lt;/strong&gt; (AAB-файл), APK в гитхабе весит всего &lt;strong&gt;3.85 мб&lt;/strong&gt;, тогда как iOS-версия весит &lt;strong&gt;5.9 мб&lt;/strong&gt;, причем около 40% веса добавилось из-за перехода с Xcode 16.4 на Xcode 26 &lt;/p&gt;
  &lt;h3 id=&quot;LE0T&quot;&gt;Минимальная версия Android&lt;/h3&gt;
  &lt;p id=&quot;0BKU&quot;&gt;Приложение работает на версии Android 8 и выше, релиз этой версии андроид был в 2017 году. Вся верстка на Jetpack Compose, стабильная версия которого вышла в 2021 году!&lt;/p&gt;
  &lt;p id=&quot;Mhhu&quot;&gt;iOS-приложение работает на версии iOS 17 и выше, релиз этой версии iOS был в 2023 году. Эту версию системы я выбрал, чтобы использовать упрощенный инструмент для хранения данных приложения (&lt;a href=&quot;https://developer.apple.com/documentation/swiftdata&quot; target=&quot;_blank&quot;&gt;Swift Data&lt;/a&gt;). Вся верстка на SwiftUI, который появился в 2019 году и был настолько дырявым, что его мало кто использовал в первые 2 года после релиза.&lt;/p&gt;
  &lt;p id=&quot;NGGh&quot;&gt;В общем, андроид ушел в большой отрыв в плане удобства верстки.&lt;/p&gt;
  &lt;h2 id=&quot;extO&quot;&gt;Что мне не понравилось&lt;/h2&gt;
  &lt;h3 id=&quot;wz1E&quot;&gt;Навигация в Jetpack Compose&lt;/h3&gt;
  &lt;p id=&quot;ncLg&quot;&gt;В iOS есть стандартные (нативные) инструменты для создания навигации любой сложности, но главное - есть простой и понятный способ как сделать простую навигацию по приложению.&lt;/p&gt;
  &lt;p id=&quot;JMMn&quot;&gt;В андроиде с навигацией намного сложнее, как и со многими другими штуками - для навигации надо подключать специальную зависимость (&lt;a href=&quot;https://developer.android.com/jetpack/androidx/releases/navigation&quot; target=&quot;_blank&quot;&gt;ссылка на документацию&lt;/a&gt;), и с ней пришлось немало повозиться, чтобы настроить безопасную зону (фронтальная камера, нижняя панель навигации) и передачу данных между экранами.&lt;/p&gt;
  &lt;h3 id=&quot;fzik&quot;&gt;Gradle&lt;/h3&gt;
  &lt;p id=&quot;gl3a&quot;&gt;Если бы это было моей работой, я бы разобрался в этом.&lt;/p&gt;
  &lt;p id=&quot;Bg8N&quot;&gt;Но поскольку это не работа (а хобби), то ну его нафиг, потому что выглядит это нифига не весело, и ни разу не очевидно 🤪&lt;/p&gt;
  &lt;p id=&quot;uF6S&quot;&gt;Нахожу решения проблем, включая обходные решения, и на этом заканчиваю разбор. &lt;/p&gt;
  &lt;h3 id=&quot;MO6m&quot;&gt;Обилие инструментов&lt;/h3&gt;
  &lt;p id=&quot;9I0O&quot;&gt;После Xcode, в котором объективно почти ничего лишнего нет (как и некоторых нужных вещей), в студии просто дофига всего, что мне не нужно, и непонятно. И это уже после их редизайна, в котором неплохо так изменили UI и спрятали многие кнопки.&lt;/p&gt;
  &lt;p id=&quot;Jlkx&quot;&gt;В том числе было непросто настроить линтер и форматирование кода - выбрал два инструмента (ktlint + detekt), которые конечно же конфликтуют в некоторых настройках &lt;/p&gt;
  &lt;p id=&quot;1lLw&quot;&gt;Выбирал не по своему опыту (т.к. у меня его нет), а по предложению гугла и нейросети, но без линтера и форматирования кода можно намесить знатную кашу, поэтому лучше уж с ними, особенно если код пишу не сам.&lt;/p&gt;
  &lt;h3 id=&quot;o92I&quot;&gt;Эмуляторы&lt;/h3&gt;
  &lt;p id=&quot;EoO1&quot;&gt;Они издают неприятные системные звуки при смене языка в настройках и вообще при запуске приложения (и еще в разных ситуациях). Похоже, пришло время погуглить как это отключить (если это вообще возможно) &lt;/p&gt;
  &lt;h2 id=&quot;GsMR&quot;&gt;Рустор&lt;/h2&gt;
  &lt;p id=&quot;vA9W&quot;&gt;Специально пишу на русском языке название этого магазина приложений, потому что, блин, это специальный магазин приложений конкретно для РФ. Нафига его называть английскими буквами?)&lt;/p&gt;
  &lt;h3 id=&quot;TWvX&quot;&gt;Требования к приложению&lt;/h3&gt;
  &lt;p id=&quot;COF4&quot;&gt;&lt;strong&gt;5 звезд&lt;/strong&gt; за требования к приложению перед релизом - минимум полей для настройки приложения перед отправкой на модерацию.&lt;/p&gt;
  &lt;p id=&quot;41kY&quot;&gt;В AppStore просто миллион полей, еще и разбросаны по разным местам на сайте.&lt;/p&gt;
  &lt;h3 id=&quot;tpV3&quot;&gt;Проверка приложения&lt;/h3&gt;
  &lt;p id=&quot;ALXg&quot;&gt;&lt;strong&gt;5 звезд&lt;/strong&gt; за скорость проверки приложения на релизе - меньше часа с момента &lt;strong&gt;первой&lt;/strong&gt; отправки приложения до одобрения и публикации.&lt;/p&gt;
  &lt;p id=&quot;bk8s&quot;&gt;В iOS у меня был рекорд по скорости публикации одного из релизов - &lt;a href=&quot;https://t.me/easy_dev991/204&quot; target=&quot;_blank&quot;&gt;меньше часа&lt;/a&gt;, но это был не первый релиз, а это важно. &lt;strong&gt;Первый&lt;/strong&gt; релиз в iOS у меня был долгим для каждого из трех приложений: модератор задает одни и те же бесполезные вопросы про монетизацию, целевую аудиторию и т.д., хотя в приложении не подключена монетизация, есть описание приложения на русском и английском языках, и заполнены все миллион обязательных полей в кабинете разработчика.&lt;/p&gt;
  &lt;h3 id=&quot;evjQ&quot;&gt;UX в русторе&lt;/h3&gt;
  &lt;p id=&quot;6ER4&quot;&gt;&lt;strong&gt;3 звезды&lt;/strong&gt; - слишком часто выдает бессмысленные ошибки в стиле &amp;quot;что-то пошло не так&amp;quot; и просит заново авторизоваться. Пишу в поддержку - они просят скриншоты. Присылаю скриншоты - они пишут, что нужны другие скриншоты, и что я что-то делаю не так.&lt;/p&gt;
  &lt;h3 id=&quot;iQbs&quot;&gt;Аналитика по приложению&lt;/h3&gt;
  &lt;figure id=&quot;JjCc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6c/b5/6cb5814c-0ad8-46ab-819f-e6d9669d1c8b.png&quot; width=&quot;2234&quot; /&gt;
    &lt;figcaption&gt;Вот и вся доступная аналитика&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ayHM&quot;&gt;&lt;strong&gt;1 звезда&lt;/strong&gt; - почти никакой аналитики нет, и это плохо. Даже в AppStore есть стандартная аналитика, включая падения приложения.&lt;/p&gt;
  &lt;p id=&quot;ycQF&quot;&gt;Поддержка рустора сказала, что можно установить их инструмент для сбора аналитики в качестве отдельной зависимости, но я решил, что пока рановато, и подключил Firebase для отслеживания падений приложения. К слову, пока падений нет, как и пользователей &lt;/p&gt;
  &lt;h2 id=&quot;8ZAE&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;2Hr7&quot;&gt;Вот такой врыв в андроид я совершил неожиданно для самого себя в конце 2025 года, и считаю это успешным результатом. Еще раз &lt;a href=&quot;https://github.com/easydev991/Jetpack-Days&quot; target=&quot;_blank&quot;&gt;ссылка на код&lt;/a&gt;, и &lt;a href=&quot;https://www.rustore.ru/catalog/app/com.dayscounter&quot; target=&quot;_blank&quot;&gt;ссылка на приложение в русторе&lt;/a&gt; - буду признателен за отзывы и тестирование &lt;/p&gt;
  &lt;p id=&quot;0VBG&quot;&gt;Приложение делал, чтобы проверить, насколько это вообще реально без знания языка, ну и всей специфики андроида, и чтобы набить руку.&lt;/p&gt;
  &lt;p id=&quot;s8OL&quot;&gt;Сейчас в свободное время делаю приложение с площадками для тренировок (аналог &lt;a href=&quot;https://github.com/easydev991/SwiftUI-WorkoutApp&quot; target=&quot;_blank&quot;&gt;iOS-версии&lt;/a&gt;), и там уже будет полный фарш с точки зрения логики и количества фичей, потому что в приложении больше 50 запросов к серверу и куча экранов, это будет интересно &lt;/p&gt;
  &lt;p id=&quot;hcrC&quot;&gt;Отдельно в платном канале напишу больше подробностей и практических примеров разработки в андроиде, включая планирование, тесты и правила/навыки ... для нейросети.&lt;/p&gt;
  &lt;p id=&quot;TsnU&quot;&gt;Спасибо за внимание!&lt;/p&gt;

</content></entry><entry><id>easy_dev991:0f3ypt50ZJT</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/0f3ypt50ZJT?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>135. Тестируем парсинг моделей</title><published>2025-12-14T09:55:02.735Z</published><updated>2025-12-14T10:28:45.064Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a2/09/a209dad4-9415-4a2d-aa66-be82a023adb6.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/62/6c/626ce370-cf46-4cd2-8e39-35978103aea9.png&quot;&gt;Недавно я выпустил в аппстор приложение для тренировок, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅</summary><content type="html">
  &lt;p id=&quot;LxAA&quot;&gt;Недавно я выпустил в аппстор &lt;a href=&quot;https://apps.apple.com/app/id6753644091&quot; target=&quot;_blank&quot;&gt;приложение для тренировок&lt;/a&gt;, предварительно провел много ручных тестов и написал больше тысячи автотестов. В этой статье расскажу про важность забытых мной тестов 😅&lt;/p&gt;
  &lt;h2 id=&quot;VPU3&quot;&gt;Тесты в первом релизе&lt;/h2&gt;
  &lt;figure id=&quot;VTP1&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/62/6c/626ce370-cf46-4cd2-8e39-35978103aea9.png&quot; width=&quot;1404&quot; /&gt;
    &lt;figcaption&gt;Автотесты на момент релиза первой версии приложения&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7igo&quot;&gt;Вот обобщенный перечень тестов, которые были на момент первого релиза:&lt;/p&gt;
  &lt;ul id=&quot;kmEj&quot;&gt;
    &lt;li id=&quot;PACM&quot;&gt;Тесты для экрана выполнения тренировки (настройка, завершение шагов, получение результата, работа с таймером, турбо-дни и т.д.)&lt;/li&gt;
    &lt;li id=&quot;e0O7&quot;&gt;Тесты для экрана превью тренировки (построение активности дня, редактирование комментариев, обновление данных, сохранение тренировки и т.д.)&lt;/li&gt;
    &lt;li id=&quot;RH0k&quot;&gt;Тесты для управления статусом программы (получение статуса, запуск, сброс, синхронизация, журнал синхронизации, интеграционные тесты и т.д.)&lt;/li&gt;
    &lt;li id=&quot;Pgar&quot;&gt;Тесты для работы с дневником тренировок (базовые операции, комментарии, разрешение конфликтов, обработка ошибок, тренировки и т.д.)&lt;/li&gt;
    &lt;li id=&quot;z4UF&quot;&gt;Тесты для работы с инфопостами (синхронизация, парсинг, управление ресурсами, работа с YouTube, избранное и т.д.)&lt;/li&gt;
    &lt;li id=&quot;ersE&quot;&gt;Тесты для работы с прогрессом пользователя (базовые операции, синхронизация, работа с фотографиями, калькулятор прогресса и т.д.)&lt;/li&gt;
    &lt;li id=&quot;eDOK&quot;&gt;Тесты для работы с пользовательскими упражнениями&lt;/li&gt;
    &lt;li id=&quot;d5s5&quot;&gt;Тесты для настроек приложения&lt;/li&gt;
    &lt;li id=&quot;Le3o&quot;&gt;Тесты для воспроизведения звука&lt;/li&gt;
    &lt;li id=&quot;RR2L&quot;&gt;Тесты для обновления стран&lt;/li&gt;
    &lt;li id=&quot;qymy&quot;&gt;Тесты для обработки изображений&lt;/li&gt;
    &lt;li id=&quot;OzRF&quot;&gt;Тесты для управления ресурсами изображений&lt;/li&gt;
    &lt;li id=&quot;9zUI&quot;&gt;Тесты для работы с YouTube видео&lt;/li&gt;
    &lt;li id=&quot;F7bP&quot;&gt;Тесты для моделей тренировок&lt;/li&gt;
    &lt;li id=&quot;nwG3&quot;&gt;Тесты для моделей дней&lt;/li&gt;
    &lt;li id=&quot;MJUK&quot;&gt;Тесты для моделей прогресса&lt;/li&gt;
    &lt;li id=&quot;ZnOv&quot;&gt;Тесты для моделей пользователя&lt;/li&gt;
    &lt;li id=&quot;PcpC&quot;&gt;Тесты для моделей синхронизации&lt;/li&gt;
    &lt;li id=&quot;w7qA&quot;&gt;Тесты для других моделей (HomeScreenModel, ShareAppURL, TimerSound)&lt;/li&gt;
    &lt;li id=&quot;VgOS&quot;&gt;Тесты для создания программы тренировок (инициализация дня, работа с активностью дня, турбо-дни, пользовательские упражнения, типы выполнения и т.д.)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;mWFS&quot;&gt;У меня была цель &lt;strong&gt;протестировать всю возможную логику&lt;/strong&gt;, чтобы было проще поддерживать и развивать приложение в любое время: хоть сейчас, хоть через год, когда я скорее всего забуду большинство мелочей, влияющих на ту же синхронизацию данных с сервером и т.д.&lt;/p&gt;
  &lt;h2 id=&quot;XRO7&quot;&gt;Проблема первого релиза&lt;/h2&gt;
  &lt;p id=&quot;ODqc&quot;&gt;Про релиз сделали пост на сайте, с которым синхронизируется приложение, и в их телеграм-чате - там я случайно увидел пост одного из пользователей:&lt;/p&gt;
  &lt;figure id=&quot;t1Lj&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e8/e2/e8e20d50-4743-47e1-a404-b0cb7d1d6cb2.jpeg&quot; width=&quot;828&quot; /&gt;
    &lt;figcaption&gt;Всем привет, была у кого такая проблема в приложении на IOS?&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Gu1O&quot;&gt;Моя реакция, когда я увидел этот пост: 🤯🤦‍♂️&lt;/p&gt;
  &lt;p id=&quot;lYN4&quot;&gt;Для разработчика проблема очевидна: не получилось спарсить (обработать) какой-то из ответов сервера, а судя по скриншоту, это вообще происходит при первой авторизации, т.к. на главном экране ничего нет кроме алерта.&lt;/p&gt;
  &lt;p id=&quot;S7If&quot;&gt;Это был &lt;strong&gt;единственный момент, который я не протестировал&lt;/strong&gt;: зарегистрировать нового пользователя на сайте, а потом авторизоваться в iOS-приложении.&lt;/p&gt;
  &lt;p id=&quot;MEgF&quot;&gt;Т.е. я тестировал только существующих пользователей, и для них все работает корректно. Вот это поворот 🙂&lt;/p&gt;
  &lt;p id=&quot;YUin&quot;&gt;С пользователем мне повезло, он прислал мне в личку детали по воспроизведению проблемы и я сходу смог у себя это повторить.&lt;/p&gt;
  &lt;h3 id=&quot;lCfz&quot;&gt;Причина проблемы&lt;/h3&gt;
  &lt;p id=&quot;Luk6&quot;&gt;Проблема возникала при декодировании JSON ответа от сервера в структуру &lt;code&gt;CurrentRunResponse&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;afKY&quot;&gt;Я использовал стандартные методы декодирования, которые не могли корректно обработать поле с датой: метод &lt;code&gt;decodeIfPresent&lt;/code&gt; не мог правильно обработать случай, когда сервер возвращает &lt;code&gt;null&lt;/code&gt; в JSON в поле с датой.&lt;/p&gt;
  &lt;p id=&quot;NT1a&quot;&gt;При этом использовалась стратегия декодирования даты &lt;code&gt;iso8601&lt;/code&gt;, которая не поддерживала формат с дробными секундами, который иногда возвращал сервер.&lt;/p&gt;
  &lt;h3 id=&quot;TkO4&quot;&gt;Почему только для новых пользователей&lt;/h3&gt;
  &lt;p id=&quot;Jayg&quot;&gt;Проблема возникала только для новых пользователей, которые еще не начинали тренироваться на сайте/в приложении, потому что сервер возвращал разные данные в зависимости от того, есть ли для пользователя запись на сервере о дате начала тренировок.&lt;/p&gt;
  &lt;p id=&quot;zGVw&quot;&gt;У существующих пользователей, которые уже успели начать тренироваться до релиза этого приложения (в старом приложении или на сайте), проблема не возникала, потому что сервер возвращал валидные данные (не &lt;code&gt;null&lt;/code&gt;) - тут стандартные методы декодирования работали корректно, потому что все поля имели ожидаемые типы данных.&lt;/p&gt;
  &lt;h3 id=&quot;Q05N&quot;&gt;Исправление проблемы&lt;/h3&gt;
  &lt;p id=&quot;SKwL&quot;&gt;Пришлось создать специальные методы декодирования, которые корректно обрабатывают все возможные случаи от сервера, и конечно на этот раз добавил тесты для этой логики, код можно посмотреть в &lt;a href=&quot;https://github.com/easydev991/SwiftUI-SotkaApp/blob/main/SwiftUI-SotkaApp/Libraries/SWNetwork/Tests/SWNetworkTests/JSONDecoderExtensionTests.swift&quot; target=&quot;_blank&quot;&gt;гитхабе&lt;/a&gt;, а тут просто перечислю сценарии:&lt;/p&gt;
  &lt;ul id=&quot;lEJ5&quot;&gt;
    &lt;li id=&quot;2j1s&quot;&gt;Должен декодировать стандартный ISO8601 формат без дробных секунд&lt;/li&gt;
    &lt;li id=&quot;IH5r&quot;&gt;Должен декодировать ISO8601 формат с таймзоной +03:00&lt;/li&gt;
    &lt;li id=&quot;jAdD&quot;&gt;Должен декодировать ISO8601 формат с дробными секундами (одна цифра)&lt;/li&gt;
    &lt;li id=&quot;sWg8&quot;&gt;Должен декодировать ISO8601 формат с дробными секундами (две цифры)&lt;/li&gt;
    &lt;li id=&quot;urXQ&quot;&gt;Должен декодировать ISO8601 формат с дробными секундами (три цифры)&lt;/li&gt;
    &lt;li id=&quot;7qdy&quot;&gt;Должен декодировать ISO8601 формат с дробными секундами (четыре цифры)&lt;/li&gt;
    &lt;li id=&quot;KbV3&quot;&gt;Должен выбрасывать ошибку для невалидной даты&lt;/li&gt;
    &lt;li id=&quot;qiG2&quot;&gt;Должен выбрасывать ошибку для пустой строки&lt;/li&gt;
    &lt;li id=&quot;FpqM&quot;&gt;Должен выбрасывать ошибку для неправильного формата даты&lt;/li&gt;
    &lt;li id=&quot;5fC8&quot;&gt;Должен декодировать опциональное поле Date? с валидной датой&lt;/li&gt;
    &lt;li id=&quot;9cCz&quot;&gt;Должен декодировать опциональное поле Date? с null значением&lt;/li&gt;
    &lt;li id=&quot;ZSzQ&quot;&gt;Должен декодировать опциональное поле Date? с отсутствующим ключом&lt;/li&gt;
    &lt;li id=&quot;jgKe&quot;&gt;Должен декодировать server date time формат без часового пояса&lt;/li&gt;
    &lt;li id=&quot;ecn2&quot;&gt;Должен декодировать опциональное поле Date? с server date time форматом&lt;/li&gt;
    &lt;li id=&quot;e8HJ&quot;&gt;Должен декодировать минимальную дату&lt;/li&gt;
    &lt;li id=&quot;Mn06&quot;&gt;Должен декодировать минимальную дату в server date time формате&lt;/li&gt;
    &lt;li id=&quot;udHu&quot;&gt;Должен декодировать дату с максимальными дробными секундами&lt;/li&gt;
    &lt;li id=&quot;AE9x&quot;&gt;Должен декодировать ISO short date формат&lt;/li&gt;
    &lt;li id=&quot;gwMg&quot;&gt;Должен декодировать опциональное поле Date? с ISO short date форматом&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;be1h&quot;&gt;Тесты во втором релизе&lt;/h2&gt;
  &lt;figure id=&quot;AGT7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/49/f9/49f92614-0687-42d2-89e6-540277ef5424.png&quot; width=&quot;1410&quot; /&gt;
    &lt;figcaption&gt;Автотесты на момент второго релиза приложения&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;cd1Z&quot;&gt;Все тесты генерировал с помощью нейросети с использованием нескольких простых правил, о чем напишу в другой раз, а на исправление проблемы вместе с написанием новых тестов потратил около часа.&lt;/p&gt;
  &lt;p id=&quot;PzyP&quot;&gt;После этого оформил запрос на ускоренную проверку релиза и ... два дня разбирался с модераторами и отвечал на их вопросы 😁&lt;/p&gt;
  &lt;h2 id=&quot;19QT&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;Ap8L&quot;&gt;Тестируем не только основную логику приложения, но и декодирование моделей, особенно в пет-проектах ☝️&lt;/p&gt;
  &lt;h3 id=&quot;7EfF&quot;&gt;Бонус&lt;/h3&gt;
  &lt;p id=&quot;TOnO&quot;&gt;Год назад еще можно было сказать, что автотесты - это дорого, сложно, непонятно, непривычно и так далее. Потому что многие еще не умели пользоваться или не знали про режим агента у популярных IDE типа cursor/windsurf.&lt;/p&gt;
  &lt;p id=&quot;TEHu&quot;&gt;Сейчас нельзя сказать, что ты не умеешь писать тесты, или что это дорого и долго - подобные вещи будут просто наглым враньем.&lt;/p&gt;
  &lt;p id=&quot;YEHB&quot;&gt;Если не хочется разбираться в новых инструментах - нужно так и говорить прямо, что все, пора на пенсию, потому что слишком сложно сказать нейросети написать тесты для логики или для модели.&lt;/p&gt;
  &lt;p id=&quot;EBlm&quot;&gt;Предлагаю всем, кто еще не пробовал генерировать тесты с нейросетью - начните это делать наконец-то, чтобы не ковыряться во внезапных багах и не ломать уже существующий функционал наших классных приложений 😉&lt;/p&gt;
  &lt;p id=&quot;4Nmw&quot;&gt;Если кто-то переживает, что у нас станет меньше работы при снижении количества багов, то это заблуждение - работы меньше не станет, как и багов, зато стабильности будет больше 👍&lt;/p&gt;

</content></entry><entry><id>easy_dev991:d_eh9E72Sb4</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/d_eh9E72Sb4?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>134. ViewThatFits + @State</title><published>2025-11-23T12:40:36.252Z</published><updated>2025-11-23T12:40:36.252Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/7d/9d/7d9d2cc9-70f6-4537-9724-7c7881a20f22.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/71/b6/71b6ff46-c0ba-4e08-b536-857d855d6f5d.gif&quot;&gt;Недавно чинил любопытный баг, который оказался очень простым на деле, но нейросеть исправить его не смогла</summary><content type="html">
  &lt;p id=&quot;z9xy&quot;&gt;Недавно чинил любопытный баг, который оказался очень простым на деле, но нейросеть исправить его не смогла &lt;/p&gt;
  &lt;h2 id=&quot;asTg&quot;&gt;Постановка и демо&lt;/h2&gt;
  &lt;p id=&quot;X7ub&quot;&gt;На главном экране в приложении 2 варианта UI: для широкого экрана (например, для iPad) и для узкого экрана (для iPhone). На этом экране можно открыть модальное окно с важной функциональностью, а для поддержки двух вариантов UI используется &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/ViewThatFits&quot; target=&quot;_blank&quot;&gt;ViewThatFits&lt;/a&gt; (удобная штука для iOS 16+), но при повороте экрана на айфоне эта модалка закрывается сама:&lt;/p&gt;
  &lt;figure id=&quot;sWDP&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/71/b6/71b6ff46-c0ba-4e08-b536-857d855d6f5d.gif&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;Демонстрация бага: неприятно будет, если модалка сама закроется при повороте экрана&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;Ofcv&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ff/7c/ff7c8507-aa44-4860-936d-e41642366b17.gif&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;Исправленный вариант: модалка не закрывается при повороте экрана, все ок.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;vsyY&quot;&gt;Причина бага&lt;/h2&gt;
  &lt;p id=&quot;copG&quot;&gt;Проблема возникает из-за хранения &lt;code&gt;@State&lt;/code&gt;-свойства для модального окна внутри дочерней вьюхи, которая используется в обоих вариантах UI, а при повороте экрана &lt;code&gt;ViewThatFits&lt;/code&gt; приводит к частичному сбросу состояний дочерних вьюх, которые исчезают.&lt;/p&gt;
  &lt;p id=&quot;DWb1&quot;&gt;&lt;strong&gt;Почему сброс частичный?&lt;/strong&gt; Потому что на девайсе при повороте экрана модалка закрывалась, а при повороте обратно - сама появлялась, но не каждый раз &lt;/p&gt;
  &lt;p id=&quot;tKuI&quot;&gt;Код для этого случая можно посмотреть в &lt;a href=&quot;https://github.com/easydev991/Shared-SwiftUI-Content/blob/main/Shared%20SwiftUI%20Content/134_SheetRotationBugDemo.swift&quot; target=&quot;_blank&quot;&gt;гитхабе&lt;/a&gt;.&lt;/p&gt;

</content></entry><entry><id>easy_dev991:jX3syALAaMi</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/jX3syALAaMi?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>Баг в тулбаре iOS 26</title><published>2025-11-09T13:26:00.199Z</published><updated>2025-11-09T13:26:00.199Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5c/db/5cdb1d2a-6e59-4e94-bfc9-b8eaefff07b0.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/c8/dd/c8dd0f57-b590-4928-b531-428cd7a3e3f0.png&quot;&gt;Если нужно скрыть кнопку в тулбаре, то нужно закрывать ее проверкой на if/else, а применять opacity больше нельзя - в iOS 26 будет такая фиговина как на скриншоте.</summary><content type="html">
  &lt;figure id=&quot;jbQz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c8/dd/c8dd0f57-b590-4928-b531-428cd7a3e3f0.png&quot; width=&quot;1170&quot; /&gt;
    &lt;figcaption&gt;iOS 18.6&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;CJra&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/61/04/61042124-a5ea-466e-8daa-57c1524f2694.png&quot; width=&quot;1206&quot; /&gt;
    &lt;figcaption&gt;iOS 26.0&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pq5o&quot;&gt;Если нужно скрыть кнопку в тулбаре, то нужно закрывать ее проверкой на &lt;code&gt;if/else&lt;/code&gt;, а применять &lt;code&gt;opacity&lt;/code&gt; больше нельзя - в &lt;code&gt;iOS 26&lt;/code&gt; будет такая фиговина как на скриншоте.&lt;/p&gt;

</content></entry><entry><id>easy_dev991:ViOqnhds2XA</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/ViOqnhds2XA?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>133. Чиним баг навигации в iOS 26</title><published>2025-10-19T10:06:48.910Z</published><updated>2025-10-19T10:06:48.910Z</updated><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/5b/dc/5bdc10d2-6bb8-4fa8-94f9-2fcf4363c473.gif&quot;&gt;Недавно заметил в симуляторе iOS 26 сломавшуюся навигацию в нескольких экранах пет-проекта. Расскажу, в чем там было дело и как это исправляется.</summary><content type="html">
  &lt;p id=&quot;2fXi&quot;&gt;Недавно заметил в симуляторе iOS 26 сломавшуюся навигацию в нескольких экранах пет-проекта. Расскажу, в чем там было дело и как это исправляется.&lt;/p&gt;
  &lt;h2 id=&quot;MzM8&quot;&gt;Демонстрация проблемы&lt;/h2&gt;
  &lt;figure id=&quot;jqj0&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5b/dc/5bdc10d2-6bb8-4fa8-94f9-2fcf4363c473.gif&quot; width=&quot;500&quot; /&gt;
    &lt;figcaption&gt;Баг №1: взаимодействие с menu закрывает экран&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;h8wG&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/09/25/09258a97-9a75-4eb5-8ad2-2cdf31d96b35.gif&quot; width=&quot;500&quot; /&gt;
    &lt;figcaption&gt;Баг №2: взаимодействие с confirmationDialog закрывает экран&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;fduY&quot;&gt;Как должно быть&lt;/h2&gt;
  &lt;figure id=&quot;uNJN&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/ed/bded5263-a008-46ce-a103-a0ca8ee770f0.gif&quot; width=&quot;512&quot; /&gt;
    &lt;figcaption&gt;iOS 18.6, все работает корректно: и menu, и confirmationDialog&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;vnVy&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3b/37/3b3739ca-022e-4554-a500-5d721bc960fb.gif&quot; width=&quot;502&quot; /&gt;
    &lt;figcaption&gt;iOS 26, все работает корректно: и menu, и confirmationDialog (это видео записано после исправления &amp;quot;ошибки&amp;quot;)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;5hBP&quot;&gt;Причина неправильного поведения для пользователей с iOS 26&lt;/h2&gt;
  &lt;p id=&quot;ridR&quot;&gt;В проекте используется &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/NavigationView&quot; target=&quot;_blank&quot;&gt;NavigationView&lt;/a&gt;, который все еще отлично работает сам по себе даже на iOS 26, а проблема возникает при отображении &lt;code&gt;NavigationView&lt;/code&gt; внутри модального окна (&lt;code&gt;sheet&lt;/code&gt;).&lt;/p&gt;
  &lt;h2 id=&quot;3YRm&quot;&gt;Варианты исправления&lt;/h2&gt;
  &lt;h3 id=&quot;oXxN&quot;&gt;Вариант 1: самый быстрый и &amp;quot;очевидный&amp;quot;&lt;/h3&gt;
  &lt;p id=&quot;EGIa&quot;&gt;Можно заменить в проекте все места, где используется &lt;code&gt;NavigationView&lt;/code&gt; на &lt;code&gt;&lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/NavigationStack&quot; target=&quot;_blank&quot;&gt;NavigationStack&lt;/a&gt;&lt;/code&gt; ... и поднять минимальную версию iOS с 15 до 16. Таким образом можно сохранить исходное поведение и на старых версиях iOS, и на новых, но придется отказаться от пользователей на iOS 15*.&lt;/p&gt;
  &lt;p id=&quot;9y4w&quot;&gt;Вы можете справедливо заметить, что на iOS &amp;lt; 16 уже почти никого нет, и тем не менее там чуть больше 4% пользователей iPhone. Они не хотят или не могут обновиться, но ведь в нашем случае причина бага не в их девайсах, а где-то под капотом iOS 26, что вообще никак не касается других версий системы.&lt;/p&gt;
  &lt;h3 id=&quot;IPmd&quot;&gt;Вариант 2: менее быстрый&lt;/h3&gt;
  &lt;p id=&quot;v4NN&quot;&gt;Можно сделать то же самое, что в варианте 1, но без поднятия минимальной версии iOS - достаточно сделать свою обертку для навигации, где внутри будет проверка на iOS 16: если возможно, используем &lt;code&gt;NavigationStack&lt;/code&gt;, а если нет, то &lt;code&gt;NavigationView&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;Tke2&quot;&gt;Такой вариант я хотел применить еще на релизе iOS 18, но в то время это привело к крашам как раз в сценарии открытия &lt;code&gt;NavigationStack&lt;/code&gt; внутри модального окна, причем именно на iOS 16+.&lt;/p&gt;
  &lt;h3 id=&quot;roas&quot;&gt;Вариант 3: поменять способ открытия родительского окна&lt;/h3&gt;
  &lt;p id=&quot;92vM&quot;&gt;Можно открывать экран с &amp;quot;площадкой&amp;quot; не в модальном окне, а в том же стеке навигации, что не требует изменения минимальной версии iOS и никаких проверок на версию iOS вообще не нужно добавлять, но немного поменяется подход к открытию некоторых экранов ... и все продолжит работать даже на iOS 26+, чудеса!&lt;/p&gt;
  &lt;h2 id=&quot;x0zX&quot;&gt;Что я сделаю&lt;/h2&gt;
  &lt;p id=&quot;kWvE&quot;&gt;Скорее всего, буду выбирать между первыми двумя вариантами. Все же это нишевое приложение, и там очень мало пользователей, и даже если кто-то сидит в приложении на iOS 15, то все основные функции там останутся доступными еще очень долго, пока на сервере никаких глобальных изменений не сделают (а там очень редко что-то меняется).&lt;/p&gt;
  &lt;h2 id=&quot;hRbd&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;Kyqt&quot;&gt;Жидкое стекло принесло нам немало багов, и в том числе задело исправно работавшую навигацию на старом добром NavigationView.&lt;/p&gt;
  &lt;p id=&quot;KC52&quot;&gt;Главное, что все еще есть решения таких проблем, в том числе и обходные.&lt;/p&gt;

</content></entry><entry><id>easy_dev991:Pereklyuchatel-dlya-podderzhki-temnoj-temy-09-21</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/Pereklyuchatel-dlya-podderzhki-temnoj-temy-09-21?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>132. Переключатель для поддержки темной темы</title><published>2025-10-19T10:09:27.863Z</published><updated>2025-10-19T10:09:27.863Z</updated><summary type="html">Возьмем рабочий проект, который поддерживает только светлую тему, и добавим туда возможность включить/выключить поддержку темной темы - не пикер темы, а именно переключатель для поддержки фичи.</summary><content type="html">
  &lt;p id=&quot;lLo1&quot;&gt;Возьмем рабочий проект, который поддерживает только светлую тему, и добавим туда возможность включить/выключить поддержку темной темы - не пикер темы, а именно переключатель для поддержки фичи.&lt;/p&gt;
  &lt;h3 id=&quot;Больше-деталей&quot;&gt;Больше деталей&lt;/h3&gt;
  &lt;p id=&quot;a5Z0&quot;&gt;Есть iOS-приложение с жизненным циклом на &lt;code&gt;UIKit&lt;/code&gt; (&lt;code&gt;AppDelegate&lt;/code&gt;), которое никто раньше не адаптировал для темной темы.&lt;/p&gt;
  &lt;p id=&quot;D84D&quot;&gt;В &lt;code&gt;Info.plist&lt;/code&gt; есть ключ &lt;code&gt;UIUserInterfaceStyle&lt;/code&gt; со значением &lt;code&gt;Light&lt;/code&gt;, что по умолчанию включает всегда светлую тему, даже если на девайсе активна темная тема.&lt;/p&gt;
  &lt;p id=&quot;NWfs&quot;&gt;Пользователи давно просят добавить поддержку темной темы, и пришло время это реализовать. Процесс займет неопределенное время, в ходе которого будет сделано несколько сборок для тестирования.&lt;/p&gt;
  &lt;p id=&quot;cECk&quot;&gt;Чтобы было удобно переключаться между светлой и темной темами, добавим переключатель в дебаг-меню для тестовых сборок, куда нет доступа у пользователей - вот об этом и будет статья.&lt;/p&gt;
  &lt;h3 id=&quot;План&quot;&gt;План&lt;/h3&gt;
  &lt;ol id=&quot;HcCC&quot;&gt;
    &lt;li id=&quot;4C9i&quot;&gt;Удаляем из &lt;code&gt;Info.plist&lt;/code&gt; неактуальный ключ &lt;code&gt;UIUserInterfaceStyle&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;pCKG&quot;&gt;Добавляем тоггл в дебаг-меню&lt;/li&gt;
    &lt;li id=&quot;mLj7&quot;&gt;Складываем логику в отдельный сервис&lt;/li&gt;
    &lt;li id=&quot;rIDO&quot;&gt;Дорабатываем &lt;code&gt;AppDelegate&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;PEpD&quot;&gt;Первые 2 пункта не очень интересные: удалить ключ из &lt;code&gt;Info.plist&lt;/code&gt; очень легко, а добавить тоггл в дебаг-меню - по-разному, в зависимости от проекта.&lt;/p&gt;
  &lt;p id=&quot;ANbf&quot;&gt;В нашем случае добавить тоггл было легко, поэтому перехожу к третьему шагу.&lt;/p&gt;
  &lt;h4 id=&quot;Складываем-логику-в-отдельный-сервис&quot;&gt;Складываем логику в отдельный сервис&lt;/h4&gt;
  &lt;p id=&quot;JGeK&quot;&gt;Управление поддержкой темной темы - это новая фича, которую хочется покрыть тестами, поэтому сделаем небольшой сервис, который будет заниматься этой логикой, и позже напишем тесты.&lt;/p&gt;
  &lt;p id=&quot;PQNE&quot;&gt;Для управления поддержкой темной темы нам нужна ссылка на &lt;code&gt;window&lt;/code&gt;, которая в нашем случае есть в &lt;code&gt;AppDelegate&lt;/code&gt;, и значение тоггла из дебаг-меню:&lt;/p&gt;
  &lt;pre id=&quot;Z3lS&quot;&gt;import UIKit

enum DarkModeSwitcher {
  @discardableResult
  static func applyIfNeeded(window: UIWindow?, isDarkModeEnabled: Bool) -&amp;gt; Bool {
    guard let window else {
      return false
    }
    let desired: UIUserInterfaceStyle = isDarkModeEnabled ? .unspecified : .light
    guard window.overrideUserInterfaceStyle != desired else {
      return false
    }
    window.overrideUserInterfaceStyle = desired
    return true
  }
}
&lt;/pre&gt;
  &lt;p id=&quot;pgV6&quot;&gt;Вот сценарии для юнит-тестов:&lt;/p&gt;
  &lt;ol id=&quot;XPrX&quot;&gt;
    &lt;li id=&quot;7HFA&quot;&gt;Включение &lt;code&gt;darkMode&lt;/code&gt; включает &lt;code&gt;.unspecified&lt;/code&gt;, если ранее было &lt;code&gt;.light&lt;/code&gt; и возвращает &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;l14k&quot;&gt;Отключение &lt;code&gt;darkMode&lt;/code&gt; устанавливает &lt;code&gt;.light&lt;/code&gt;, если ранее было &lt;code&gt;.unspecified&lt;/code&gt; и возвращает &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;mAKe&quot;&gt;Повторный вызов с тем же значением не меняет стиль и возвращает &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;yzOK&quot;&gt;Вызов с &lt;code&gt;nil&lt;/code&gt; окном безопасен и возвращает &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h4 id=&quot;Дорабатываем-AppDelegate&quot;&gt;Дорабатываем AppDelegate&lt;/h4&gt;
  &lt;p id=&quot;PXbp&quot;&gt;Первым делом дорабатываем метод &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:didfinishlaunchingwithoptions:)&quot; target=&quot;_blank&quot;&gt;application(_:didFinishLaunchingWithOptions:)&lt;/a&gt;, который вызывается при запуске приложения:&lt;/p&gt;
  &lt;pre id=&quot;cGRM&quot;&gt;func application(
 _ application: UIApplication,
 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -&amp;gt; Bool {
 // ...
 DarkModeSwitcher.applyIfNeeded(
  window: window,
  isDarkModeEnabled: isDarkModeEnabled // &amp;lt;- ссылка на тоггл в дебаг-меню
 )
 // ...
 return true
}
&lt;/pre&gt;
  &lt;p id=&quot;f52l&quot;&gt;Чтобы тестировать было удобнее, вызовем &lt;code&gt;DarkModeSwitcher&lt;/code&gt; еще и при &amp;quot;разворачивании&amp;quot; приложения, т.е. когда оно переходит в активное состояние - таким образом можно будет сразу после переключения тоггла в дебаг-меню свернуть и развернуть приложение и сразу же увидеть темную тему (если она активна на устройстве):&lt;/p&gt;
  &lt;pre id=&quot;IbrE&quot;&gt;func applicationDidBecomeActive(_: UIApplication) {
  DarkModeSwitcher.applyIfNeeded(
    window: window,
    isDarkModeEnabled: isDarkModeEnabled
  )
}
&lt;/pre&gt;
  &lt;p id=&quot;qQhO&quot;&gt;Готово! Теперь можно продолжать процесс переезда на темную тему и не волноваться о том, что пользователи могут увидеть промежуточный результат, ну и не &amp;quot;солить&amp;quot; код в отдельной ветке.&lt;/p&gt;
  &lt;h3 id=&quot;Бонус:-SwiftUI&quot;&gt;Бонус: SwiftUI&lt;/h3&gt;
  &lt;p id=&quot;QxDS&quot;&gt;Как уже можно было догадаться, если жизненный цикл вашего приложения использует SwiftUI, то достаточно модификатора &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/preferredcolorscheme(_:)&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;preferredColorScheme&lt;/code&gt;&lt;/a&gt; - в него нужно передать значение тоггла из дебаг-меню и ... готово!&lt;/p&gt;
  &lt;h3 id=&quot;Заключение&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;Wic8&quot;&gt;Код сервиса-переключателя для темной темы вместе с тестами можно посмотреть в &lt;a href=&quot;https://gist.github.com/easydev991/184cf0434fa6cdd63517f3d490f091eb&quot; target=&quot;_blank&quot;&gt;гитхабе&lt;/a&gt;. Если интересно почитать про план адаптации темной темы в этом приложении с нуля, ставьте лайк 👍&lt;/p&gt;

</content></entry><entry><id>easy_dev991:131-Onbording-v-avtotesty-na-Python-09-06</id><link rel="alternate" type="text/html" href="https://teletype.in/@easy_dev991/131-Onbording-v-avtotesty-na-Python-09-06?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=easy_dev991"></link><title>131. Онбординг в автотесты на Python</title><published>2025-10-19T10:18:55.247Z</published><updated>2025-10-19T10:18:55.247Z</updated><summary type="html">Вчера я организовал себе онбординг в автотесты на Python в рабочем проекте - расскажу кому это может пригодиться и как все прошло.</summary><content type="html">
  &lt;p id=&quot;pc9N&quot;&gt;Вчера я организовал себе онбординг в автотесты на &lt;code&gt;Python&lt;/code&gt; в рабочем проекте - расскажу кому это может пригодиться и как все прошло.&lt;/p&gt;
  &lt;h3 id=&quot;Кому-будет-полезно&quot;&gt;Кому будет полезно&lt;/h3&gt;
  &lt;ul id=&quot;iU8P&quot;&gt;
    &lt;li id=&quot;EO85&quot;&gt;Новичкам, кто хочет научиться делать автотесты на &lt;code&gt;Python&lt;/code&gt; (популярные языки мало отличаются в этом плане - дальше в статье все расскажу)&lt;/li&gt;
    &lt;li id=&quot;Nz0l&quot;&gt;Действующим специалистам в этой теме - если ты уже пишешь автотесты самостоятельно, то эта статья может быть особенно полезной&lt;/li&gt;
    &lt;li id=&quot;HNib&quot;&gt;Менеджерам, которые хотят снизить нагрузку на ручных тестировщиков (автотесты работают намного быстрее, чем живые люди - это факт)&lt;/li&gt;
    &lt;li id=&quot;IcVy&quot;&gt;Всем желающим, кто умеет читать и нажимать на кнопки в этом нашем &amp;quot;айти&amp;quot;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Прежде-чем-начинать&quot;&gt;Прежде чем начинать&lt;/h3&gt;
  &lt;p id=&quot;4vqU&quot;&gt;Ключевые моменты, влияющие на успех при изучении автотестов:&lt;/p&gt;
  &lt;ol id=&quot;KNNA&quot;&gt;
    &lt;li id=&quot;g3OD&quot;&gt;Мотивация - если не хочется узнавать новое и учиться, можно закрывать статью&lt;/li&gt;
    &lt;li id=&quot;0YAk&quot;&gt;Отсутствие страхов: если страшно, если есть сомнения типа &amp;quot;а вдруг не получится&amp;quot;, &amp;quot;а что если нужно уметь программировать&amp;quot;, &amp;quot;да я не знаю математику&amp;quot;, &amp;quot;да я не знаю Python&amp;quot; - лучше сразу закрыть статью, чтобы не страдать&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;Мой-опыт-в-Python&quot;&gt;Мой опыт в Python&lt;/h3&gt;
  &lt;p id=&quot;JcJU&quot;&gt;В 2022 году я изучал &lt;code&gt;Python&lt;/code&gt; в каком-то бесплатном мобильном приложении, потратил на это около 3-4 дней и не стал углубляться в детали, т.к. не использую этот язык в работе никогда, и вообще не использую его нигде.&lt;/p&gt;
  &lt;p id=&quot;HKWY&quot;&gt;Просто было любопытно посмотреть на один из самых популярных языков программирования, вот и все.&lt;/p&gt;
  &lt;h3 id=&quot;Как-я-пришел-к-автотестам-в-рабочем-проекте&quot;&gt;Как я пришел к автотестам в рабочем проекте&lt;/h3&gt;
  &lt;p id=&quot;S2eX&quot;&gt;Сначала я начал активно писать (генерировать) автотесты в iOS-приложении, и после пары сотен тестов на &lt;code&gt;Swift&lt;/code&gt; поинтересовался у наших тестировщиков, как они пишут свои тесты.&lt;/p&gt;
  &lt;p id=&quot;vdHd&quot;&gt;Свои тесты я пишу почти целиком с использованием курсора, о котором я уже писал &lt;a href=&quot;/125-Otzyv-na-Cursor-v-2025-godu-06-29&quot;&gt;тут&lt;/a&gt; и &lt;a href=&quot;/127-Auto-Run-Mode-cursor-07-12&quot;&gt;тут&lt;/a&gt;, поэтому предложил поделиться опытом, чтобы упростить их работу.&lt;/p&gt;
  &lt;p id=&quot;VIvA&quot;&gt;Оказалось, что ребята не пользуются курсором, и вообще автотестам нужно учиться, а это небыстро и т.д.&lt;/p&gt;
  &lt;p id=&quot;cuVr&quot;&gt;Я решил разобраться в этой теме.&lt;/p&gt;
  &lt;h4 id=&quot;Шаг-1&quot;&gt;Шаг 1&lt;/h4&gt;
  &lt;p id=&quot;UsD0&quot;&gt;Первым делом запросил &lt;strong&gt;доступ к репозиториям&lt;/strong&gt; с автотестами. Репозиторий - место, где хранится код проекта. Например, &lt;a href=&quot;https://github.com/easydev991?tab=repositories&quot; target=&quot;_blank&quot;&gt;тут список&lt;/a&gt; моих репозиториев в гитхабе.&lt;/p&gt;
  &lt;h4 id=&quot;Шаг-2&quot;&gt;Шаг 2&lt;/h4&gt;
  &lt;p id=&quot;LeIr&quot;&gt;Дальше нужно их клонировать, т.е. &lt;strong&gt;скачать себе на комп&lt;/strong&gt; - для этого я использую &lt;a href=&quot;https://desktop.github.com/download/&quot; target=&quot;_blank&quot;&gt;GitHub Desktop&lt;/a&gt;, но можно пользоваться любыми инструментами, которые вам нравятся, даже просто командами в терминале (я их не заучивал, поэтому не использую).&lt;/p&gt;
  &lt;h4 id=&quot;Шаг-3&quot;&gt;Шаг 3&lt;/h4&gt;
  &lt;p id=&quot;gZiL&quot;&gt;Когда все загружено, осталось &lt;strong&gt;открыть проект в курсоре&lt;/strong&gt; и приступать к делу - вот тут и начинается самое интересное и веселое.&lt;/p&gt;
  &lt;h3 id=&quot;Разбираемся-вместе-с-курсором&quot;&gt;Разбираемся вместе с курсором&lt;/h3&gt;
  &lt;p id=&quot;7xip&quot;&gt;Открываем проект в курсоре и что мы видим? Как всегда - список папок с файлами, и просто файлы.&lt;/p&gt;
  &lt;p id=&quot;r6IW&quot;&gt;Если в проекте есть документация, хотя бы &lt;code&gt;README.md&lt;/code&gt;, нужно обязательно туда заглянуть - вдруг документ не забросили и поддерживают в актуальном состоянии. Примеры ридми-файлов: &lt;a href=&quot;https://github.com/easydev991/WrappingHStackLimited/blob/main/README.md&quot; target=&quot;_blank&quot;&gt;первый&lt;/a&gt;, &lt;a href=&quot;https://github.com/easydev991/SwiftUI-Days/blob/main/README.md&quot; target=&quot;_blank&quot;&gt;второй&lt;/a&gt;, &lt;a href=&quot;https://github.com/easydev991/SwiftUI-WorkoutApp/blob/main/README.md&quot; target=&quot;_blank&quot;&gt;третий&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;2QPS&quot;&gt;Поскольку я вообще не представлял, как все работает в этом проекте, то я сказал курсору:&lt;/p&gt;
  &lt;blockquote id=&quot;C0ZA&quot;&gt;изучи этот проект и скажи, нужно ли мне что-то установить для работы с этим проектом и для написания/запуска тестов, или можно сразу писать тесты и запускать их? если можешь, попробуй запустить тесты&lt;/blockquote&gt;
  &lt;p id=&quot;kxmF&quot;&gt;Курсор подумал, что-то сделал, и &lt;strong&gt;через пару-тройку&lt;/strong&gt; минут выдал ответ:&lt;/p&gt;
  &lt;p id=&quot;1CJv&quot;&gt;&lt;em&gt;Я подготовил окружение и проверил запуск части тестов.&lt;/em&gt;&lt;/p&gt;
  &lt;ul id=&quot;BPzN&quot;&gt;
    &lt;li id=&quot;xQmB&quot;&gt;&lt;em&gt;Что нужно установить/сделать:&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;AbC4&quot;&gt;&lt;em&gt;Установить Python 3.11+.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;nuOv&quot;&gt;&lt;em&gt;Создать venv и поставить зависимости из requirements.txt (там свой PyPI-индекс, работает).&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;Csf0&quot;&gt;&lt;em&gt;Создать /.env.local рядом с config.py с переменными: BASE_URL, USER_PASSWORD, TESTERDAV_PASSWORD.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;DFfy&quot;&gt;&lt;em&gt;Сгенерировать tests/_generated/actions.py.&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;bwFy&quot;&gt;&lt;em&gt;Я уже выполнил:&lt;/em&gt;&lt;/p&gt;
  &lt;ul id=&quot;8ALp&quot;&gt;
    &lt;li id=&quot;mJ89&quot;&gt;&lt;em&gt;Установил Python 3.11 через Homebrew.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;Z6Cj&quot;&gt;&lt;em&gt;Создал venv на 3.11, установил зависимости.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;tA7E&quot;&gt;&lt;em&gt;Сгенерировал tests/_generated/actions.py.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;npVi&quot;&gt;&lt;em&gt;Скопировал .env в .env.local (для локального прогона; значения лучше заменить на корректные).&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;UIbE&quot;&gt;&lt;em&gt;Запустил безопасный тест без авторизации: 3 passed, 1 deselected.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;SIZ5&quot;&gt;&lt;em&gt;Тест с логином упал из‑за отсутствия валидных данных для логина (пустой токен).&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;SJ66&quot;&gt;&lt;em&gt;Можно ли сразу писать и запускать тесты?&lt;/em&gt;&lt;/p&gt;
  &lt;ul id=&quot;QLhG&quot;&gt;
    &lt;li id=&quot;glzS&quot;&gt;&lt;em&gt;Да, если у вас установлен Python 3.11+ и заполнен /.env.local. Тесты без авторизации уже успешно проходят. Для тестов с авторизацией нужно корректно заполнить логин/пароль в /.env.local.&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;BP3h&quot;&gt;&lt;em&gt;1-2 предложения о прогрессе:&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Az5u&quot;&gt;&lt;em&gt;Я собрал окружение на Python 3.11, поставил зависимости, сгенерировал actions и запустил тесты: без авторизации — успех, авторизационные падают из‑за отсутствия валидных учётных данных. Готов подключить реальные значения в .env.local и прогнать полный набор.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;0Kch&quot;&gt;Знаю ли я про окружения в &lt;code&gt;Python&lt;/code&gt; - совсем нет. Как их настраивать? Без понятия.&lt;/p&gt;
  &lt;p id=&quot;jFGp&quot;&gt;При этом у меня в &lt;code&gt;GitHub Desktop&lt;/code&gt; отобразилось несколько сотен новых файлов, что меня немного напрягло, и я написал в курсор:&lt;/p&gt;
  &lt;blockquote id=&quot;1418&quot;&gt;проверь список изменений, которые мы не закоммитили - нужно ли что-то из этого добавить в гитигнор, чтобы не ломать работу другим коллегам в этом репозитории? и нужно ли нам оставлять тесты, которые ты создал в @actions/ , или это были просто пробные тесты и их можно удалить? если можно удалить, сделай это&lt;/blockquote&gt;
  &lt;p id=&quot;AtLe&quot;&gt;Ответ курсора через 20 секунд:&lt;/p&gt;
  &lt;ul id=&quot;wiuh&quot;&gt;
    &lt;li id=&quot;t7vd&quot;&gt;&lt;em&gt;Проверил незакоммиченные изменения: изменены только .gitignore и README.md. Новых тестовых файлов я не создавал.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;labd&quot;&gt;&lt;em&gt;Добавил в .gitignore: /.venv/, **/__pycache__/, .DS_Store. Это исключит локальные артефакты macOS и Python, чтобы не мешали коллегам.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;hg8n&quot;&gt;&lt;em&gt;tests/_generated/actions.py уже игнорируется (правило /tests/_generated/* есть), оставляем как есть.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;Gj6Q&quot;&gt;&lt;em&gt;Про «пробные тесты» в tests/api/actions/: новых не добавлял — папка уже была в проекте; удалять нечего.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;cBCY&quot;&gt;&lt;em&gt;Итого:В гит добавлять больше ничего не нужно, кроме уже внесённого .gitignore и обновлённого README.md.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;uQQa&quot;&gt;&lt;em&gt;Тесты в @actions/ не мной созданы и удалять их не нужно.&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;bKQy&quot;&gt;Все новые файлы исчезли из списка в гите после обновления &lt;code&gt;gitignore&lt;/code&gt; курсором, все четко ✅&lt;/p&gt;
  &lt;p id=&quot;vSMX&quot;&gt;Дальше я сказал курсору создать &lt;code&gt;Makefile&lt;/code&gt; с командами для настройки проекта и запуска тестов, чтобы коллегам в будущем было проще все настроить одной командой на &lt;code&gt;macOS&lt;/code&gt;/&lt;code&gt;linux&lt;/code&gt;, и проверить эти команды на работоспособность.&lt;/p&gt;
  &lt;p id=&quot;Y45y&quot;&gt;Про &lt;code&gt;Makefile&lt;/code&gt; я писал &lt;a href=&quot;/122-Avtomatiziruem-rutinu-v-iOS-proekte-06-05&quot;&gt;тут&lt;/a&gt; и &lt;a href=&quot;https://t.me/easy_dev991/221&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;QU0N&quot;&gt;Примерно через 25 минут, когда я сам проверил команды из &lt;code&gt;Makefile&lt;/code&gt; и убедился, что все работает, я попросил курсор написать новый автотест по аналогии с существующими - он написал тест и запустил, все ок ✅&lt;/p&gt;
  &lt;h4 id=&quot;Обновляем-документацию&quot;&gt;Обновляем документацию&lt;/h4&gt;
  &lt;p id=&quot;JTBr&quot;&gt;&lt;u&gt;Пишу курсору:&lt;/u&gt;&lt;/p&gt;
  &lt;blockquote id=&quot;N2qA&quot;&gt;теперь обнови ридми, чтобы там было краткое и понятное объяснение как запускать тесты и через стандартные команды (pytest), и через make-команды, и приведи по одному примеру для каждого варианта запуска тестов&lt;/blockquote&gt;
  &lt;p id=&quot;6Vb3&quot;&gt;Он обновил ридми, я внес пару корректировок, а потом &lt;u&gt;задал пару уточняющих вопросов&lt;/u&gt; про окружения в &lt;code&gt;Python&lt;/code&gt;:&lt;/p&gt;
  &lt;blockquote id=&quot;PuhR&quot;&gt;получается, что для запуска тестов через команду pytest не нужно предварительно активировать окружение? или я неправильно понял? если нужно активировать окружение, то прямо в примере запуска тестов с pytest напиши это. а то сейчас путаница возникает, как будто при использовании pytest не нужно делать предварительно source .venv/bin/activate&lt;/blockquote&gt;
  &lt;p id=&quot;XsTa&quot;&gt;&lt;u&gt;Ответ:&lt;/u&gt;&lt;/p&gt;
  &lt;p id=&quot;5Ajf&quot;&gt;&lt;em&gt;Я обновлю раздел в README, чтобы в примере для pytest явно сначала шёл source .venv/bin/activate, а альтернативный вариант без активации был выделен отдельно, чтобы не было путаницы. Затем вернусь с кратким резюме.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;55YW&quot;&gt;&lt;em&gt;Уточнил формулировку в README:&lt;/em&gt;&lt;/p&gt;
  &lt;ul id=&quot;9w8v&quot;&gt;
    &lt;li id=&quot;gwKS&quot;&gt;&lt;em&gt;Для обычного вызова pytest через оболочку нужно активировать окружение. В примере теперь явно:&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;O5Sv&quot;&gt;&lt;em&gt;сначала source .venv/bin/activate&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;jvFv&quot;&gt;&lt;em&gt;затем pytest tests/... -q&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;wQSh&quot;&gt;&lt;em&gt;Отдельной строкой оставил альтернативу без активации: ./.venv/bin/pytest ...&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;1DRF&quot;&gt;&lt;em&gt;Так путаницы не будет: либо source + pytest, либо прямой вызов бинарника из .venv.&lt;/em&gt;&lt;/p&gt;
  &lt;blockquote id=&quot;hyty&quot;&gt;проверь, есть ли в проекте какой-то пример оформления тестов? или инструкция по написанию тестов? я хочу добавить такой раздел в ридми, чтобы проще было начинать работать новым членам команды&lt;/blockquote&gt;
  &lt;p id=&quot;98oF&quot;&gt;В результате в ридми появился разделы &amp;quot;Запуск тестов на macOS&amp;quot; и &amp;quot;Как писать тесты&amp;quot; ✅&lt;/p&gt;
  &lt;h4 id=&quot;Выводы-по-этому-проекту&quot;&gt;Выводы по этому проекту&lt;/h4&gt;
  &lt;ol id=&quot;XBkU&quot;&gt;
    &lt;li id=&quot;fwV7&quot;&gt;Я потратил около 30 минут на полную настройку проекта на своем ноуте, но в следующий раз другим ребятам (у которых &lt;code&gt;macOS&lt;/code&gt;/&lt;code&gt;linux&lt;/code&gt;) это можно будет сделать за пару минут, просто выполнив одну команду в терминале&lt;/li&gt;
    &lt;li id=&quot;lGYd&quot;&gt;Разобрался как запускать существующие тесты и увидел как они проходят/падают&lt;/li&gt;
    &lt;li id=&quot;nQGu&quot;&gt;Написал свой первый тест на &lt;code&gt;Python&lt;/code&gt; за 1 запрос в курсоре&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h4 id=&quot;Что-было-дальше&quot;&gt;Что было дальше&lt;/h4&gt;
  &lt;p id=&quot;jtjo&quot;&gt;Дальше я повторил все с самого начала во втором проекте с UI-тестами для сайта, и это заняло в сумме около полутора часов, потому что:&lt;/p&gt;
  &lt;ol id=&quot;1AXB&quot;&gt;
    &lt;li id=&quot;fN7Y&quot;&gt;Долго разбирался с кредами для тестовых учетных записей (логины, пароли, адреса почты), в итоге все нашлось, спасибо коллегам&lt;/li&gt;
    &lt;li id=&quot;3uYw&quot;&gt;Сгенерировал неправильную автоматизацию в &lt;code&gt;Makefile&lt;/code&gt; сначала&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;akmV&quot;&gt;В первом проекте ребята использовали другой файл для хранения секретов, а поскольку у меня вообще не было никакого опыта работы с этими проектами, то я даже не думал, что надо где-то это проверить.&lt;/p&gt;
  &lt;p id=&quot;g7W8&quot;&gt;В общем, есть такой файл &lt;code&gt;config.py&lt;/code&gt;, где есть строка с явным указанием названия файла с кредами, например:&lt;/p&gt;
  &lt;p id=&quot;aTZ0&quot;&gt;&lt;code&gt;load_dotenv(find_dotenv(filename=&amp;quot;.env&amp;quot;), override=True)&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;GDPQ&quot;&gt;&lt;strong&gt;Как я в этом разобрался?&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;1msn&quot;&gt;Написал курсору, чтобы он запустил любой тест, где есть авторизация, и поправил ошибки.&lt;/p&gt;
  &lt;p id=&quot;Hf7b&quot;&gt;Он запустил первый тест с авторизацией, тест упал, потом курсор за минуту нашел проблему и поправил ✅&lt;/p&gt;
  &lt;h3 id=&quot;Заключение&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;hXIB&quot;&gt;Я хотел показать, что нет необходимости предварительно учить язык программирования, или покупать и проходить курсы по &amp;quot;тестированию с нуля&amp;quot;, или просто платить кому-то деньги за обучение новому в &amp;quot;айти&amp;quot;, прежде чем начинать заниматься делом. &lt;strong&gt;Можно сразу начинать разбираться в рабочих задачах.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;hSt1&quot;&gt;&lt;strong&gt;Хочется &lt;/strong&gt;чему-то&lt;strong&gt; научиться &lt;/strong&gt;илиразобраться в новой теме? Открываем бесплатные инструменты типа перплексити и &lt;strong&gt;просим составить план&lt;/strong&gt; обучения. Есть еще российские аналоги, но я ими ни разу не пользовался, поэтому не могу ничего полезного сказать про них.&lt;/p&gt;
  &lt;p id=&quot;vB0b&quot;&gt;Нет уверенности в правильности ответов нейросетей? Живые &lt;strong&gt;люди&lt;/strong&gt; &lt;strong&gt;тоже&lt;/strong&gt; &lt;strong&gt;ошибаются&lt;/strong&gt; очень часто, в том числе менторы, блогеры и эксперты на платформах, где продаются курсы.&lt;/p&gt;
  &lt;p id=&quot;i5aS&quot;&gt;Боишься, что не получится? Я старался писать простым языком и делать акцент на том, что не нужно иметь опыт, чтобы его получить. Да, не нужен опыт работы, чтобы начать работать. Не нужно получать высшее образование, чтобы начать зарабатывать деньги.&lt;/p&gt;
  &lt;p id=&quot;6uJU&quot;&gt;&lt;strong&gt;У тебя все получится&lt;/strong&gt;, главное взять на себя ответственность за свое обучение и терпеливо решать возникающие задачи (проблемы) 🚀&lt;/p&gt;

</content></entry></feed>