Flutter для iOS — добавление элемента

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

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

Принятие этого требования должно быть в состоянии:

  • Поиск нового рецепта
  • Сохраните в наш дайджест:
  • Заголовок
  • Автор
  • Миниатюрное изображение

Для начала я добавил верхнюю панель навигации с действием, которое позволяет нам добавить рецепт.

@override
Widget build(BuildContext context) {
  return CupertinoPageScaffold(
    navigationBar: const CupertinoNavigationBar(
      heroTag: "RecipeTabHeroTag",
      transitionBetweenRoutes: false,
      middle: Text(
        "Recipies",
      ),
      trailing: Icon(CupertinoIcons.add)
    ),
    child: Container(
      color: CupertinoColors.activeGreen,
      child: recipiesList(),
    ),
  );
}

Я переименовал вкладку обратно в «Дайджесты», теперь у нас есть заголовок «Рецепты» в верхней панели навигации.

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

Таким образом, нам просто нужно использовать поиск Google, чтобы найти несколько новых рецептов.

За этим я обратился в SerpAPI и взял некоторые данные из поиска рецептов жареной картошки с бальзамическим уксусом.

https://serpapi.com/search.json?q=Roast+potatoes+with+balsamic&location=United+Kingdom&hl=en&gl=uk&google_domain=google.co.uk&api_key=secret_api_key

{
    "search_metadata": {
      "id": "61c3369cc99903747c1e643b",
      "status": "Success",
      "json_endpoint": "https://serpapi.com/searches/bdb5db8a7f4c5593/61c3369cc99903747c1e643b.json",
      "created_at": "2021-12-22 14:30:52 UTC",
      "processed_at": "2021-12-22 14:30:52 UTC",
      "google_url": "https://www.google.co.uk/search?q=Roast+potatoes+with+balsamic&oq=Roast+potatoes+with+balsamic&uule=w+CAIQICIOVW5pdGVkIEtpbmdkb20&hl=en&gl=uk&sourceid=chrome&ie=UTF-8",
      "raw_html_file": "https://serpapi.com/searches/bdb5db8a7f4c5593/61c3369cc99903747c1e643b.html",
      "total_time_taken": 1.99
    },
    "search_parameters": {
      "engine": "google",
      "q": "Roast potatoes with balsamic",
      "location_requested": "United Kingdom",
      "location_used": "United Kingdom",
      "google_domain": "google.co.uk",
      "hl": "en",
      "gl": "uk",
      "device": "desktop"
    },
    "search_information": {
      "organic_results_state": "Results for exact spelling",
      "query_displayed": "Roast potatoes with balsamic",
      "total_results": 12800000,
      "time_taken_displayed": 0.49
    },
    "recipes_results": [
      {
        "title": "Balsamic potatoes",
        "link": "https://www.jamieoliver.com/recipes/potato-recipes/balsamic-potatoes/",
        "source": "Jamie Oliver",
        "total_time": "2 hrs 20 mins",
        "ingredients":
        [
          "Balsamic vinegar",
          "maris piper potatoes",
          "red onions",
          "rocket",
          "olive oil"
        ],
        "thumbnail": "https://serpapi.com/searches/61c3369cc99903747c1e643b/images/bd928f9ef521c02bbdb2df96157011d6a02d619d06f8a6e0e62bb157f48e10e5.jpeg"
      },
      {
        "title": "Balsamic roast potatoes",
        "link": "https://www.taste.com.au/recipes/balsamic-roast-potatoes/1bcb6bd4-2efa-4f01-bc98-f2ce32eaa906",
        "source": "Taste",
        "rating": 4,
        "reviews": 2,
        "total_time": "1 hr 5 mins",
        "ingredients":
        [
          "Balsamic vinegar",
          "kipfler potatoes",
          "olive oil",
          "garlic"
        ],
        "thumbnail": "https://serpapi.com/searches/61c3369cc99903747c1e643b/images/bd928f9ef521c02bbdb2df96157011d625322c9b727d95f76706f0471bbaba31.jpeg"
      },
      {
        "title": "Balsamic roast potatoes",
        "link": "https://www.delicious.com.au/recipes/balsamic-roast-potatoes/1e2f30ad-d9b4-41bf-aa2e-16f1e2accfb7",
        "source": "Delicious",
        "rating": 5,
        "reviews": 1,
        "total_time": "1 hr 5 mins",
        "ingredients":
        [
          "Balsamic vinegar",
          "kipfler potatoes",
          "olive oil",
          "garlic"
        ],
        "thumbnail": "https://serpapi.com/searches/61c3369cc99903747c1e643b/images/bd928f9ef521c02bbdb2df96157011d6ebca3a2868589cb9feab32215a0ba5e8.jpeg"
      }
    ]
}

