Планирование, разработка и развертывание прототипа Todo-App. Разверните его на Android и Google Cloud Platform.

Todo-App идеально подходит для изучения языка программирования. Это требует ввода данных пользователем, состояния удержания и обновления пользовательского интерфейса. Однако большинство курсов и руководств затрагивают только поверхность проблемы. Если вы знакомы с базовыми концепциями, не существует курсов, которые могли бы улучшить ваши знания.

Одно лишь программирование - не единственное, что мешает разработчикам спать по ночам. Планирование и развертывание программного обеспечения - это навыки, о которых нам нужно поговорить подробнее. Мы должны делиться своими знаниями, даже если мы не все делаем идеально. Это единственный способ начать обсуждение наших навыков и выйти на более высокий уровень.

Итак, давайте переосмыслим простое старое приложение Todo-App, чтобы сделать его привлекательным в качестве учебного проекта для более продвинутых разработчиков.

Это подходит тебе?

Мотивация и требования.

Зачем вам делать еще одно Todo-приложение? Вы, наверное, уже видели тысячи из них.

Основное внимание в этом курсе уделяется всему, что связано с Todo-App. Я покажу базовый рабочий процесс для планирования приложения, разработки, добавления функций и развертывания в Интернете и на мобильных устройствах.

Приложение, которое я буду создавать, будет базовым, так что не ожидайте фантастически выглядящего пользовательского интерфейса. Задача сделать это красивым - на ваше усмотрение.

Главный вывод этого курса - получить представление о большой картине веб-разработки. Это также должно побудить вас создать больше прототипов и улучшить свои навыки.

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

Вам потребуется настроенная Android Studio и учетная запись GCP. Я предоставил ссылки на документацию о том, как это сделать там, где это требуется. Если у вас нет телефона Android, вы можете использовать эмулятор Android Studio. Вы также можете развернуть на iOS с небольшими изменениями.

«План с ошибками приводит к провалу проекта». - Из далекой-далекой Галактики.

План

Создайте приложение Todo-App!

Простое приложение Todo требует добавления Todos в список, перечисления Todos и переключения их статуса. Более продвинутые приложения также позволяют фильтровать Todos по их состоянию.

Более того …

  • Мы добавим фильтр даты в наше Todo-приложение. Работа с датами - одна из наиболее сложных задач. Мы также хотим добавить больше шума кода в наш проект, чтобы продемонстрировать сложные рабочие процессы приложений. Это означает добавление еще одного модуля и последующее разделение проблем в нашем коде (в результате мы получим Calender-Module рядом с нашим Todo-модулем).
  • Мы развернем наше приложение в Интернете с помощью Google Cloud Platform и на мобильных устройствах (Android) с помощью Capacitor. Мы хотим показать, как развертывать веб-проекты для различных целей и как использовать сложные фреймворки.
  • Мы создадим пользовательский интерфейс с помощью React. Многие компании ищут людей со знаниями React. Таким образом, решение здесь также для вашего резюме, а не только для приложения.
  • Все данные мы будем хранить в SQLite. Таким образом, у нас не будет никаких проблем с конфиденциальностью данных, потому что данные всегда будут оставаться на стороне клиента.

Этот план определяет наши эпосы и задачи.

Упражнение: Вы бы что-нибудь еще добавили? Создайте набросок своего собственного приложения.

Epic: Инфраструктура
Задачи: настроить базовый проект, развернуть на GCP, развернуть на Android, добавить сценарии npm для удобного развертывания

Epic: Календарь
Задачи: создание представления календаря (UI) с возможностью навигации, реализация логики (логики) навигации

Эпический: Задачи
Задачи: добавление задачи, список задач, обновление задачи, фильтрация задач (логика), создание списка задач (пользовательский интерфейс)

Список задач не исчерпывающий, но идея должна проясняться. Вы можете отразить это в Trello или Jira в качестве упражнения. Также: вы можете определить больше задач и оценить время, которое вам потребуется для каждой работы.

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

