Создание первого приложения Flutter

Создание первого приложения Flutter, часть 1

1. Введение

Flutter это SDK для создания мобильных приложений от Google под iOS и Android. С Flutter можно переиспользовать код, им пользуются разработчики по всему миру, он имеет открытый исходный код и бесплатен.

В этом уроке вы создадите несложное приложение Flutter. Если вы знакомы с объектно-ориентированным программированием и основами разработки, такими как переменные, циклы и условия, у вас всё должно получиться. Опыта разработки на Dart или под мобильные устройства не требуется.

Что мы узнаем в этом уроке

  • Как написать приложение на Flutter, выглядящее нативно и в  iOS, и в Android.
  • Базовую структуру приложений Flutter.
  • Поиск и использование пакетов для расширения функционала.
  • Использование горячей перезагрузки (hot reload) для удобства разработки.
  • Как реализовать stateful-виджет.
  • Как создать бесконечный список с «ленивой» загрузкой.

Во второй части мы добавим интерактивность, изменим тему приложения, добавим возможность перехода на другую страницу (называемую роутами (route) во Flutter).

Что мы сделаем в первой части

Вы сделаете простое мобильное приложение, генерирующее наименования для стартапов. Пользователь сможет выбирать и отменять выбор названий, сохраняя понравившиеся. Код будет «лениво» генерировать десять названий за один раз. По мере прокрутки пользователем списка, новые названия будут создаваться в фоне и подгружаться. Таким образом, пользователь сможет прокручивать список вечно.

В анимированном GIF показано, как будет выглядеть приложение в конце этой части:

Создание первого приложения Flutter, часть 1

2. Настройте окружение Flutter

Всё, что вам нужно для этого: Flutter SDK и редактор кода. В этом уроке предполагается, что вы выберете Android Studio, но выбор редактора остаётся за вами.

Протестировать работу приложения можно будет с этими устройствами:

  • Физическое устройство (Android или iOS), подключенное к компьютеру с настроенным режимом разработчика.
  • Симулятор iOS. (Требуется установка XCode)
  • Эмулятор Android. (Требуется установленная Android Studio)

3. Создаём каркас приложения Flutter

Для этого воспользуйтесь инструкцией Getting Started with your first Flutter app (возможно, позже её перевод будет доступен). Назовите проект startup_namer (вместо myapp). В дальнейшем мы будем дорабатывать это приложение.

Совет: Если вы не видите пункт «New Flutter Project» в вашей IDE, убедитесь, что она настроена для работы с Flutter и Dart.

В этом уроке мы будем преимущественно править файл lib/main.dart, в котором находится логика приложения.

Замените содержимое файла lib/main.dart.
Для этого удалите весь код в нём. Затем вставьте в него код ниже, выводящий надпись «Hello World» по центру экрана.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: const Text('Welcome to Flutter'),
        ),
        body: const Center(
          child: const Text('Hello World'),
        ),
      ),
    );
  }
}

Замечание: При вставке кода могут исказиться отступы. Вот как это поправить:
   — Android Studio / IntelliJ IDEA: кликните правой кнопкой на коде и выберите Reformat Code with dartfmt.
   — VS Code: кликните правой кнопкой и выберите Format Document.
   — Terminal: запустите flutter format <имя файла>.

Запустите приложение. Вы должны увидеть приметно такое, в зависимости от того, Android или iOS у вас:

Замечание:  При первом запуске на физическом устройстве может пройти много времени. После этого будет работать hot reload. При нажатии на кнопку Save также будет запущен hot reload при работающей программе.

Подытожим

  • В этом примере создаётся приложение Material. Material — это описание визуального представления, он является стандартом в web и на мобильных платформах. Flutter содержит огромный спектр виджетов типа Material.
  • Метод main использует нотацию с большой стрелкой (=>), такая нотация используется в однострочных функциях.
  • Приложение наследует StatelessWidget, следовательно всё приложение также является виджетом. Во Flutter практически всё является виджетом, включая выравнивание, отступы, макет.
  • Виджет Scaffold из библиотеки Material предоставляет панель приложения, заголовок и свойство body, которое отвечает за иерархию виджетов на основном экране. И эта иерархия может быть иногда очень огромной.
  • Главная задача виджета — вернуть метод build, который описывает как нужно вывести виджет по отношению к соседним.
  • body в этом примере состоит из виджета Center, содержащего дочерний виджет Text. Виджет Center выравнивает всю иерархию потомков по центру экрана.

4. Используем сторонние пакеты

