Время прочтения: 11 мин.

DoNuts — это платформа интеллектуального волонтерства. Наших пользователей мы никак не ограничиваем — наш проект открыт для всех.

Миссия DoNuts – объединить возможности и потребности волонтеров, бизнеса, государства и НКО для решения социальных и бизнес-задач в России на единой цифровой площадке для всеобщего вовлечения общества в благотворительную деятельность.  Принцип работы нашей платформы прост: заказчики обращаются к нам за выполнением задачи, а мы публикуем задачу на платформе, чтобы исполнители ее выполнили в нужный срок. Чтобы мотивировать исполнителей и придать ценность работе, мы создали свою собственную виртуальную валюту – «орешки». Орешки можно обменять на цифровые продукты или сделать пожертвование.

Но внутри команды мы наш проект называем просто «агрегатор добрых дел»:)

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

Что такое flutter flow?

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

Основное преимущество Flutter Flow в том, что его достаточно просто освоить даже людям, не знакомым с программированием. Наша команда не имела опыта разработки мобильных приложений, но все же мы были уверены, что сможем создать приложение самостоятельно, главное — следовать инструкциям.

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

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

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

Шаг 1. Определяем минимальный функционал. Из чего должно состоять приложение?

Приложение состоит из стандартных экранов — логин/регистрация, личный кабинет, лидерборд и «стакан» задач.

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

Регистрация — экран для создания нового аккаунта. Пользователь заполняет форму с личными данными (имя, фамилия, электронная почта, пароль), после чего система отправляет письмо на указанный электронный адрес для подтверждения регистрации.

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

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

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

Шаг 2. Реализация описанной архитектуры и плюсы использования FlutterFlow