Вы можете возразить, что сделать правильную оценку невозможно, потому что у вас еще нет необходимых навыков. Правда в том, что это реальность многих разработчиков. В оценках технологий, с которыми вы не знакомы, нет ничего необычного. Вы можете попытаться угадать сложность, если раньше работали с подобными вещами. Например: вы можете попытаться оценить время, которое вам понадобится для изучения SQLite и выполнения задачи, если вы когда-либо работали с другой реляционной БД.

«Лучший источник уверенности - это опыт». - Из далекой-далекой Галактики.

Выполнение

Давайте погрузимся в код! Почему бы и нет ?!

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

Это хорошо известная стратегия - написать рабочий прототип того, что вы планируете. Не стоит тратить на это столько усилий - просто сделайте его функциональным. Затем выбросьте его и используйте свои новые знания, чтобы составить план. Итерация - это обучение.

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

Создавая прототип, обязательно поставьте перед собой цель. Вот несколько примеров правильных целей:

  • Изучите неизвестные технологии, прежде чем приступить к разработке полного приложения.
  • Подтверждение концепции для конкретной части приложения (концепция UI / UX, логическая концепция, инфраструктура,…)
  • Протестируйте инфраструктуру или даже определенные стили кода. Легче рассуждать о том, что вы видите.
  • Выиграй клиента.

Цели определяют ваш фокус. Например, мы не будем беспокоиться о пользовательском интерфейсе, потому что наша основная цель - эффект обучения. Пользовательский интерфейс добавит лишнего шума, что противоречит нашей основной цели.

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

Построим минимальный подход к функционалу; мы также не поймем всех ошибок.

Подсказка: наилучшие результаты обучения будут достигнуты при планировании, написании и развертывании настоящего Todo-приложения после написания рабочего прототипа.

Базовая настройка проекта

Мы будем использовать приложение Create React (CRA) с шаблоном Typescript в качестве нашей основы и инициализировать Capacitor. Создайте новый проект с помощью следующих команд:

npx create-react-app next-todo-app --template typescript
cd next-todo-app
// yarn
yarn add @capacitor/core @capacitor/cli
npx cap init
yarn build
// or npm
npm i -S @capacitor/core @capacitor/cli
npx cap init
npm run build

Откройте capacitor.config.json и измените webDir на build. webDir будет содержать наше статическое приложение, которое будет развернуто на платформе. Поскольку каталог build является выходным каталогом по умолчанию для CRA, мы должны настроить этот параметр.

После этого запуска (это будет работать, только если у вас есть рабочая среда Android dev):

npx cap add android

Подсказка: если у вас не настроена среда разработки Android, вы можете ознакомиться с этой замечательной статьей из документации Unity3D: https://docs.unity3d.com/2018.2/Documentation/Manual/android-sdksetup.html

Наше приложение загружено. Мы инициализировали базовый проект и добавили платформу Android.

Тестирование на платформе

Выполните следующие команды, чтобы создать наш CRA для вашей платформы:

yarn build
npx cap copy
// or
npm run build
npx cap copy

Мы должны копировать наши файлы в наш Android-проект каждый раз, когда вносим изменения. Возможно, вы захотите написать сценарий npm, который будет обрабатывать нечеткость за вас.

Теперь ваш телефон должен быть подключен к компьютеру. Он также должен быть в developer mode с включенным USB-Debugging.

Вы можете открыть проект Android Studio с помощью npx cap open android. Это запустит Android Studio в каталоге вашего проекта (в корневом каталоге есть папка android). Вы должны увидеть, что Gradle немедленно начинает синхронизацию ваших зависимостей (это сообщение на информационной панели в левом нижнем углу программы). Он автоматически попытается создать ваше приложение после завершения синхронизации.

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

Run -> Run 'app'

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

✅ Задача завершена: «Настройка базового проекта».
✅ Задача завершена: «Развернуть на Android».

Рабочий процесс GCP