Сейчас вы научитесь работать с внешними пакетами, на примере пакета english_words, содержащего несколько тысяч популярных английских слов и утилиты для удобной работы с ними.

Найти пакет english_words, так же, как и многие другие пакеты с открытым исходным кодом, вы можете на pub.dartlang.org.

Файл pubspec управляет списком зависимостей приложения Flutter. В конец pubspec.yaml добавим english_words: ^3.1.0 (english_words версии 3.1.0 или выше) в список зависимостей (dependencies):

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0   # добавьте эту строку

Если вы открыли файл pubspec в Android Studio, кликните Packages get. Эта команда загрузит пакет из сети. В консоли вы должны увидеть что-то подобное:

flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0

Откроем lib/main.dart, импортируем пакет:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';  // добавьте эту строку.

По мере написания, Android Studio будет предлагать библиотеки для импорта. Но добавленная строка сразу окрасится серым цветом, показывая неиспользуемый (пока) импорт.

Пришло время применить пакет english_words, пусть он генерит текст вместо текущей статичной надписи «Hello World».

Сделайте следующие изменения:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random(); // добавьте эту строку.
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(    // Замените "const" на "new".
          //child: const Text('Hello World'),   // Замените этот текст...
          child: new Text(wordPair.asPascalCase),  // этим.
        ),
      ),
    );
  }
}

Замечание: «Pascal case» (так же известный как CamelCase («ВерблюжийРегистр»)), означает, что каждое слово в строке, включая первое, начинается с большой буквы. Таким образом, «upper camel case» становится «UpperCamelCase».

Если не заменить Center на «new Center», система типов покажет предупреждение о том, что объект Center не должен быть константой (const), потому что его потомок, объект Text больше не является константой. Поэтому и Center и Text должны создавать экземпляры с помощью new.

Если приложение уже запущено, используйте для перезапуска кнопку hot reload. При каждом перезапуске или холодном запуске проекта, вы будете видеть в приложении разные пары слов, выбранные в случайном порядке. Это происходит потому, что фразы создаются внутри метода build, запускаемого каждый раз при запросе от MaterialApp на перерисовку, или же переключении Platform в Flutter Inspector.

Проблемы?

Если ваше приложение не работает, проверьте опечатки. Но если не разобрались, вот код по ссылкам, скачайте его и замените у себя:

5. Добавим stateful виджет

Stateless-виджеты неизменяемые (immutable), это значит, что их свойства не могут меняться  — все их значения завершённые (final).

Stateful виджеты поддерживают смену состояния. Реализация такого виджета подразумевает создание минимум двух классов: 1) класс StatefulWidget создающий экземпляр класса 2) State. Класс StatefulWidget сам по себе неизменяемый (immutable), однако класс State сохраняется на протяжении всего жизненного цикла виджета.

На этом шаге вы добавите stateful-виджет RandomWords, который создаст свой State-класс, RandomWordsState. Затем вы добавите RandomWords как дочерний элемент stateless-виджета MyApp.

Создадим минимальный класс состояния (state). Он может располагаться в любом месте файла в MyApp, но в этом решении он расположен в конце файла. Добавьте эти строки:

class RandomWordsState extends State<RandomWords> {
  // TODO Добавить метод build
}

Обратите внимание на объявление State<RandomWords>. Это указывает на то, что мы используем универсальный класс State, специализированный для использования с RandomWords. Большинство логики приложения и состояние находится здесь для поддержки состояния виджета RandomWords. Этот класс сохраняет сгенерированные пары слов, количество которых растёт бесконечно по мере прокрутки пользователя, так же как и избранных пар слов (будет в части 2), которые пользователь добавляет или удаляет в списке, нажимая кнопку в виде сердечка.

RandomWordsState зависит от класса RandomWords. Сейчас добавим его.

Добавьте stateful-виджет RandomWords в main.dart. Виджет RandomWords делает немного больше, чем просто создаёт State-класс:

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

После этого IDE сообщит, что в классе отсутствует метод build. Добавим метод build, создающий пары слов, переместив этот код из MyApp в RandomWordsState. Добавьте код:

class RandomWordsState extends State<RandomWords> {
  @override                                  // Добавим с этой строки ... 
  Widget build(BuildContext context) {
    final WordPair wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }                                          // ... до этой.
}

Удалим часть кода из MyApp:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final WordPair wordPair = new WordPair.random();  // Удалите эту строку.

    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          //child: new Text(wordPair.asPascalCase), // А эту замените... 
          child: new RandomWords(),                 // ... на эту.
        ),
      ),
    );
  }
}

