<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>IT KPI Dart &amp; Flutter</title><generator>teletype.in</generator><description><![CDATA[🎯 Dart &amp; Flutter at IT KPI
Chat: https://t.me/itkpi_dart

More related channels and chats: https://t.me/itkpi_list]]></description><image><url>https://teletype.in/files/58/16/58166d05-ac38-47e3-9cf7-6d8d8ab298de.png</url><title>IT KPI Dart &amp; Flutter</title><link>https://teletype.in/@itkpi_dart</link></image><link>https://teletype.in/@itkpi_dart?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/itkpi_dart?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/itkpi_dart?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Mon, 20 Apr 2026 02:49:32 GMT</pubDate><lastBuildDate>Mon, 20 Apr 2026 02:49:32 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@itkpi_dart/VS-Code-server-for-Android-Dev</guid><link>https://teletype.in/@itkpi_dart/VS-Code-server-for-Android-Dev?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart</link><comments>https://teletype.in/@itkpi_dart/VS-Code-server-for-Android-Dev?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart#comments</comments><dc:creator>itkpi_dart</dc:creator><title>Розробка під Android на Flutter з VS Code на сервері</title><pubDate>Fri, 19 Aug 2022 07:08:19 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/a4/c4/a4c4de08-e38a-4833-9fb5-10cc28ac622b.png"></media:content><category>Flutter</category><description><![CDATA[<img src="https://img1.teletype.in/files/06/30/06303c23-0636-45a6-9649-7ed119abd323.png"></img>Гнучкість VS Code дозволяє займатися розробкою прямо у браузері. Прикладами є vscode.dev, що працює виключно на фронтенді, та github.dev з послугою GitHub Codespaces, що використовує віддалений сервер як потужний бекенд для аналізу коду, тоді як фронтенд є лише відображенням результатів обчислень на сервері.]]></description><content:encoded><![CDATA[
  <figure id="4E0p" class="m_column">
    <img src="https://img1.teletype.in/files/06/30/06303c23-0636-45a6-9649-7ed119abd323.png" width="1200" />
    <figcaption><a href="https://stackoverflow.com/a/73412266/9004442" target="_blank">Read in English</a></figcaption>
  </figure>
  <p id="rGBm">Гнучкість VS Code дозволяє займатися розробкою прямо у браузері. Прикладами є vscode.dev, що працює виключно на фронтенді, та github.dev з послугою GitHub Codespaces, що використовує віддалений сервер як потужний бекенд для аналізу коду, тоді як фронтенд є лише відображенням результатів обчислень на сервері.</p>
  <p id="4K2c">Безкоштовною альтернативою GitHub Codespaces є self-hosted проєкт <code>code-server</code>.</p>
  <p id="mzmw">Давайте розберемося як завдяки <code>code-server</code> та SSH налаштувати середовище для <strong>Android розробки на Flutter</strong> так, щоб потужний віддалений сервер виконував усі обчислення, а локальна малопотужна машина відображала GUI та підключала фізичний Android пристрій.</p>
  <p id="QhM6">Перевірено на Ubuntu 22.04 (сервер), Fedora 36 (локальна машина) та Flutter 3.0.5.</p>
  <h2 id="%D0%B2%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%BD%D1%8F-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B7%D0%B0%D0%B1%D0%B5%D0%B7%D0%BF%D0%B5%D1%87%D0%B5%D0%BD%D0%BD%D1%8F-%D1%82%D0%B0-sdk-%D0%BD%D0%B0-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D1%96">Встановлення програмного забезпечення та SDK на сервері</h2>
  <h3 id="1-%D0%B2%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D1%96%D1%82%D1%8C-code-server-%D0%B7-%D0%BE%D1%84%D1%96%D1%86%D1%96%D0%B9%D0%BD%D0%BE%D0%B3%D0%BE-%D1%80%D0%B5%D0%BF%D0%BE%D0%B7%D0%B8%D1%82%D0%BE%D1%80%D1%96%D1%8E">1. Встановіть <code>code-server</code> з <a href="https://github.com/coder/code-server" target="_blank">офіційного репозиторію</a></h3>
  <blockquote id="ZDmR"><strong>Важливо</strong>: Після встановлення <code>code-server</code> просить налаштувати автозапуск серверу через systemd шляхом виконання команди <code>systemctl</code>. Не дотримуйтесь цих вказівок, інакше VS Code не знаходитиме пристрої через ADB. Способу примусити ADB працювати сумісно з systemd поки не знайшлось.</blockquote>
  <h3 id="2-%D0%B2%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D1%96%D1%82%D1%8C-flutter-sdk-%D1%82%D0%B0-%D0%BE%D0%BD%D0%BE%D0%B2%D1%96%D1%82%D1%8C-path">2. Встановіть Flutter SDK та оновіть <code>PATH</code></h3>
  <p id="GA8c">Вашій системі також можуть знадобитися додаткові залежності для запуску Flutter SDK. Рекомендовано дотримуватися <a href="https://docs.flutter.dev/get-started/install/linux" target="_blank">офіційного посібника</a>. Надавайте перевагу описаним там ручним способам встановлення.</p>
  <p id="NlnD">Після встановлення оновіть змінну <code>PATH</code> у <code>~/.bashrc</code>, щоб включити папку <code>/bin</code> з Flutter SDK, наприклад, додайте такий рядок:</p>
  <pre id="EH1q" data-lang="bash">export PATH=&quot;$PATH:$HOME/path/to/flutter/bin&quot;</pre>
  <p id="vS8U">після чого, застосуйте зміни:</p>
  <pre id="AyBF" data-lang="bash">source ~/.bashrc</pre>
  <h3 id="3-%D0%B2%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%BD%D1%8F-%D1%96%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D1%96%D1%8E-android">3. Встановлення інструментарію Android</h3>
  <p id="miSq">Припустимо, на вашому сервері немає графічного середовища, тому ми встановимо інструментарій Android без Android Studio (оскільки для роботи Studio потрібен DE).</p>
  <h4 id="%D1%81%D0%BA%D0%B0%D1%87%D1%83%D0%B2%D0%B0%D0%BD%D0%BD%D1%8F-cmdline-tools">Скачування cmdline-tools</h4>
  <p id="f72F">Перейдіть на <a href="https://developer.android.com/studio#command-tools" target="_blank">сайт Android Studio</a> та завантажте пакет &quot;Command line tools only&quot;. Розпакуйте архів командою <code>unzip</code> за бажаним шляхом. Краще зробити структуру папок наступною:</p>
  <pre id="aANf" data-lang="bash">~/path/to/android-sdk/cmdline-tools</pre>
  <p id="8ZO6">Таким чином, коли <code>sdkmanager</code> завантажуватиме свої пакети, нові папки будуть створені всередині папки <code>android-sdk</code>.</p>
  <p id="XT7w">Станом на серпень 2022 року для <code>sdkmanager</code> в інструментах командного рядка Android потрібна спеціальна ієрархія папок, яку можна досягти, помістивши вміст папки <code>cmdline-tools</code> в папку <code>latest</code> під нею за допомогою цієї команди:</p>
  <pre id="VOHs" data-lang="bash">mv ./cmdline-tools/ ./latest &amp;&amp; mkdir cmdline-tools &amp;&amp; mv ./latest/ ./cmdline-tools/latest/</pre>
  <p id="loQx">Додайте інструменти до свого <code>PATH</code> у <code>.bashrc</code> і вкажіть <code>ANDROID_SDK_ROOT</code>, додавши нові рядки:</p>
  <pre id="qlGR" data-lang="bash">export ANDROID_SDK_ROOT=&quot;$HOME/path/to/android-sdk&quot;
export PATH=&quot;$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools&quot;</pre>
  <p id="sAqj">Не забудьте запустити <code>source ~/.bashrc</code></p>
  <h4 id="%D0%B2%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D1%96%D1%82%D1%8C-sdk">Встановіть SDK</h4>
  <p id="4CvR">Flutter SDK вимагає встановлення двох пакетів: <code>build-tools</code> і <code>platform-tools</code>.</p>
  <pre id="BE8Z" data-lang="bash">sdkmanager &quot;build-tools;33.0.0&quot; &quot;platform-tools&quot;</pre>
  <p id="3mar"><code>33.0.0</code> — це остання версія <code>build-tools</code> станом на серпень 2022 року. Дізнайтеся, яка остання версія <code>build-tools</code> доступна, запустивши:</p>
  <pre id="usTA" data-lang="bash">sdkmanager --list | grep build-tools</pre>
  <h4 id="%D0%BF%D0%BE%D0%B3%D0%BE%D0%B4%D1%8C%D1%82%D0%B5%D1%81%D1%8C-%D0%B7-%D0%BB%D1%96%D1%86%D0%B5%D0%BD%D0%B7%D1%96%D1%8F%D0%BC%D0%B8">Погодьтесь з ліцензіями</h4>
  <p id="Gx0u">Запустіть <code>sdkmanager --licenses</code> і прийміть усі ліцензії, натискаючи клавішу <code>y</code></p>
  <h2 id="%D0%BD%D0%B0%D0%BB%D0%B0%D1%88%D1%82%D1%83%D0%B2%D0%B0%D0%BD%D0%BD%D1%8F-vs-code">Налаштування VS Code</h2>
  <p id="Zcvw">Після встановлення <code>code-server</code> ви можете отримати доступ до редактора зі свого браузера.</p>
  <p id="JKKa">Також рекомендується встановити його як PWA, щоб у вас було більше місця на екрані, більше можливостей для комбінацій клавіш і можливість запускати редактор із системної панелі запуску програм.</p>
  <ol id="QxJw">
    <li id="rD8p">Встановіть розширення Flutter</li>
    <li id="WPHL">Додайте ці опції до користувацьких налаштувань VS Code:</li>
  </ol>
  <pre id="gNWF" data-lang="javascript">{
    &quot;dart.flutterRunAdditionalArgs&quot;: [
        // Dart Developer Service port (debugger)
        &quot;--dds-port=10388&quot;,
        // Dart VM Service instance port (device)
        &quot;--host-vmservice-port=10389&quot;
    ],
    &quot;dart.devToolsPort&quot;: 9100,
    &quot;dart.devToolsLocation&quot;: &quot;external&quot;
}</pre>
  <p id="GZVU">За замовчуванням Dart вибирає випадкові порти для підключення між налагоджувачем і пристроєм. Встановлюючи ці параметри, ми робимо порти статичними, тому ми можемо їх легко переадресовувати між пристроями.</p>
  <p id="6Mz1">Можливо, ви захочете налаштувати <code>dart.devToolsLocation: external</code> через помилку <code>code-server</code>, яка не дозволяє завантажувати iframe Dart DevTools. Цей параметр запускає DevTools у вашому браузері за умовчанням. Причиною проблеми може бути <a href="https://github.com/coder/code-server/issues/1936" target="_blank">цей issue</a>.</p>
  <h2 id="%D0%BF%D0%B5%D1%80%D0%B5%D0%B0%D0%B4%D1%80%D0%B5%D1%81%D0%B0%D1%86%D1%96%D1%8F-%D0%BF%D0%BE%D1%80%D1%82%D1%96%D0%B2-%D0%B2%D0%B8%D0%BA%D0%BE%D1%80%D0%B8%D1%81%D1%82%D0%BE%D0%B2%D1%83%D1%8E%D1%87%D0%B8-ssh-%D0%BD%D0%B0-%D0%BB%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%96%D0%B9-%D0%BC%D0%B0%D1%88%D0%B8%D0%BD%D1%96">Переадресація портів використовуючи SSH на локальній машині</h2>
  <p id="Ss5N">Для налагодження програми Android за допомогою Flutter нам доведеться перенаправити 4 порти. У таблиці наведено номери портів відповідно до параметрів VS Code вище. Ви можете використовувати власні номери портів, але тоді вам доведеться відповідно змінити конфігурації.</p>
  <figure id="xiwo" class="m_column">
    <img src="https://img2.teletype.in/files/9a/1b/9a1b16b3-a568-49f5-83c1-38c44632b357.png" width="671" />
  </figure>
  <h3 id="%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D0%B8">Команди</h3>
  <p id="C7Sq">SSH можна використовувати для перенаправлення портів.</p>
  <p id="cCZB">Щоб перенаправити порт з локального хоста на віддалений хост, запустіть на локальному комп’ютері:</p>
  <pre id="gxRR" data-lang="bash">ssh -R XXXX:localhost:XXXX user@host</pre>
  <p id="xcN4">Щоб перенаправити порт із віддаленого хоста на локальний, запустіть на локальному комп’ютері:</p>
  <pre id="3UrC" data-lang="bash">ssh -L XXXX:localhost:XXXX user@host</pre>
  <p id="7wcp">Ви можете з’єднати параметри команди <code>ssh</code>, наприклад:</p>
  <pre id="byVQ" data-lang="bash">ssh -R 5037:localhost:5037 -L 10388:localhost:10388 -R 10389:localhost:10389 -L 9100:localhost:9100 user@host</pre>
  <p id="Y0iK">Перенаправлення портів буде активним, доки ви не закриєте з’єднання SSH.</p>
  <p id="uHvm">Переконайтеся, що ваш брандмауер налаштовано на переадресацію портів.</p>
  <h2 id="%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%B4%D0%BB%D1%8F-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%B7%D0%B0%D1%86%D1%96%D1%97">Скрипт для автоматизації</h2>
  <p id="PfOi">Сценарій, який автоматизує процес:</p>
  <ol id="UuDY">
    <li id="IKkB">Вимикає локальний ADB і перезапускає його, щоб звільнити використані порти</li>
    <li id="yXkx">Налаштовує переадресацію портів для вказаних портів на віддалений хост. Сценарій передбачає використання ключів для автентифікації SSH.</li>
    <li id="1Whz">Знищує всі запущені екземпляри <code>code-server</code>, <code>node</code> і <code>adb</code>. Якщо вам це не підходить, відредагуйте скрипт.</li>
    <li id="ZmSx">Запускає ADB і запускає <code>code-server</code>.</li>
  </ol>
  <p id="Hf1K">Призначений для запуску на локальній машині.</p>
  <pre id="zrJj" data-lang="bash">#!/bin/bash

# Remote machine
CE_MACHINE=&quot;user@host&quot;

# Local machine, no need to change
CE_LOCALHOST=&quot;localhost&quot;

# Default port for ADB daemon is 5037
CE_ADB_PORT=&quot;5037&quot;

# You might need to specify exact path to adb on your
# remote system since .bashrc is not sourced for
# non-interactive sessions (see https://stackoverflow.com/a/6212684)
CE_ADB_EXECUTABLE=&quot;~/dev/tools/android-sdk/platform-tools/adb&quot;

# &quot;Dart Developer Service port
CE_DDS_PORT=&quot;10388&quot;

# Dart VM Service instance port
CE_HOST_VMSERVICE_PORT=&quot;10389&quot;

# Flutter DevTools port
CE_DEVTOOLS_PORT=&quot;9100&quot;

#### VSCode Settings ####
# &quot;dart.flutterRunAdditionalArgs&quot;: [
# &quot;--dds-port=10388&quot;,
# &quot;--host-vmservice-port=10389&quot;,
# ],
# &quot;dart.devToolsPort&quot;: 9100,
# &quot;dart.devToolsLocation&quot;: &quot;external&quot;,
#### VSCode Settings ####


# Reset ADB on local machine
# so it releases used ports
killall adb
adb devices

# When &#x60;adb devices&#x60; is called, ADB checks the daemon port.
# If it detects there&#x27;s no response on the port, it
# launches a new daemon.
#
# After killing ADB and forwarding the ADB port to local machine,
# a newly launched ADB client will bind to the existing connection
# (which is our physical device) instead of launching a daemon on
# the remote machine.
#
# Restart code-server
# ADB doesn&#x27;t detect devices if code-server is managed by systemctl
# WARNING, killing all Node processes here. Customize if needed.
#
# 1. ADB forwarding Local -&gt; Remote
# 2. Dart Dev Server forwarding Remote -&gt; Local
# 3. Dart on-device debugger client forwarding Local -&gt; Remote
# 4. Flutter DevTools Remote -&gt; Local forwarding
ssh -R $CE_ADB_PORT:$CE_LOCALHOST:$CE_ADB_PORT \
-L $CE_DDS_PORT:$CE_LOCALHOST:$CE_DDS_PORT \
-R $CE_HOST_VMSERVICE_PORT:$CE_LOCALHOST:$CE_HOST_VMSERVICE_PORT \
-L $CE_DEVTOOLS_PORT:$CE_LOCALHOST:$CE_DEVTOOLS_PORT \
$CE_MACHINE &quot;killall code-server; killall node; killall adb; $CE_ADB_EXECUTABLE devices; code-server&quot;</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@itkpi_dart/Future-Completer</guid><link>https://teletype.in/@itkpi_dart/Future-Completer?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart</link><comments>https://teletype.in/@itkpi_dart/Future-Completer?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart#comments</comments><dc:creator>itkpi_dart</dc:creator><title>Completer: створюємо власні Future</title><pubDate>Wed, 05 May 2021 13:21:36 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/da/8e/da8e977f-b642-4257-89fb-f51e537766d2.png"></media:content><category>Dart</category><description><![CDATA[<img src="https://teletype.in/files/78/77/7877ea4d-c3df-4a5e-8b45-a5fea6f2c7c4.png"></img>Future є базовим поняттям для Dart. Детальний розбір асинхронного програмування ми вже робили у перекладі англомовної статті з dart.dev.]]></description><content:encoded><![CDATA[
  <p>Future є базовим поняттям для Dart. Детальний розбір асинхронного програмування ми вже робили <a href="https://teletype.in/@itkpi_dart/Asynchronous-programming" target="_blank">у перекладі англомовної статті</a> з dart.dev.</p>
  <p>Та іноді, під час написання коду, інтерфейсу Future починає не вистачати. Таке трапляється частіше за все під час написання власних бібліотек та контролерів.</p>
  <blockquote>Також ми переклали цю статтю <a href="https://teletype.in/@itkpi_dart_en/Future-Completer" target="_blank">англійською</a></blockquote>
  <figure class="m_column">
    <img src="https://teletype.in/files/78/77/7877ea4d-c3df-4a5e-8b45-a5fea6f2c7c4.png" width="1200" />
  </figure>
  <h2>Як це, не вистачає?</h2>
  <p>Розгляньмо наявний інтерфейс класу Future:</p>
  <pre data-lang="dart">Future(FutureOr&lt;T&gt; computation())
/* Виклик функції computation() асинхронно,
використовуючи Timer.run */
Future.delayed(Duration duration, [FutureOr&lt;T&gt; computation()])/* Аналогічно Future(), але виконання відбувається
після затримки */
Future.error(Object error, [StackTrace? stackTrace])// Створення Future, що завершується помилкою
Future.microtask(FutureOr&lt;T&gt; computation())
// Аналогічно Future(), але використовується scheduleMicrotask
Future.sync(FutureOr&lt;T&gt; computation())
// Аналогічно Future(), але функція викликається негайно (синхронно)
Future.value([FutureOr&lt;T&gt;? value])
// Створення Future з уже вказаним результатом
</pre>
  <p>Як бачимо, наданий інтерфейс дає лише можливість продукувати Future як результати інших обчислень, але не дає ніяких інструментів, щоб повернути індикацію обчислень, що ще тривають.</p>
  <p>Наданий інтерфейс корисно використовувати хіба що у синхронному коді, якщо, наприклад, ми імплементуємо який-небудь інтерфейс, що вимагає повернення Future у методі, у той час як наша реалізація є синхронною:</p>
  <pre data-lang="dart">import &#x27;dart:io&#x27;;

abstract class FileReader {
  Future&lt;String&gt; readFile(String name);
}

class SyncFileReader implements FileReader {
  @override
  Future&lt;String&gt; readFile(String name) {
    return Future(() {
      return File(name).readAsStringSync();
    });
  }
}</pre>
  <p>Тепер уявімо ситуацію, що ми створюємо контролер для бази даних. Для того, щоб почати працювати з базою даних, до неї треба підключитися. До того ж, необхідно зберегти підключення до БД у контролері — адже створення багатьох паралельних підключень є неефективним.</p>
  <p>Спробуймо написати такий код з тими знаннями, що у нас є:</p>
  <pre data-lang="dart">import &#x27;package:sqflite/sqflite.dart&#x27;;

class DbConnection {
  Database? _database;

  Future&lt;Database&gt; use() async {
    _database ??= await openDatabase(
      &#x27;my.db&#x27;,
      onCreate: (db, version) async {
        ...
      },
      version: 1,
    );

    return _database!;
  }
}</pre>
  <p>Даний клас має метод <code>use()</code>, який:</p>
  <ul>
    <li>Перевіряє наявність вже активного підключення</li>
    <li>Якщо підключення немає, виконує його й повертає результат</li>
    <li>Якщо підключення вже є, повертає його</li>
  </ul>
  <p>Здавалось би, все гаразд, але у даній реалізації можна знайти недолік — якщо кілька незалежних частин коду спробують отримати доступ до бази даних до того, як було ініційовано перше підключення, на кожний запит буде відкрите окреме підключення: </p>
  <figure class="m_column">
    <iframe src="https://player.vimeo.com/video/545413122/?autoplay=false&loop=false&muted=false&title=true"></iframe>
    <figcaption>Демонстрація того, що відбувається у даному прикладі</figcaption>
  </figure>
  <p>Ось ще один наглядний приклад цієї проблеми:</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=c6fba0c5cebbded8f6d905384c15a8f5&split=75&null_safety=true"></iframe>
  </figure>
  <p>Розглянемо його детальніше:</p>
  <ul>
    <li>Клас <code>NumberHolder</code> має один раз отримати значення з асинхронної функції <code>asyncNumberGet()</code>, після чого зберегти його та щоразу повертати збережене значення</li>
    <li><code>asyncNumberGet()</code> після кожного виклику збільшує своє значення</li>
    <li>У функції <code>main()</code> ми:</li>
    <ul>
      <li>Паралельно запускаємо два запити на отримання значення числа</li>
      <li>Окремо запускаємо один запит на отримання числа</li>
    </ul>
  </ul>
  <p>За логікою:</p>
  <ol>
    <li>Ми створюємо екземпляр <code>NumberHolder</code></li>
    <li>Виклик 1: Клас <code>NumberHolder</code> має отримати значення числа з функцї <code>asyncNumberGet()</code> <strong>один раз</strong> та записати його у змінну <code>_result</code>. Результат: 1</li>
    <li>Виклик 2: змінна <code>_result</code> вже наявна, тому використовуємо її. Результат: 1</li>
    <li>Виклик 3: змінна <code>_result</code> вже наявна, тому використовуємо її. Результат: 1</li>
  </ol>
  <p>Тобто, очікуваним виводом даної програми мають бути такі рядки:</p>
  <pre>[1, 1]
1</pre>
  <p>Але якщо ми запустимо приклад, то побачимо інший результат:</p>
  <pre>[1, 2]
2</pre>
  <p>Даний результат виникає через проблему, що називається Race Condition, або конкуренція: коли від порядку виконання коду залежить результат.</p>
  <p>У прикладі з базою даних стався б витік ресурсів через непотрібне та незакрите з&#x27;єднання з БД.</p>
  <p>Давайте розглянемо, чому так стається. Переглянувши реалізацію <code>Future.wait</code>, робимо висновок, що всередині <code>Future.wait([task1(), task2()])</code> виклик відбувається так:</p>
  <pre data-lang="dart">task1();
task2();</pre>
  <p>Тобто обидва асинхронні завдання виконуються конкурентно, не очікуючи один на одного. </p>
  <h2>Конкурентність у контексті Dart</h2>
  <p>JavaScript-розробники вже мають бути знайомі з поняттям черги подій (event loop).</p>
  <p>Коротко: хоча й Dart підтримує справжню багатопоточність (isolates), увесь звичайний (синхронний та асинхронний) код виконується в одному потоці. Це означає, що усі команди можуть виконуватись виключно <strong>одна за одною</strong>.</p>
  <p>Але одночасно у логіки роботи черги є одна особливість: як тільки інтерпретатор потрапляє на асинхронну команду, ця команда переміщується у кінець черги, очікуючи на виконання синхронного коду, що розміщується після неї. </p>
  <figure class="m_original">
    <img src="https://teletype.in/files/d9/22/d9221700-8859-4161-a910-99b77df79524.png" width="704" />
    <figcaption>Логіка роботи Future</figcaption>
  </figure>
  <p>У нашому прикладі замість <code>task1</code> ми викликаємо <code>number.use</code>. Перегляньмо цю функцію.</p>
  <pre data-lang="dart">Future&lt;int&gt; use() async {
    _result ??= await asyncNumberGet();
    return _result!;
}</pre>
  <p>Перепишемо дану функцію простіше та без використання <code>async</code>, щоб зрозуміти логіку роботи:</p>
  <pre data-lang="dart">Future&lt;int&gt; use() {
  if (_result == null) {
    return asyncNumberGet() # Future
    .then((number) {
      _result = number;
      return number;
    });
  }
  return Future.value(_result);
}</pre>
  <p>Застосуємо наші знання про конкурентність та проілюструємо порядок виконання очима інтерпретатора Dart:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/e3/ab/e3aba160-836c-4e8c-9169-06837bb77de0.png" width="711" />
  </figure>
  <p>Тоді викличмо нашу функцію двічі підряд, як це робить <code>Future.wait</code> й відсортуймо команди, як це робить Dart:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/ed/b9/edb92057-ba42-4197-ab15-d8d2eef22b58.png" width="1045" />
  </figure>
  <p>Так як асинхронний код було переміщено, виклик <code>asyncNumberGet()</code> поки що не стався, а отже й значення _result не було встановлено. Через це друга перевірка <code>_result == null</code> є істинною, через що дану ділянку коду не буде пропущено, а функцію <code>asyncNumberGet()</code> буде викликано двічі:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/fe/e1/fee13e3e-5c26-493f-b5b9-9fcc1443be26.png" width="1054" />
  </figure>
  <p>Врешті-решт, асинхронна функція виконується синхронно в кінці черги. </p>
  <p>У перенесеному коді записуємо усі конструкції <code>Future().then()</code> аналогічною конструкцією з <code>await</code> для спрощення сприйняття:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/1b/a3/1ba3d491-670b-40de-b040-a65d6bf9b01a.png" width="945" />
  </figure>
  <p>У результаті маємо два виклики <code>asyncNumberGet()</code> через те, що перевірка умови під час другого виклику відбувається раніше, ніж завершення першого виклику.</p>
  <h2>Completer на допомогу</h2>
  <p>Але ж що робити, якщо хочеться створити безшовний доступ до кешованого результату? </p>
  <p><a href="https://api.dart.dev/stable/2.12.4/dart-async/Completer-class.html" target="_blank">Completer</a> це вбудований у Dart контролер Future. Він здатний продукувати Future будь-якого виду та завершувати (<em>resolve/complete</em>) їх у будь-який час.</p>
  <p>Completer дуже простий у використанні. Екземляр даного класу має такі методи:</p>
  <pre data-lang="dart">complete([FutureOr&lt;T&gt;? value]) → void
// Завершує Future наданим значенням
completeError(Object error, [StackTrace? stackTrace]) → void
// Завершує Future з помилкою</pre>
  <p>Та такі властивості:</p>
  <pre data-lang="dart">future → Future&lt;T&gt;
// Екземляр Future, який ми контролюємо
isCompleted → bool
// Повертає істину, якщо Future вже завершено</pre>
  <p>Отож, перепишемо наш приклад з використанням Completer:</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=614d3688f99a622125858b6861bd9093&split=75&null_safety=true"></iframe>
  </figure>
  <p>Отримуємо результат, що підтверджує правильність.</p>
  <pre>CALLED
[1, 1]
1</pre>
  <h2>Висновки</h2>
  <p>Завдяки Completer ми змогли зробити контрольований екземпляр Future, який можна повертати, поки тривають конкурентні обчислення, у результаті чого ми змогли уникнути помилки Race Condition.</p>
  <blockquote>Запрошуємо усіх зацікавлених у Dart і Flutter приєднуватись до нашого <a href="https://t.me/itkpi_dart" target="_blank">чату</a>, щоб отримувати більше цікавих матеріалів та новин про ці технології.</blockquote>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@itkpi_dart/Asynchronous-programming</guid><link>https://teletype.in/@itkpi_dart/Asynchronous-programming?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart</link><comments>https://teletype.in/@itkpi_dart/Asynchronous-programming?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart#comments</comments><dc:creator>itkpi_dart</dc:creator><title>Асинхронне програмування у Dart: Future, async, await</title><pubDate>Fri, 30 Apr 2021 11:35:45 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/bf/64/bf64b38f-5d20-4dc9-8c37-6ce3794a5767.png"></media:content><category>Dart</category><description><![CDATA[<img src="https://teletype.in/files/f9/02/f9023d80-ba17-427e-bb50-95b86c7da7c8.png"></img>Асинхронність є однією з фундаментальних умов для роботи з Dart. Навіть якщо дане поняття вам відоме з JavaScript, асинхронність у Dart має несуттєві відмінності, тож радимо прочитати цю статтю кожному.]]></description><content:encoded><![CDATA[
  <p>Асинхронність є о<u>д</u>нією з фундаментальних умов для роботи з Dart. Навіть якщо дане поняття вам відоме з JavaScript, асинхронність у Dart має несуттєві відмінності, тож радимо прочитати цю статтю кожному.</p>
  <p>Дана стаття є перекладом <a href="https://dart.dev/codelabs/async-await" target="_blank">англомовного туторіалу з dart.dev</a>.</p>
  <figure class="m_column">
    <img src="https://teletype.in/files/f9/02/f9023d80-ba17-427e-bb50-95b86c7da7c8.png" width="1200" />
  </figure>
  <p>Ця кодова лабораторія навчить вас писати асинхронний код за допомогою об&#x27;єкту Future та ключових слів <code>async</code>, <code>await</code>.</p>
  <p>Щоб отримати максимальну віддачу від цієї кодової лабораторії, вам слід мати:</p>
  <ul>
    <li>Знання <a href="https://dart.dev/samples" target="_blank">базового синтаксису Dart</a>.</li>
    <li>Деякий досвід написання асинхронного коду іншою мовою.</li>
  </ul>
  <p>Приблизний час для проходження цієї кодової лабораторії: 40-60 хвилин.</p>
  <h3>Чому асинхронний код важливий</h3>
  <p>Асинхронні операції дозволяють вашій програмі продовжувати працювати, одночасно чекаючи закінчення іншої операції. Ось декілька поширених асинхронних операцій:</p>
  <ul>
    <li>Отримання даних через мережу.</li>
    <li>Запис у базу даних.</li>
    <li>Зчитування даних з файлу.</li>
  </ul>
  <p>Для виконання асинхронних операцій в Dart ви можете використовувати клас <code>Future</code> та ключові слова <code>async</code> і <code>await</code>.</p>
  <h4>Приклад: Неправильне використання асинхронної функції</h4>
  <p>Наступний приклад показує неправильний спосіб використання асинхронної функції ( <code>fetchUserOrder()</code>). Пізніше ви виправите приклад за допомогою <code>async</code> та <code>await</code>. Перш ніж запускати цей приклад, спробуйте виявити проблему — який, на вашу думку, буде результат?</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=07943e44a22c3bbc53906cd9a58778e4&split=75&null_safety=true"></iframe>
  </figure>
  <p>Ось чому в прикладі не вдається вивести значення, яке <code>fetchUserOrder()</code> повертає з запізненням у 2 секунди:</p>
  <ul>
    <li><code>fetchUserOrder()</code> — це асинхронна функція, яка після затримки надає рядок, що описує замовлення користувача: “Large Latte”.</li>
    <li>Щоб отримати замовлення користувача, <code>createOrderMessage()</code> слід викликати <code>fetchUserOrder()</code> і зачекати, поки замовлення буде повернуто. Через те, що <code>createOrderMessage()</code> виконує виведення до того, як <code>fetchUserOrder()</code> завершує роботу, <code>createOrderMessage()</code>не може отримати текстове значення, яке <code>fetchUserOrder()</code> ще не повернуло.</li>
    <li>Натомість <code>createOrderMessage()</code> отримує об&#x27;єкт, що позначає результат, який ще очікується: незавершену Future. Докладніше про Future ви дізнаєтесь у наступному розділі.</li>
    <li>Оскільки <code>createOrderMessage()</code> не вдається отримати значення, що описує замовлення користувача, приклад виводить не “Large Latte”, а “Ваше замовлення: <code>Instance of &#x27;_Future&lt;String&gt;&#x27;</code>”.</li>
  </ul>
  <p>У наступних розділах ви дізнаєтеся про Future та про роботу з ними (за допомогою <code>async</code> та <code>await</code>), що дасть вам змогу написати код, який зможе коректно вивести &quot;Large Latte&quot; у консолі, який повертає функція <code>fetchUserOrder()</code>.</p>
  <h3>Ключові терміни:</h3>
  <ul>
    <li><strong>Синхронна операція</strong>: синхронна операція блокує виконання інших операцій до свого завершення.</li>
    <li><strong>Синхронна функція</strong>: синхронна функція виконує лише синхронні операції.</li>
    <li><strong>Асинхронна операція</strong>: після запуску асинхронна операція дозволяє паралельно виконувати інші операції впродовж своєї роботи.</li>
    <li><strong>Асинхронна функція</strong>: асинхронна функція виконує принаймні одну асинхронну операцію, а також може виконувати <em>синхронні</em> операції.</li>
  </ul>
  <h2>Що таке Future?</h2>
  <p>Future - це екземпляр класу <a href="https://api.dart.dev/stable/dart-async/Future-class.html" target="_blank">Future</a>. Future це представлення результату роботи асинхронної операції, що може мати два стани: незавершений або завершений.</p>
  <blockquote><strong>Примітка: </strong>Незавершений (<em>uncompleted)</em> - термін у мові Dart, що стосується стану Future до того, як повертається значення.</blockquote>
  <h3>Незавершений</h3>
  <p>Коли ви викликаєте асинхронну функцію, вона повертає незавершений Future. Цей Future чекає на успішне завершення асинхронної операції функції або на видачу помилки.</p>
  <h3>Завершений</h3>
  <p>Якщо асинхронна операція вдається, Future завершується (<em>completes</em>) значенням. В іншому випадку Future завершується помилкою.</p>
  <p><strong>Завершення зі значенням</strong></p>
  <ul>
    <li><code>Future&lt;T&gt;</code> завершується значенням типу <code>T</code>. Наприклад, Future з типом <code>Future&lt;String&gt;</code> створює значення String (рядка). Якщо Future не повертає будь-якого значення, тоді це тип <code>Future&lt;void&gt;</code>.</li>
  </ul>
  <p><strong>Завершення з помилкою</strong></p>
  <ul>
    <li>Якщо асинхронна операція, виконана функцією, не вдається з будь-якої причини, Future завершується помилкою.</li>
  </ul>
  <h3>Приклад: вводимо Future у код</h3>
  <p>У цьому прикладі <code>fetchUserOrder()</code> повертає Future, яке завершується вже після виводу в консолі. Оскільки функція не повертає яке-небудь корисне значення, <code>fetchUserOrder()</code>має тип <code>Future&lt;void&gt;</code>. Перш ніж запускати приклад, спробуйте спрогнозувати, що буде виведено першим: “Large Latte” або “Отримання замовлення користувача…”.</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=0b7f74b7cfd01182fc0458ba104bdddc&split=75&null_safety=true"></iframe>
  </figure>
  <p>У попередньому прикладі, незважаючи на те, що <code>fetchUserOrder()</code> виконується до виклику <code>print()</code> на рядку 8, консоль відображає дані з рядка 8 (“Fetching user order…”) перед даними з <code>fetchUserOrder()</code>(“Large Latte”). Це трапляється тому, що <code>fetchUserOrder()</code> затримується перед тим, як надрукувати “Large Latte”.</p>
  <h3>Приклад: Завершення з помилкою</h3>
  <p>Запустіть цей приклад, щоб побачити, як Future завершується помилкою. Трохи пізніше ви дізнаєтесь, як правильно працювати з помилками у Future.</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=04efee97ab19debc266ae5711cf65c73&split=75&null_safety=true"></iframe>
  </figure>
  <p>У цьому прикладі <code>fetchUserOrder()</code> завершується помилкою, яка вказує на те, що ідентифікатор користувача недійсний.</p>
  <p>Ви дізналися про Future та про те, як вони повертають значення, але як використати результати асинхронних операцій? У наступному розділі ви дізнаєтесь, як отримати результати за допомогою ключових слів <code>async</code> та <code>await</code>.</p>
  <p><strong>Короткий огляд</strong></p>
  <ul>
    <li>Об&#x27;єкт типу <code>Future&lt;T&gt;</code> продукує значення типу <code>T</code></li>
    <li>Якщо Future не продукує корисне значення, це тип <code>Future&lt;void&gt;</code></li>
    <li><code>Future</code> має два стани: завершений та незавершений</li>
    <li>Коли ви викликаєте функцію, що повертає Future, дана асинхронна задача стає у чергу та повертає значення через якийсь час, з затримкою</li>
    <li>Коли виконання Future завершується, результатом є значення заданого типу або помилка</li>
  </ul>
  <p><strong>Ключові терміни</strong></p>
  <ul>
    <li><strong>Future</strong>: <a href="https://api.dart.dev/stable/dart-async/Future-class.html" target="_blank">клас Future</a> у Dart</li>
    <li><strong>a future</strong> (з малої літери): екземпляр класу Future, що позначає асинхронну задачу</li>
  </ul>
  <h2>Працюємо з Future: async та await</h2>
  <p>Ключові слова <code>async</code> та <code>await</code> створені для декларування асинхронних функцій та для використання їх результатів. Запам’ятайте ці дві основні рекомендації при використанні <code>async</code> та <code>await</code>:</p>
  <ul>
    <li>Щоб створити асинхронну функцію, додайте <code>async</code> перед тілом функції</li>
    <li>Ключове слово <code>await</code> можна використовувати тільки в async функціях</li>
  </ul>
  <p>Ось як перетворити синхронну функцію в асинхронну:</p>
  <p>Спочатку додайте ключове слово <code>async</code> перед тілом функції:</p>
  <pre data-lang="dart">void main() async { ··· }</pre>
  <p>Якщо функція має оголошений тип, який вона повертає, змініть тип на <code>Future&lt;T&gt;</code>, де <code>T</code> є саме тим типом, що повертає функція. Якщо функція не повертає явного значення, тоді слід вказати тип <code>Future&lt;void&gt;</code>:</p>
  <pre data-lang="dart">Future&lt;void&gt; main() async { ··· }</pre>
  <p>Тепер, коли у вас є <code>async</code>-функція, ви можете використовувати ключове слово <code>await</code>, щоб дочекатися результату <code>Future</code>:</p>
  <pre data-lang="dart">print(await createOrderMessage());</pre>
  <p>Як показують наступні два приклади, ключові слова <code>async</code> та <code>await</code> продукують асинхронний код, який дуже схожий на синхронний код. Єдині відмінності виділено в асинхронному прикладі, який знаходиться праворуч від синхронного прикладу:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/7a/b7/7ab70589-890f-4a46-858d-bd2459bb728b.png" width="910" />
  </figure>
  <p>Асинхронний приклад має три відмінності:</p>
  <ul>
    <li>Тип, що повертає функція <code>createOrderMessage()</code> <strong>змінено </strong>з <code>String</code> на <code>Future&lt;String&gt;</code>.</li>
    <li>Ключове слово <strong><code>async</code></strong> <strong>додано </strong>перед тілом функції <code>createOrderMessage()</code> і <code>main()</code>.</li>
    <li>Ключове слово <strong><code>await</code> додано</strong> перед викликом асинхронних функцій <code>fetchUserOrder()</code> і <code>createOrderMessage()</code>.</li>
  </ul>
  <p><strong>Ключові терміни</strong></p>
  <ul>
    <li><strong>async</strong>: Використовуйте ключове слово async, щоб зробити функцію асинхронною.</li>
    <li><strong>async function</strong>: <code>async</code>-функція - це функція, позначена ключовим словом <code>async</code>.</li>
    <li><strong>await</strong>: Ви можете використовувати ключове слово <code>await</code>, щоб отримати завершений результат асинхронного виразу. Ключове слово <code>await</code>працює тільки в межах <code>async</code>-функцій.</li>
  </ul>
  <h2>Порядок виконання з async та await</h2>
  <blockquote><strong>Примітка</strong>: До версії Dart 2.0, <code>async</code>-функція негайно повертала значення, не виконуючи код у тілі функції</blockquote>
  <h3> Приклад: Виконання всередині async-функції</h3>
  <p>Запустіть цей приклад, щоб побачити, як працює виконання у тілі <code>async</code>-функції. Як ви думаєте, яким буде результат?</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=8a8dc054f725551ddf8d36bd9dc72c28&split=75&null_safety=true"></iframe>
  </figure>
  <p>Після запуску коду в попередньому прикладі спробуйте поміняти рядки 2 і 3 місцями:</p>
  <pre data-lang="dart">var order = await fetchUserOrder();
print(&#x27;Awaiting user order...&#x27;);</pre>
  <p>Зверніть увагу як змінюється порядок виводу: тепер <code>print(&#x27;Awaiting user order&#x27;)</code> з&#x27;являється після першого використання ключового слова <code>await</code> в <code>printOrderMessage()</code>.</p>
  <h3>Вправа: Попрактикуйтеся з async й await</h3>
  <p>Наступна вправа — це непрацюючий юніт-тест, який містить частково готові фрагменти коду. Ваше завдання — виконати вправу, написати такий код, щоб тест проходив успішно. Вам не потрібно реалізовувати функцію <code>main()</code>.</p>
  <p>Щоб імітувати асинхронні операції, використовуйте надані вам функції:</p>
  <pre data-lang="dart">Future&lt;String&gt; fetchRole() {...} // Отримує короткий опис ролі користувача
Future&lt;int&gt; fetchLoginAmount() {...}
// Отримує кількість входів користувача в систему.</pre>
  <p><strong>Частина 1:</strong> <code>reportUserRole()</code></p>
  <p>Доробіть функцію <code>reportUserRole()</code> так, щоб вона робила наступне:</p>
  <ul>
    <li>Функція має повертати Future, яке завершується наступним рядком: <code>&quot;User role: &lt;user role&gt;&quot;</code></li>
    <ul>
      <li>Примітка: Ви повинні використовувати фактичне значення, яке повертає <code>fetchRole()</code>; копіювання та вставка значення з прикладу не дасть вам пройти тест</li>
      <li>Приклад поверненого значення: <code>&quot;User role: tester&quot;</code></li>
    </ul>
    <li>Функція має отримувати роль користувача, викликаючи надану функцію <code>fetchRole()</code>.</li>
  </ul>
  <p><strong>Частина 2: </strong><code>reportLogins()</code></p>
  <p>Реалізуйте <code>async</code>-функцію <code>reportLogins()</code> так, щоб вона робила наступне:</p>
  <ul>
    <li>Функція має повертати рядок <code>&quot;Total number of logins: &lt;# of logins&gt;&quot;</code>.</li>
    <ul>
      <li>Примітка: Ви повинні використовувати фактичне значення, яке повертає <code>fetchLoginAmount()</code>; копіювання та вставка значення з прикладу не дасть вам пройти тест</li>
      <li>Приклад поверненого значення з <code>reportLogins()</code>:<code>&quot;Total number of logins: 57&quot;</code></li>
    </ul>
    <li>Функція має отримувати кількість входів, викликаючи надану функцію <code>fetchLoginAmount()</code>.</li>
  </ul>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=a657df144a77805cbb8fc25ef6e8f461&split=75&null_safety=true"></iframe>
  </figure>
  <blockquote><strong>Зауважте</strong>: Якщо ваш код проходить усі тести, ви можете ігнорувати <a href="https://dart.dev/guides/language/analysis-options#customizing-analysis-rules" target="_blank">попередження з міткою <code>info</code></a></blockquote>
  <h2>Обробка помилок</h2>
  <p>Для обробки помилок у <code>async</code>-функції, використовуйте конструкцію try-catch:</p>
  <pre data-lang="dart">try {
  var order = await fetchUserOrder();
  print(&#x27;Awaiting user order...&#x27;);
} catch (err) {
  print(&#x27;Caught error: $err&#x27;);
}</pre>
  <p>У межах <code>async</code>-функції ви можете використовувати <a href="https://dart.dev/guides/language/language-tour#catch" target="_blank">try-catch конструкції</a> так само, як і в синхронному коді.</p>
  <h3>Приклад: async та await з використанням try-catch</h3>
  <p>Запустіть даний приклад, щоб побачити, як обробляти помилку в асинхронній функції. Як ви думаєте, яким буде результат?</p>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=85d6a2c2527b5bd7335f8e51687543bb&split=75&null_safety=true"></iframe>
  </figure>
  <h3>Вправа: Практикуємося обробляти помилки</h3>
  <p>Наступна вправа надає практичні навички в асинхронному коді, використовуючи підхід, описаний у попередньому розділі. Для імітації асинхронних операцій вам надано цю функцію:</p>
  <pre data-lang="dart">Future&lt;String&gt; fetchNewUsername() {...} /* Повертає нове ім’я користувача, 
яке можна використовувати для заміни старого. */</pre>
  <p>Використовуйте <code>async</code>і <code>await</code> для реалізації асинхронної функції <code>changeUsername()</code>, що має робити наступне:</p>
  <ul>
    <li>Функція має викликати надану асинхронну функцію <code>fetchNewUsername()</code> і повертати її результат.</li>
    <ul>
      <li>Приклад поверненого значення з <code>changeUsername()</code>: <code>&quot;jane_smith_92&quot;</code></li>
    </ul>
    <li>Ловить (<em>catch</em>) будь-яку помилку, що виникає, і повертає значення рядка помилки.</li>
    <ul>
      <li>Ви можете використовувати метод <a href="https://api.dart.dev/stable/dart-core/ArgumentError/toString.html" target="_blank">toString()</a> як для <a href="https://api.dart.dev/stable/dart-core/Exception-class.html" target="_blank">винятків (Exception)</a>, так і для <a href="https://api.dart.dev/stable/dart-core/Error-class.html" target="_blank">помилок (Error)</a></li>
    </ul>
  </ul>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=5f14e25ba35b3bcd08a982ee95fa7d33&split=75&null_safety=true"></iframe>
  </figure>
  <h3>Вправа: Складемо все докупи</h3>
  <p>Настав час потренуватися в тому, що ви дізналися, в одній заключній вправі. Для імітації асинхронних операцій ця вправа надає асинхронні функції <code>fetchUsername()</code>та <code>logoutUser()</code>:</p>
  <pre data-lang="dart">Future&lt;String&gt; fetchUsername() {...}// Повертає ім&#x27;я поточного користувача
Future&lt;String&gt; logoutUser() {...}
/* Виконує вихід із поточного користувача
та повертає ім’я користувача, що вийшов з системи */</pre>
  <p>Напишіть наступне:</p>
  <p><strong>Частина 1:</strong> <code>addHello()</code></p>
  <ul>
    <li>Напишіть функцію, <code>addHello()</code>яка приймає один аргумент String.</li>
    <li>Функція <code>addHello()</code> повертає свій аргумент типу String, перед яким стоїть &quot;Hello&quot;.<br />Приклад: <code>addHello(&#x27;Jon&#x27;)</code>повертає <code>&#x27;Hello Jon&#x27;</code>.</li>
  </ul>
  <p><strong>Частина 2: </strong><code>greetUser()</code></p>
  <ul>
    <li>Напишіть функцію, <code>greetUser()</code>, що не приймає аргументів.</li>
    <li>Щоб отримати ім’я користувача, <code>greetUser()</code>викликає надану асинхронну функцію <code>fetchUsername()</code>.</li>
    <li><code>greetUser()</code> створює привітання для користувача, викликаючи <code>addHello()</code>, передаючи йому ім’я користувача та повертаючи результат.<br />Приклад: Якщо <code>fetchUsername()</code> повертає <code>&#x27;Jenny&#x27;</code>, то <code>greetUser()</code>повертає <code>&#x27;Hello Jenny&#x27;</code>.</li>
  </ul>
  <p><strong>Частина 3: </strong><code>sayGoodbye()</code></p>
  <ul>
    <li>Напишіть функцію <code>sayGoodbye()</code>, яка робить наступне:</li>
    <ul>
      <li>Не приймає аргументів.</li>
      <li>Ловить будь-які помилки.</li>
      <li>Викликає надану асинхронну функцію <code>logoutUser()</code>.</li>
    </ul>
    <li>Якщо <code>logoutUser()</code> не вдається, <code>sayGoodbye()</code>повертає довільний рядок String.</li>
    <li>У разі успіху <code>logoutUser()</code>, <code>sayGoodbye()</code>повертає рядок <code>&#x27;&lt;result&gt; Thanks, see you next time&#x27;</code>, де <code>&lt;result&gt;</code> — значення рядка, яке повертається за допомогою виклику <code>logoutUser()</code>.</li>
  </ul>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-dart.html?id=89d2f295f7b006069e26bf71182f03c0&split=75&null_safety=true"></iframe>
  </figure>
  <h2>Що далі?</h2>
  <p>Вітаємо, ви закінчили роботу з кодовою лабораторією! Якщо ви хочете дізнатись більше, ось кілька підказок, куди йти далі:</p>
  <ul>
    <li>Експериментуйте у <a href="https://translate.google.com/website?sl=en&tl=uk&ajax=1&u=https://dartpad.dev" target="_blank">DartPad.</a></li>
    <li>Спробуйте іншу <a href="https://dart.dev/codelabs" target="_blank">кодову лабораторію [англ.]</a>.</li>
    <li>Дізнайтеся більше про Future та асинхронність:</li>
    <ul>
      <li><a href="https://dart.dev/tutorials/language/streams" target="_blank">Ознайомлення з Stream [англ.]</a>: Дізнайтеся, як працювати з послідовністю асинхронних подій.</li>
      <li><a href="https://www.youtube.com/playlist?list=PLjxrf2q8roU0Net_g1NT5_vOO3s_FR02J" target="_blank">Відео про Dart від Google [англ.]</a>: перегляньте одне або кілька відео про асинхронне кодування. Або, якщо хочете, прочитайте статті, основані на цих відео. (Почніть із <a href="https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a" target="_blank">ізоляцій та циклів подій [англ.]</a>).</li>
    </ul>
    <li><a href="https://dart.dev/get-dart" target="_blank">Завантажте Dart SDK</a>.</li>
  </ul>
  <p>Переклад виконано спеціально для <a href="https://t.me/dart_itkpi" target="_blank">@dart_itkpi</a>.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@itkpi_dart/Icon-widget</guid><link>https://teletype.in/@itkpi_dart/Icon-widget?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart</link><comments>https://teletype.in/@itkpi_dart/Icon-widget?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=itkpi_dart#comments</comments><dc:creator>itkpi_dart</dc:creator><title>Приборкуємо Icon: як він працює зсередини?</title><pubDate>Fri, 30 Apr 2021 09:42:46 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/85/eb/85eb37b6-b665-4a4d-8595-8ff3104a5a89.png"></media:content><category>Flutter</category><description><![CDATA[<img src="https://teletype.in/files/b3/98/b3986097-0bf3-4f8c-a2f4-cc22a8ffd7ad.png"></img>У даній статті ми розглянемо віджет Icon, а також розкажемо про лайфхак, завдяки якому значення для піктограми можна задавати динамічно: наприклад, передавати з сервера.]]></description><content:encoded><![CDATA[
  <p>У даній статті ми розглянемо віджет Icon, а також розкажемо про лайфхак, завдяки якому значення для піктограми можна задавати динамічно: наприклад, передавати з сервера.</p>
  <blockquote>Також ми переклали цю статтю <a href="https://teletype.in/@itkpi_dart_en/Icon-widget" target="_blank">англійською</a></blockquote>
  <figure class="m_column">
    <img src="https://teletype.in/files/b3/98/b3986097-0bf3-4f8c-a2f4-cc22a8ffd7ad.png" width="1200" />
  </figure>
  <h3>Віджет Icon</h3>
  <p>У Material Framework, що входить до Flutter, є віджет Icon. Даний віджет використовується, щоб відобразити яку-небудь піктограму. Його відмінність від віджету Image у тому, що даний віджет підпорядковується встановленій темі та може бути інтегрований з віджетом, у якому він використовується (наприклад, піктограма відповідно змінює свій колір у віджеті IconButton).</p>
  <p>Розглянемо, які параметри приймає даний віджет:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/1a2a609f85b683058e7e8.png" width="515" />
    <figcaption>Приклад роботи віджету Icon</figcaption>
  </figure>
  <p>Наведений приклад виводить піктограму з колекції Material Icons. Розберемо параметри:</p>
  <ul>
    <li><strong>Перший позиційний параметр</strong>: значення типу IconData. Його ми розглянемо нижче</li>
    <li><strong>Непозиційний параметр color</strong>: значення типу Color щоб, відповідно, перефарбувати піктограму</li>
    <li><strong>Непозиційний параметр size</strong>: дійсне число, розмір піктограми у пікселях</li>
  </ul>
  <h3>Віджет ImageIcon</h3>
  <p>Даний віджет дозволяє використовувати растрові зображення для простого створення власних піктограм:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/588379883c9b8e3b06df2.png" width="831" />
    <figcaption>Приклад роботи віджету ImageIcon</figcaption>
  </figure>
  <p>Синтаксис аналогічний до Icon, окрім того, що у перший позиційний параметр передається об&#x27;єкт типу ImageProvider. ImageProvider це об&#x27;єкт у Flutter, що відповідає за отримання зображення іншими віджетами. Наприклад, віджетом Image.</p>
  <p>Скоріш за все, ви ніколи не працюватимете з ImageProvider напряму, натомість, ви будете використовувати одну з готових реалізацій:</p>
  <ul>
    <li>AssetImage — отримання зображення з асетів Flutter (<a href="https://api.flutter.dev/flutter/painting/AssetImage-class.html" target="_blank">див. документацію щодо того, як додати зображення до списку асетів</a>)</li>
    <li>NetworkImage — завантаження зображення по HTTP</li>
    <li>FileImage — отримання зображення з об&#x27;єкту File</li>
    <li>MemoryImage — отримання зображення з масиву байтів у оперативній пам&#x27;яті</li>
    <li>ExactAssetImage — аналог AssetImage, <a href="https://api.flutter.dev/flutter/painting/ExactAssetImage-class.html" target="_blank">див. документацію щодо особливостей</a></li>
  </ul>
  <p>У даному прикладі використовується зображення-асет.</p>
  <p>Слід зазначити, що кольорові піктограми будуть приведені до монохромного вигляду за каналом прозорості:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/4f718759c7e7a1545f657.png" width="330" />
    <figcaption>Зміна кольору у ImageIcon</figcaption>
  </figure>
  <h3>Колекція Icons та CupertinoIcons</h3>
  <p>Дані класи містять у собі колекції піктограм для Material Design та iOS відповідно. Слід зазначити, що між собою дані колекції не сумісні: якщо ви розробляєте такий застосунок, що використовує Material Icons на Android, а Cupertino — на iOS, вам доведеться власноруч вказувати відповідну піктограму, або, наприклад, скористатись бібліотекою <a href="https://pub.dev/packages/flutter_platform_widgets" target="_blank">flutter_platform_widgets</a>, яка пропонує обмежений список піктограм, що відповідають одна одній.</p>
  <p>Список усіх піктограм для Material Design знаходиться на сторінці <a href="https://fonts.google.com/icons" target="_blank">Material Icons</a>. Кожна піктограма представлена у чотирьох стилях: звичайний, Rounded, Sharp та Outline. Щоб звернутися до необхідного стилю, слід додати до назви піктограми відповідне закінчення:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/82e157dd459fbe3e631b8.png" width="470" />
    <figcaption>Використання різних стилів для піктограм</figcaption>
  </figure>
  <p>Список усіх наявних Cupertino Icons наведено на <a href="https://flutter.github.io/cupertino_icons/" target="_blank">сторінці бібліотеки</a>.</p>
  <h3>Піктограми та теми</h3>
  <p>Усі компоненти Material Framework підпорядковуються заданій темі. Тема задається у віджеті MaterialApp, завдяки об&#x27;єкту ThemeData та властивості theme.</p>
  <p>Ви можете глобально змінити стиль за замовчуванням для усіх піктограм у вашому застосунку, використовуючи властивість iconTheme у ThemeData:</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/c8/33/c833f421-1609-48f1-806c-335af36a6683.png" width="537" />
    <figcaption>Приклад використання теми для задання стилю піктограм</figcaption>
  </figure>
  <p>Але крім цього, тему можна перезаписати безпосередньо у дереві віджетів, використовуючи віджет IconTheme (варто помітити, при цьому на перезаписану піктограму властивості теми діяти припиняють):</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/54b6e251a9ba96c44659a.png" width="545" />
    <figcaption>Приклад локального перезапису теми піктограми</figcaption>
  </figure>
  <h3>Як працюють векторні піктограми</h3>
  <p>У Flutter для роботи векторних піктограм використовуються так звані <em>icon fonts</em>, тобто шрифти, що використовують символи для виводу піктограм. Насправді, коли ви використовуєте віджет Icon, Flutter створює віджет Text з спеціально заданим шрифтом. Ми переконаємося у цьому далі.</p>
  <h3>Тип IconData</h3>
  <p>IconData виступає контейнером для даних піктограми, що має бути виведено. Саме об&#x27;єкти цього типу зберігаються у колекції Icons: наприклад, Icons.people є об&#x27;єктом IconData. Давайте розглянемо конструктор цього об&#x27;єкта:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/affb39feeb60f37987ce3.png" width="487" />
    <figcaption>Конструктор IconData — саме так зберігаються піктограми у колекції Icons</figcaption>
  </figure>
  <p><strong>Першим позиційним параметром</strong> є ціле число, що позначає номер символу, який буде використано для поточної піктограми.</p>
  <p><strong>Параметр fontFamily</strong> містить назву шрифта, що використовується для виводу піктограми.</p>
  <p>Таким чином, вивід піктограми зводиться до віджету Text (\ue8f5 - позначення символу під номером e8f5):</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/e14aff67f0c19f209b4fb.png" width="756" />
    <figcaption>Аналог віджету Icon, реалізований на Text</figcaption>
  </figure>
  <p>Крім того ж, у поле тексту можна помістити читабельну назву піктограми:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/b5727c84e9d418b3a45e5.png" width="753" />
    <figcaption>Замінюємо &#x27;\ue8f5&#x27; на &#x27;people&#x27; — віджет продовжує функціонувати</figcaption>
  </figure>
  <p>Слід зауважити, що конструктор IconData є константним, що дозволяє компілятору виключити невикористані піктограми з програми.</p>
  <h3>Динамічні піктограми й інтеграція з Material Framework</h3>
  <blockquote>❗ Робіть зазначене нижче тільки якщо вам дійсно це треба. Дана практика шкодить розміру застосунку. Замість цього, ви можете, наприклад, завантажувати піктограму з мережі через NetworkImage та використовувати ImageIcon.</blockquote>
  <p>Таким чином можна зробити висновок, що використовуючи віджет Text можна відобразити піктограму за її кодом або навіть назвою. Проте, у такого підходу є суттєвий недолік: відсутність інтеграції з темою та Material Framework у цілому, адже Flutter сприймає таку піктограму як звичайний текст. Тому, ми напишемо власний об&#x27;єкт Icon, що не є константою, та який приймає назву піктограми для її відображення (див. кінець статті).</p>
  <p>Використання:</p>
  <figure class="m_original">
    <img src="https://telegra.ph/file/ad7317c106c711b91638e.png" width="517" />
    <figcaption>Приклад використання DynamicIcon</figcaption>
  </figure>
  <p>Увага, через те, що конструктор не константний, tree shaking не працюватиме, що призведе до помилки компіляції, але дану поведінку можна виключили прапорцем збирання <code>--no-tree-shake-icons</code>.</p>
  <p>Приєднуйтесь до нашого <a href="https://t.me/itkpi_dart" target="_blank">чату</a>.</p>
  <h3>Віджет DynamicIcon</h3>
  <figure class="m_column">
    <iframe src="https://dartpad.dev/embed-flutter.html?id=cff07341614d667d7ef9e482524c5df4&split=75&null_safety=true"></iframe>
  </figure>

]]></content:encoded></item></channel></rss>