Добавлен список для возврата результатов поиска.

Когда результат выбран (onTap), мы добавляем его в наш дайджест рецепта.

Ta Da

Задняя конфорка

Сохранение дайджеста.

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

Только для iOS — нажмите вкладку для сброса

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

Анимированная панель поиска

Я решил не внедрять более расширенную панель поиска, подобную той, что в приложении Apple «App Store», где анимация находится в фокусе.

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

КупертиноSearchTextField

Я получаю сообщения об ошибках на уровне библиотеки от Flutter, которые просят меня сообщить об ошибке фреймворка, когда я нажимаю ввод на клавиатуре в поле поиска с помощью эмулятора iOS.

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

Я также заметил, что вам нужно дважды нажать на клавиатуре в эмуляторе, прежде чем сработает событие «OnTap».

XP

Данные в памяти

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

На данный момент мы используем класс репозитория памяти для управления этим и внедряем его как ChangeNotifierProvider.

ChangeNotifierProvider<MemoryRepository>(
	create: (_) => MemoryRepository(),
    lazy: false,
),

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

List<Recipe> findAllRecipes();
Recipe addRecipe(Recipe recipe);

См. документацию Flutter для получения дополнительной информации об управлении состоянием.

Когда мы переходим в хранилище данных, например. БД через API мы можем улучшить этот код.

Повторное использование элемента списка

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

Widget recipesList() {
  final repository = Provider.of<MemoryRepository>(context);
  var recipes = repository.findAllRecipes();

  return ListView.builder(
    itemCount: recipes.length,
    itemBuilder: (context, index) {
      var listItemInfo = ListItemInfo(
        id: recipes[index].link,
        author: recipes[index].source,
        title: recipes[index].title,
        titleImage: Image.network(recipes[index].thumbnail),
        showAddIcon: false,
      );

      return ListItem(listItemInfo);
    },
  );
}

Widget recipeSearchResultsList() {
  var searchResultRecipes = _searchResults.data.length > 0
    ? Recipe.fromSerpApiResultJson(_searchResults.data)
    : <Recipe>[];

  final repository = Provider.of<MemoryRepository>(context);

  return ListView.builder(
    itemCount: searchResultRecipes.length,
    itemBuilder: (context, index) {
      var listItemInfo = ListItemInfo(
        id: searchResultRecipes[index].link,
        author: searchResultRecipes[index].source,
        title: searchResultRecipes[index].title,
        titleImage: Image.network(searchResultRecipes[index].thumbnail),
        showAddIcon: true,
        onTap: () => {
          repository.addRecipe(searchResultRecipes[index]),
          Navigator.pop(context)
        },
      );

      return ListItem(listItemInfo);
    },
  );
}

Scope Creep (Осторожно — ЮТНИ, он вам вряд ли понадобится)

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

Я хотел сделать больше работы над:

  • доступ к данным, включая пакетные запросы API
  • создавайте гораздо лучшие виджеты с красивой анимацией
  • добавить тему и вкус
  • Ведение журнала
  • Автоматизированное приемочное тестирование.
  • Улучшить дизайн для Android
  • Посмотрите, как это будет работать в качестве веб-сайта
  • ввести несколько библиотек управления состоянием
  • и т. д..

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

В конце концов, я вернулся к демонстрационному менталитету, чтобы заставить его работать, сбалансированному с советом дяди Боба, что, как только это работает, это только 50%, теперь вам нужно сделать это правильно.

Это равнялось еще некоторому времени извлечения методов и уборки.

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

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

Ссылки

Большое спасибо M4trix Dev, так как его статья о панели вкладок iOS была неоценимой и позволила мне быстро исправить проблемы, которые у меня были с моей первой реализацией.

Люди

«Дело в том, что очень легко быть другим, но очень трудно быть лучше».

Джони Айв_

Звук и изображение

Еще кое-что

«Каждое утро я смотрел в зеркало и спрашивал себя: «Если бы сегодня был последний день моей жизни, хотел бы я делать то, что собираюсь сделать сегодня?» И всякий раз, когда ответ был «Нет» слишком много дней подряд, я знаю, что мне нужно что-то изменить».

Стив Джобс_