Перезапустите приложение. Ничего не должно измениться в поведении, приложение так же должно выводить пары слов при сохранении кода или перезапуске.

Совет: Если вы видите следующее сообщение при горячей перезагрузке, лучше полностью перезапустить его:
Reloading…
Not all changed program elements ran during view reassembly; consider restarting.

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

Проблемы?

Если ваше приложение не корректно работает, рабочий код по ссылке:

6. Создание ListView с бесконечной прокруткой

В этом шаге мы расширим RandomWordsState таким образм, чтобы выводился список пар слов. По мере его прокрутки, список (представленный виджетом ListView) будет расти бесконечно. Конструктор builder в ListView позволяет создавать списковые представления на лету, по запросу.

Добавьте список _suggestions в класс RandomWordsState для хранения предложенных пар. Также добавьте переменную _biggerFont для увеличения размера шрифта.

Замечание:  Добавление префикса в виде подчёркнутой линии к имени переменной делает её закрытой (private) в языке Dart.

class RandomWordsState extends State<RandomWords> {
  // добавьте следующие две строки.
  final List<WordPair> _suggestions = <WordPair>[];
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0); 
  ...
}

Затем добавьте функцию _buildSuggestions() в класс RandomWordsState. Этот метод создаст ListView, который выведет сгенерированные пары слов.

Класс ListView предоставляет свойство itemBuilder, это построитель и анонимная функция с обратным вызовом. Она принимает два параметра — BuildContext, и переменную-итератор i. Итератор начинается с 0 и увеличивается при каждом вызове функции, т.е. каждый раз при генерации новой пары слов. Такая модель позволяет списку предложений расти постепенно при перемотке списка пользователем.

Добавьте функцию _buildSuggestions, описанную ниже, в класс RandomWordsState (delete the comments, if you prefer):

  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // The itemBuilder callback is called once per suggested 
      // word pairing, and places each suggestion into a ListTile
      // row. For even rows, the function adds a ListTile row for
      // the word pairing. For odd rows, the function adds a 
      // Divider widget to visually separate the entries. Note that
      // the divider may be difficult to see on smaller devices.
      itemBuilder: (BuildContext _context, int i) {
        // Add a one-pixel-high divider widget before each row 
        // in the ListView.
        if (i.isOdd) {
          return new Divider();
        }

        // The syntax "i ~/ 2" divides i by 2 and returns an 
        // integer result.
        // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
        // This calculates the actual number of word pairings 
        // in the ListView,minus the divider widgets.
        final int index = i ~/ 2;
        // If you've reached the end of the available word
        // pairings...
        if (index >= _suggestions.length) {
          // ...then generate 10 more and add them to the 
          // suggestions list.
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }

Функция _buildSuggestions вызывает _buildRow для каждой пары слов. Она выводит созданную пару в ListTile, и это позволит вам сделать ряды более привлекательными в части 2.

Добавьте функцию _buildRow в RandomWordsState:

  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }

Обновите метод build в RandomWordsState с добавлением _buildSuggestions() вместо прямого вызова библиотеки генерации слов. (Scaffold реализует базовый визуальный стиль в Material Design)

  @override
  Widget build(BuildContext context) {
    //final wordPair = new WordPair.random(); // удалить эти... 
    //return new Text(wordPair.asPascalCase); // ... две строки.

    return new Scaffold (                   // Добавить отсюда... 
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );                                      // ... досюда.
  }

Обновим метод build в MyApp, сменим заголовок и основной экран на виджет RandomWords.

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }

Перезапустим приложение. Вы должны увидеть бесконечный список пар слов, не важно насколько далеко вы пролистаете.

Проблемы?

Если ваше приложение работает не так, как должно, скачайте код по ссылке ниже и замените им свой, чтобы продолжить работу.

7. Что дальше

Поздравляем!

Вы закончили первую часть! Если хотите продолжить, переходите в часть 2, где сможете доработать приложение:

  • Добавить интерактивность.
  • Добавить навигацию.
  • Сменить цвет темы.

В конце части 2 приложение будет таким:

Создание первого приложения Flutter, часть 1

Подписывайтесь на новости Flutter! https://t.me/flutterdaily

Перевод «Write Your First Flutter App, part 1»

Создание первого приложения Flutter, часть 1

Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.

Пишите: @ighar. Buy me a coffee, please :).

Leave a Comment

Чтобы не пропустить новые статьи, оставь свой Email

Поздравляем вы подписаны на новости ТехноДжем!

TВо время отправки данных произошла ошибка. Попробуйте ещё раз

Оставляя свою почту