Виджеты и Дерево виджетов

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

    Во FlutterFlow виджет — это элемент пользовательского интерфейса, который помогает создавать макет страницы. Почти все, что видно на странице, является виджетом. Создаем пользовательский интерфейс, комбинируя виджеты в отношениях родитель-потомок. Text, Row, Column , Stack и Container — самые основные типы виджетов. В проекте, о котором идет речь в данном посте, использованы разные виджеты для более полной реализации возможного функционала.

    Вообще, во Flutter Flow виджеты реализуются с помощью языка программирования Dart.

    Warn!

    Далее я буду обсуждать код на языке Dart. Отсутствие знания этого языка программирования никак не повлияет на вашу возможность пользоваться Flutter Flow.

    Разобраться в этом было необходимо, чтобы понять, как на языке программирования реализуется Flutter Flow и как можно улучшить приложение с точки зрения функциональности, дописав необходимый код. Экспортирую проект из Flutter Flow в код на Flutter SDK. Flutter Flow сгенерировал соответствующий код на Dart на основе нашего индивидуального интерфейса, который был создан с помощью Flutter Flow.

    Продолжаю разработку после экспорта в среде разработки Flutter, добавляя логику и дополнительные функции.

    Загляну под капот виджетов:

    1. Создание виджета.

    Виджеты во Flutter Flow являются классами, наследующими от базового класса `StatelessWidget` или `StatefulWidget`.

    Вот наш пример:

    import ‘package:flutter/material.dart’;
    
    Class MyWidget extends StatelessWidget {
    	@override
    	Widget build(BuildContext context) {
    	return Container (
    	// Здесь код для настройки внешнего вида виджета
    	);
         }
    } 
    
    1. Значительное удобство виджетов заключается в том, что они могут переиспользоваться. В данном случае – это карточка задачи. Каждый раз, когда задача публикуется, дополнительный элемент-виджет создавать не нужно. Его можно просто переиспользовать, иначе просто не хватило бы ресурсов.

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

    Вот базовое решение задачи в виде кода Dart:

    import ‘package:flutter/material.dart’;
    
    class MyWidget extends StatelessWidget {
    	final String text;
    
    	MyWidget({required this.text});
    	
    	@override
    	Widget build(BuildContext context) {
    	    return Container (
    	       child: Text(text),
    	);
         }
    } 
    

    Предположим, у меня есть следующие текстовые поля для каждой задачи: «Название», «Описание» и «Дата».

    Опишу приблизительную самую базовую структуру карточки:

    Card 
    ListTile
    -	Text (Название задачи) 
    -	Text (Описание задачи)
    -	Text (Дата)

    Как будет выглядеть переиспользуемая карточка на Dart:

    import ‘package:flutter/material.dart’;
    
    class TaskCard extends StatelessWidget {
    	final String title;
    	final String description;
    	final String date;
    
    	TaskCard({required this.title, required this.description, required this.date});
    	
    	@override
    	Widget build(BuildContext context) {
    	    return Card (
    	       child: ListTile(
    		title: Text(title),
    		subtitle: Column(
    		  crossAxisAlignment: crossAxisAlignment.start, 
    		children: [
    		     Text(description),
    	                 Text(date),
    		],
                       ),
    	);
         }
    } 
    

    В этом примере класс `TaskCard` является переиспользуемым виджетом, представляющим карточку задачи. Он принимает параметры `title`, `description`, `date`, которые будут отображаться в соответствующих текстовых виджетах.

    В методе `build` класса `TaskCard`, создаю виджет `Card` с вложенным виджетом `ListTile`.Внутри `ListTile`виджет `Text` используется для отображения заголовка задачи (`title`), а виджет `Column` содержит текстовые виджеты для описания задачи (`description`) и даты(`date`).

    Сложность задачи заключается в том, что текст, который задается для карточек является input предыдущей формы, которую заполняет клиент, чтобы задача была размещена.

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

    Пример того, как на Dart можно передавать из формы данные в виджет карточки:

    import ‘package:flutter/material.dart’;
    
    class TaskCard extends StatelessWidget {
    	final String title;
    	final String description;
    	final String date;
    
    	TaskCard({required this.title, required this.description, required this.date});
    	
    	@override
    	Widget build(BuildContext context) {
    	    return Card (
    	       child: ListTile(
    		title: Text(title),
    		subtitle: Column(
    		  crossAxisAlignment: crossAxisAlignment.start, 
    		children: [
    		     Text(description),
    	                 Text(date),
    		],
                       ),
    	);
         }
    } 
    
    class FormScreen extends StatefulWidget {
    	@override
    	_FormScreenState createState() => _FormScreenState();
    }
    
    class _FormScreenState extends State<FormScreen> {
    	String title = ‘’;
    	String description = ‘’;
    	String date = ‘’;
    
    	void handleSubmit() {
    // в этом методе можно обработать отправку формы и сохранить значения полей                                             в состоянии приложения
    
    // пример сохранения значений полей в состоянии
    setState(() {
    title = ‘Новая задача’;
    description = ‘Описание задачи’;
    date = ’01.01.2023’;
    	  });
    }
    
    @override
    Widget build(BuildContext context) {
    	return Scaffold(
    	  appBar: AppBar(
    	    title: Text(‘Форма’),
    	  ),
    	body: Center(
    	  Child: Column(
    	    Children: [
    	      TextFormField(
    		onChanged: (value) {
    		  // в этом обратном вызове обновляйте значение переменной description при в		      воде пользователя
    		setState(() {
    			description = value;
    		});
    	       },
    	       decoration: InputDecoration(
    		labelText: ‘Описание задачи’,
    	      ),
                  ),
    ElevatedButton(
    	onPressed: handleSubmit,
    	child: Text(‘Создать задачу’),
    ),
    
    TaskCard(
    	Title: ‘Новая задача’,
    	Description: description,
    	Date: date,
           ),
         ],
        ),
       ),
      );
     }
    }
    

    В этом примере есть два класса: `TaskCard` и `FormScreen`. Класс `TaskCard` представляет виджет карточки задачи, который принимает параметры `title`, `description`, `date`. Класс `FormScreen` является виджетом экрана с формой, в которой пользователь может вводить описание задачи.

    В классе `FormScreen` есть состояние, в котором хранятся значения полей формы (`title`, `description`, `date`). При вводе пользователем в поле `Описание задачи` обновляю значение переменной `description` с помощью обратного вызова `onChanged`.

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

    Затем значение `description` передаеся из состояния в виджет `TaskCard` при создании карточки задачи. Таким образом, введенное пользователем описание будет отображаться в карточке задачи.

    Важно отметить, что в этом примере используется класс `StatefulWidget` для создания экрана формы, чтобы иметь возможность обновлять состояние при вводе пользователя (приложение может содержать более сложную структуру состояний, в зависимости от требований).

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

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

    Доступ к нему можно получить из меню навигации (левая часть экрана). Это дает лучшее представление о том, сколько виджетов добавляется на страницу и как они добавляются (знание родителя или дочернего элемента любого виджета).

    Как выглядит дерево виджетов в FlutterFlow:

    Посмотрю под капот дерева виджетов:

    import ‘package:flutter/material.dart’;
    
    class MyApp extends StatelessWidget {
    	@override
    	Widget build(BuildContext context) {
    	    return MaterailApp (
                       home: Scaffold(
    	          appBar: AppBar(
    		  title: Text(‘Do Nuts’),
    	),
    	body: Center(
    	   child: Column(
     	       children: [
    		Text(‘Логин’),
    		Text(‘Пароль’),
    	           ],
    	      ),
                  ),
               ),
           );
        }
    } 
    

    Это самый простой пример реализации представленного на скриншоте поля.

    Создано приложение `MyApp`, которое содержит `Scaffold` с `AppBar` и центрированным содержимым. Внутри `body` есть виджет `Column`, который содержит два виджета `Text`. В результате получается вертикальная компоновка виджетов.

    Далее усложню структуру:

    import ‘package:flutter/material.dart’;
    
    class MyApp extends StatelessWidget {
    	@override
    	Widget build(BuildContext context) {
    	    return MaterailApp (
                       home: Scaffold(
    	          appBar: AppBar(
    		  title: Text(‘Do Nuts’),
    	),
    	body: Center(
    	   child: Column(
     	       children: [
    		Text(‘Логин’),
    		Row(
                                children: [
    Text(‘Widget 2’),
    Text(‘Widget 3’),
    	           ],
    	         ),
    	      ],
                   ),
                 ),
              ),
           );
        }
    } 
    

    Дальнейшие планы на развитие

    Исходный код и API

    Хотя для использования FlutterFlow не обязательно обладать знаниями в области программирования, платформа позволяет пользователям получать доступ к исходному коду для дальнейшей настройки своих приложений, выходящих за рамки возможностей визуального редактора. Эта функция дает более продвинутым пользователям возможность изменять сгенерированный код, добавлять новые функции и расширять возможности своих приложений. Существует поддержка API с использованием виджета «Внешний API», где есть возможность добавлять сторонние сервисы, такие как платежные шлюзы или платформы.

    Что позволяют сделать вызовы API?

    Акроним API расшифровывается как Application Programming Interface . Он позволяет продукту или услуге (в данном случае это приложение, которое создается) связываться с другими продуктами и услугами через защищенный канал, не передавая много информации об их реализации.

    Двумя наиболее популярными спецификациями API являются SOAP и REST.Не буду вдаваться в подробности здесь, просто дам краткое представление:
    SOAP : Простой протокол доступа к объектам использует XML для своего формата сообщений и получает запросы через HTTP или SMTP .
    REST : Аббревиатура от Representational State Transfer — это архитектурный стиль, за которым следуют различные API. REST API известны своей высокой производительностью и надежностью.

    Большинство веб-API, с которыми придется иметь дело, — это REST API с форматом JSON; сейчас это самая распространенная спецификация.

    Более подробно про методы вызова API можно прочитать в документации Flutter Flow.

    Почему это важно?

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

    Вот так с помощью простого low-code эффективно развивается проект😊

    Кстати, мы в поисках frontend-разработчиков (Vue.js). Если вы заинтересованы в развитии своих навыков и социально значимого проекта, свяжитесь, пожалуйста, с нашим продактом – Солонкиной Мариной – donutsiv@mail.ru.

    Подписывайтесь на нас в социальных сетях:

    https://t.me/DoNutsIV

    https://vk.com/donutsiv