Теперь, когда мы понимаем, как тестировать и развертывать на нашем Android-устройстве, давайте поговорим о нашем веб-сайте.

Мы также хотим, чтобы наш проект запускался в браузере. Если вы предпочитаете работать на AWS, а не на GCP, я рекомендую мою другую статью о развертывании статического CRA на S3: https://medium.com/swlh/aws-cdk-and-typescript-deploy-a-static-react-app -to-s3-df74193e9e3d .

Мы будем использовать инструмент командной строки gcloud для развертывания нашего приложения в Google Cloud App Engine (настройте клик https://cloud.google.com/sdk/docs/quickstarts).

// create a new project
// the name must be unique
gcloud projects create my-todo-app-2-0
// set project as default for all cmds
gcloud config set project my-todo-app-2-0

Добавьте новый файл в корень вашего проекта и назовите его app.yaml. Этот файл будет содержать необходимую информацию о развертывании.

Время выполнения не имеет значения.
Обработчики определяют, где веб-сервер должен искать наши индексные документы.

Мы также должны исключить некоторые каталоги из развертывания. Создайте .gcloudignore рядом с app.yaml в корневом каталоге вашего проекта.

Вы можете развернуть свое приложение, используя gcloud app deploy после сохранения этих файлов.

После успешного развертывания вы сможете перейти к target url, под которым был развернут ваш проект. Или используйте gcloud app browse.

✅ Задача выполнена: «Развернуть в GCP».

Добавить удобные скрипты

Теперь, когда мы знаем, как развертывать наши проекты, мы добавим несколько удобных скриптов в наш package.json.

Развертывание на Android требует от нас сборки и копирования. Добавьте в свой package.json следующий скрипт:

"deploy:android": "yarn build && cap copy"

Нам еще предстоит кое-что сделать вручную, например npx cap open android, а затем нажать Run -> Run 'app'. Мы не будем исправлять это в этом курсе. Мы также не будем говорить о публикации в App Store (это довольно просто).

Развертывание в Интернете немного проще упростить:

"deploy:web": "yarn build && gcloud app deploy -q"

Это автоматически обновит онлайн-версию вашего приложения. Параметр -q (тихо) подавляет подсказку.

Давайте также добавим скрипт для развертывания всего:

"deploy": "npm run deploy:web && npm run deploy:android"

Теперь, когда мы хотим обновить наш проект, мы будем использовать yarn deploy.

Очистить проект

Мне нравится удалять весь ненужный код, который я не планирую использовать. Вы можете удалить любые файлы, кроме index.html, из общей папки. Они только добавляют лишнего шума. Не забудьте удалить ссылки на эти файлы в самом index.html.

Избавьтесь от всех test файлов в нашем src каталоге. Удалите изображение и serviceWorker.ts (не забудьте также удалить его из index.tsx) очистите App.css и очистите свой App.tsx следующим образом:

import React from "react";
function App() {
  return (
    <div className="App">
    </div>
  );
}
export default App;

Добавьте необходимые зависимости и обновите проект Android.

Нам нужна библиотека для добавления возможностей SQLite в наше приложение.

yarn add capacitor-data-storage-sqlite
// or
npm i -S capacitor-data-storage-sqlite

После добавления зависимости мы должны скопировать ее в наш проект Android и зарегистрировать там. Мы не сможем использовать SQLite на устройствах Android, если пропустим этот шаг.

Отредактируйте /android/app/src/main/java/my/todo/app/MainActivity.java и добавьте дополнительный импорт

import com.jeep.plugin.capacitor.CapacitorDataStorageSqlite;

Также добавьте возможность к методу onCreate:

add(CapacitorDataStorageSqlite.class);

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

npx cap update

В консоли вы должны увидеть что-то вроде следующего:

√ Updating Android plugins in 60.36ms
  Found 1 Capacitor plugin for android:
    capacitor-data-storage-sqlite (2.1.0) <---
√ update android in 196.14ms
√ update web in 7.50μp

Это означает, что вы готовы написать первую часть приложения.

Добавление Todo-модуля

Я не буду рассказывать о каждой мелочи того, что я сделал; мы рассмотрим только один файл и базовую структуру проекта. Если вас интересует состояние всего проекта после добавления первых битов, взгляните на этот коммит: https://github.com/evayde/new-todo-app/tree/f6f539aad9b48467d6151ab342f8eca0b88b8010

Базовая структура проекта

Мне нравится использовать папки для группировки модулей. Таким образом, вы найдете любые файлы, относящиеся к Todo-модулю, в каталоге /src/todo. Я использую файл /src/App.tsx для сборки различных представлений.
Я мог бы (теоретически) скопировать каталог todo в другой проект и повторно использовать каждый компонент. Однако следует помнить о нескольких вещах. Самое главное - это использование SQLite. Вы должны предоставить Todo-Module абстракцию, если вы фокусируетесь на возможности повторного использования. Модуль Todo излишне тесно связан с тем, как мы храним данные. То, как это реализовано прямо сейчас, помешало бы вам повторно использовать его, не полагаясь на SQLite.

Упражнение: отделите метод хранения от Todo-Module.

Крючок todo

Вы могли заметить, что я использую специальный крючок. Вы можете найти его под /src/todo/use-todo.ts.

Хук должен работать с базой данных и позволяет потребителю (App.tsx) редактировать, перечислять и переключать задачи. Он также инициализирует соединение с БД и первоначально загружает из него задачи.

Давайте подробнее рассмотрим, что делает каждая функция use-todo.ts. Мы сделаем это в порядке исполнения.

В первую очередь выполняются наши useEffects.

/**
 * Initialize the database and select the right table
 */
useEffect(() => {
  Device.getInfo()
    .then((info) => {
      const adapter = getAdapterByPlatform(info.platform);
      setDb(adapter);
      adapter.openStore({database: "todoapp", table: "todos"});
  })
  .catch(err => {
    console.log({err})
  });
}, []);

Первый useEffect - инициализатор. Мы инициализируем адаптер базы данных и сохраняем его в переменной состояния, чтобы мы могли использовать его на протяжении всего жизненного цикла нашего приложения. Я создал вспомогательную функцию getAdapterByPlatform, чтобы уменьшить шум в useEffect. Я часто вижу людей, использующих здесь однострочные комментарии, например // getting adapter by platform. Вам следует подумать о создании функции с таким именем, если вы видите такой комментарий в своем коде.

const getAdapterByPlatform = (platform: string) => {
  let sqlLite;
if (platform === "android") {
    sqlLite = CapacitorDataStorageSqlite as CapacitorSQLPlugin.CapacitorDataStorageSqlitePlugin;
  }
  else if (platform === "web") {
    sqlLite = CapacitorSQLPlugin.CapacitorDataStorageSqlite;
  }
  else {
    throw new Error("Unsupported feature");
  }
return sqlLite;
}

Код функции getAdapterByPlatform - это шаблон, скопированный из файла readme для пакетов npm. Я выкинул только ненужные для приложения биты (например, поддержку iOS). Они добавили бы нежелательный дополнительный шум в кодовую базу. Однако я добавил исключение (чтобы напомнить мне, когда я тестирую неподдерживаемую платформу). Примечание. Сообщение об исключении должно быть более конкретным в реальном приложении.

Еще у меня есть второй useEffect.

useEffect(() => {
  if (!db) {
    return;
  }
db.get({key: "todos"})
    .then((res: CapacitorSQLPlugin.capDataStorageResult) => {
      if (res) {
        const loadedTodos: any[] = JSON.parse(res.value || "[]");
        // We have to format the duedate of the loaded todos
        setTodos(
          loadedTodos.map(t => ({...t, duedate: new Date(t.duedate)}))
        );
      }
    });
}, [db]);

Этот useEffect отвечает за загрузку начального списка задач из нашей базы данных. Следовательно, у него есть переменная состояния db в качестве зависимости (эта useEffect будет перезапускаться каждый раз, когда db обновляется). Поскольку наша база данных хранит только строку JSON всего массива задач, мы должны оживить объекты Date (проверьте тип интерфейса задачи). Мы также могли работать со строками даты и получать новые объекты даты там, где это необходимо.

Сам useEffect просто вернется, ничего не делая при первом запуске. Он будет запущен снова, как только состояние db будет установлено первым useEffect. Затем он загрузит начальный набор задач из нашей базы данных SQLite.

Остальные функции не вызываются автоматически. Они экспортируются крючком и вызываются при необходимости.

Одна из этих функций addTodo, давайте внимательно рассмотрим:

/**
 * add a todo to database
 * @param {Todo} todo
 */
const addTodo = async ({ title, duedate = new Date()}: Todo) => {
  if (!db || !title) {
    return;
  }
const newTodos = [{title, duedate, done: false}, ...todos];
await db.set({key: "todos", value: JSON.stringify(newTodos)});
  setTodos(newTodos);
}

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

Функция toggleTodo работает аналогичным образом. Мне нужно использовать комбинацию задач title и duedate, чтобы найти конкретную задачу, поскольку я не устанавливал идентификатор для каждого Todo.

Упражнение: создайте идентификатор для задач.

✅ Выполненные задачи: «Добавить задачу, составить список задач, обновить задачу, создать список задач (UI)».

Добавление календаря-модуля

Давайте создадим Календарь-модуль, чтобы было интереснее. В текущем состоянии приложения всякий раз, когда мы создаем задачу, duedate будет установлен на текущую дату. Мы хотим изменить это поведение: duedate должна быть датой, которая выбрана в данный момент. Мы также хотим отображать задачи только на выбранную в данный момент дату. Это довольно простая задача.

Вы можете увидеть полную реализацию модуля в этом коммите: https://github.com/evayde/new-todo-app/tree/755b375e58cef1914c2a338afb87654e24c5039d

Главное, что изменилось, это то, что я добавил новый пользовательский хук в /src/calendar/use-calendar.ts.

import { useState } from "react";
const useCalendar = () => {
  const [currentDay, setCurrentDay] = useState(new Date());
const gotoNextDay = () => {
    currentDay.setDate(currentDay.getDate() + 1);
    setCurrentDay(new Date(currentDay));
  }
const gotoPreviousDay = () => {
    currentDay.setDate(currentDay.getDate() - 1);
    setCurrentDay(new Date(currentDay));
  }
  
  const isSameDay = (d1: Date, d2: Date) => {
    return (
      d1.getDate() === d2.getDate() &&
      d1.getMonth() === d2.getMonth() &&
      d1.getFullYear() === d2.getFullYear()
    );
  }
  
  return { currentDay, gotoNextDay, gotoPreviousDay, isSameDay }
}
export default useCalendar;

Этот хук просто позволит нам перемещаться между датами, а также предоставит нам удобную функцию для сравнения двух дат.

После реализации ловушки нужно сделать еще две вещи.

  1. Недавно созданное задание должно получить currentDay как duedate.
  2. TodoList необходимо получить filter-Attribute, чтобы мы могли фильтровать задачи по дате.

✅ Выполненные задачи: «Создание представления календаря (UI) с возможностью навигации, реализация логики (логики) навигации, фильтрация задач (логика)»

Теперь разверните и протестируйте свое приложение.

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

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

Резюме

Самая важная часть проекта: ретроспектива!

Задайте себе следующие вопросы и подумайте несколько секунд над ответом:

  • Смог бы я сам сделать этот проект?
  • Что бы я улучшил в отношении проекта (планирование, разработка, развертывание)?
  • Какие библиотеки я считаю полезными? Знал ли я их раньше?

Спасибо, что дочитали так далеко.

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

Я надеюсь, что у всех вас будет отличный день.