Огляд FlutterFlow та автоматично створеного ним коду

Усі статті, обговорення, новини про Mobile — в одному місці. Підписуйтеся на телеграм-канал!

Привіт, мене звати Ліза і я Flutter Developer у Customertimes. Уже понад 8 років я займаюся мобільною розробкою і стежу за розвитком технологій у цій сфері. Я перейшла з Android на Flutter розробку 3 роки тому і не шкодую про це.

Ні для кого не новина, що з кожним роком зʼявляються нові інструменти, які дозволяють оптимізувати процес розробки та роблять його якомога доступнішим для широкого кола людей. Сьогодні я хотіла б поділитися з вами досвідом використання low-code платформи для розробки мобільних застосунків — FlutterFlow.

Що таке Flutter

Спочатку трохи теорії з історією. Flutter — це набір програмного забезпечення з відкритим вихідним кодом, який створив Google для побудови мультиплатформних програм за допомогою мови програмування Dart, оптимізований для швидких програм на будь-якій платформі.

Flutter використовується для розробки міжплатформних застосунків на Android, iOS, macOS, Windows, Linux і вебзастосунків з єдиної кодової бази.

Flutter вийшов у травні 2017 року і на сьогодні, як зазначено в документації, за його допомогою було розроблено вже понад 400 000 застосунків для сотень мільйонів пристроїв.

Що таке FlutterFlow

Йдемо далі і переходимо до FlutterFlow. Це потужна low-code платформа, яку можна використовувати для розробки високоякісних мобільних і вебзастосунків на основі фреймворку Flutter. Його drag-and-drop інтерфейс дозволяє створювати юзер-інтерфейси та робочі процеси без необхідності писати код з нуля.

Крім того, він надає доступ до різноманітних готових компонентів і віджетів, які можна налаштувати відповідно до вимог конкретного застосунку.

FlutterFlow — інструмент на основі браузера, з ним можна створювати повнофункціональні масштабовані програми з інтеграцією Firebase, підтримкою API, анімацією, локалізацією, push-повідомленнями, налаштованими платежами тощо. Ви також можете додавати власні функції або віджети за допомогою Dart, щоб розширити наявний функціонал.

Однак важливо зауважити, що хоча такі low-code платформи, як FlutterFlow, є зручними для швидкої побудови прототипів і створення застосунків, вони можуть бути не найкращим вибором для складних або надто кастомізованих застосунків. У деяких випадках вам доведеться написати спеціальний код, щоб отримати бажаний функціонал.

Для кого підходить FlutterFlow

Це перш за все інструмент для нетехнічних користувачів, які завдяки йому мають змогу створити застосунки з нуля без знання мов програмування. І це досить широкий спектр застосунків — від MVP демопроєктів до повноцінних мобільних застосунків для широкої аудиторії.

Користувачі можуть створити мультиплатформний мобільний застосунок без жодного рядка коду. До того ж FlutterFlow має зручний drag-n-drop інтерфейс з можливістю переходу до дерева віджетів, і завдяки цьому застосунок не сплутати з макетом.

Саме тому розуміння фундаментальних підходів розробки для роботи з FlutterFlow було б плюсом, але на практиці це зовсім не є обов’язковим.

Ціни

FlutterFlow пропонує 4 різні плани для користувачів. Чудово, що ця платформа має серед іншого і безкоштовний план, тож можна протестувати інструмент і перевірити, чи підходить він для ваших цілей. Ви не отримаєте доступ до файлів APK, але матимете змогу запустити свій застосунок, протестувати його та переглянути код. Опція завантаження коду вимкнена для користувачів безкоштовного плану, однак ви матимете доступ до всіх файлів.

Якщо ж ви хочете працювати над проєктом разом з командою або плануєте завантажувати чи публікувати файли APK, доведеться вибрати платний тариф користування. Усі вони детально описані на сайті.

Крім того, FlutterFlow може надати експертів, які зорієнтують і можуть допомогти створити рішення з проєктуванням, прототипом, впровадженням і поданням до магазинів застосунків.

Як це працює

Перш за все, FlutterFlow пропонує вам вибрати один з уже наявних шаблонів, і ще більше спростити процес розробки. Або ж, в іншому випадку, ви можете створити застосунок самостійно та розробити свій юзер-інтерфейс з нуля.

Як приклад я використала open public API coinstats, щоб отримати та відобразити динаміку щоденних змін ціни залежно від ринкової капіталізації. Юзер-інтерфейс має ListView з елементами, який створюється динамічно на основі відповіді API. Іконки «Вгору» та «Вниз» мають відображатися залежно від того, як змінюється ціна, та з’являються на екрані з примітивною анімацією повороту.

Результат

Едітор

Ви можете написати власні функції, віджети чи дії на вкладці «Custom Code», але ви не можете змінювати код, створений автоматично UI-конструктором.

Автоматично створений код віджета Dart виглядає так:

import '/backend/api_requests/api_calls.dart';
import '/flutter_flow/flutter_flow_animations.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.dart';
 
import 'home_page_model.dart';
export 'home_page_model.dart';
 
class HomePageWidget extends StatefulWidget {
  const HomePageWidget({Key? key}) : super(key: key);
 
  @override
  _HomePageWidgetState createState() => _HomePageWidgetState();
}
 
class _HomePageWidgetState extends State<HomePageWidget>
    with TickerProviderStateMixin {
  late HomePageModel _model;
 
  final scaffoldKey = GlobalKey<ScaffoldState>();
  final _unfocusNode = FocusNode();
 
  final animationsMap = {
    'listViewOnPageLoadAnimation': AnimationInfo(
      trigger: AnimationTrigger.onPageLoad,
      effects: [
        MoveEffect(
          curve: Curves.easeInOut,
          delay: 0.ms,
          duration: 800.ms,
          begin: Offset(0, 1000),
          end: Offset(0, 0),
        ),
      ],
    ),
    'iconOnPageLoadAnimation1': AnimationInfo(
      trigger: AnimationTrigger.onPageLoad,
      effects: [
        VisibilityEffect(duration: 1000.ms),
        RotateEffect(
	      curve: Curves.easeInOut,
          delay: 1000.ms,
          duration: 1000.ms,
          begin: 0,
          end: 1,
        ),
      ],
    ),
    'iconOnPageLoadAnimation2': AnimationInfo(
      trigger: AnimationTrigger.onPageLoad,
      effects: [
        VisibilityEffect(duration: 1000.ms),
        RotateEffect(
          curve: Curves.easeInOut,
          delay: 1000.ms,
          duration: 1000.ms,
          begin: 0,
          end: 1,
        ),
      ],
    ),
  };
 
  @override
  void initState() {
    super.initState();
    _model = createModel(context, () => HomePageModel());
  }
 
  @override
  void dispose() {
    _model.dispose();
 
    _unfocusNode.dispose();
	super.dispose();
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
      appBar: AppBar(
        backgroundColor: FlutterFlowTheme.of(context).primaryColor,
        automaticallyImplyLeading: false,
        title: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
          	'Markets',
          	style: FlutterFlowTheme.of(context).title2.override(
                	fontFamily: 'Poppins',
                	color: Colors.white,
                	fontSize: 22,
              	),
            ),
          ],
        ),
        actions: [],
        centerTitle: false,
        elevation: 2,
      ),
      body: SafeArea(
        child: GestureDetector(
          onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
          child: SingleChildScrollView(
            child: Column(
          	mainAxisSize: MainAxisSize.max,
          	children: [
            	RefreshIndicator(
              	onRefresh: () async {
                	setState(() => _model.pagingController?.refresh());
                	await _model.waitForOnePage();
              	},
                  child: PagedListView<ApiPagingParams, dynamic>(
                	pagingController: () {
                  	if (_model.pagingController != null) {
                    	return _model.pagingController!;
                  	}
 
           	       _model.pagingController = PagingController(
                    	firstPageKey: ApiPagingParams(
                      	nextPageNumber: 0,
                      	numItems: 0,
                      	lastResponse: null,
                	    ),
                  	);
                  	_model.pagingController!
                          .addPageRequestListener((nextPageMarker) {
                        GetMarketsInfoCall.call(
                      	start: valueOrDefault<int>(
 	                       getJsonField(
                              (nextPageMarker.lastResponse ??
                                      ApiCallResponse({}, {}, 200))
                              	.jsonBody,
                          	r'''$.next''',
                        	),
                        	1,
                      	),
                        ).then((listViewGetMarketsInfoResponse) {
                      	final pageItems = getJsonField(
                            listViewGetMarketsInfoResponse.jsonBody,
                        	r'''$.coins''',
                      	).toList() as List;
                      	final newNumItems =
                              nextPageMarker.numItems + pageItems.length;
                          _model.pagingController!.appendPage(
                        	pageItems,
                        	(pageItems.length > 0)
                  	          ? ApiPagingParams(
                                    nextPageNumber:
                                        nextPageMarker.nextPageNumber + 1,
                                	numItems: newNumItems,
                                	lastResponse:
                                        listViewGetMarketsInfoResponse,
                              	)
                            	: null,
                      	);
                    	});
                  	});
            	      return _model.pagingController!;
                	}(),
                	padding: EdgeInsets.zero,
                	shrinkWrap: true,
                	reverse: false,
                	scrollDirection: Axis.vertical,
                	builderDelegate: PagedChildBuilderDelegate<dynamic>(
                  	// Customize what your widget looks like when it's loading the first page.
                      firstPageProgressIndicatorBuilder: (_) => Center(
          	          child: SizedBox(
                      	width: 50,
                      	height: 50,
                      	child: CircularProgressIndicator(
                        	color: FlutterFlowTheme.of(context).primaryColor,
            	          ),
                    	),
                  	),
 
                  	itemBuilder: (context, _, marketsIndex) {
                    	final marketsItem =
                            _model.pagingController!.itemList![marketsIndex];
                        return Padding(
                      	padding: EdgeInsetsDirectional.fromSTEB(8, 4, 8, 4),
                      	child: InkWell(
                        	onTap: () async {
                              context.pushNamed(
                                'details',
                            	queryParams: {
                              	'info': serializeParam(
                                    getJsonField(
                                      marketsItem,
                                  	r'''$''',
                                	),
                                    ParamType.JSON,
                              	),
                            	}.withoutNulls,
                          	);
                        	},
                        	child: Container(
                          	width: 100,
                          	height: 100,
                              decoration: BoxDecoration(
                            	color: FlutterFlowTheme.of(context)
                                    .secondaryBackground,
                          	),
                          	child: Padding(
                                padding: EdgeInsetsDirectional.fromSTEB(
                                	16, 16, 16, 16),
                            	child: Row(
                              	mainAxisSize: MainAxisSize.max,
                       	       mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                              	children: [
                                	Text(
                                      getJsonField(
                	                    marketsItem,
                                        r'''$.name''',
                                      ).toString(),
                                      textAlign: TextAlign.center,
                                  	style: FlutterFlowTheme.of(context)
                                          .subtitle1,
                                	),
                                	Row(
                                      mainAxisSize: MainAxisSize.max,
                                  	children: [
                                    	if (getJsonField(
                                              marketsItem,
                                              r'''$.priceChange1d''',
               	                         ) >
                                        	0)
                                      	Icon(
                                            Icons.arrow_drop_up,
                                            color: FlutterFlowTheme.of(context)
                                                .secondaryColor,
                                            size: 56,
                                          ).animateOnPageLoad(animationsMap[
                                          	'iconOnPageLoadAnimation1']!),
                                    	if (getJsonField(
                                              marketsItem,
                                              r'''$.priceChange1d''',
                                        	) <
                                        	0)
                                      	Icon(
                                       	 Icons.arrow_drop_down,
                                            color: Color(0xFFE0180E),
                                            size: 56,
                                          ).animateOnPageLoad(animationsMap[
                           	               'iconOnPageLoadAnimation2']!),
                                    	Text(
                                          getJsonField(
                                            marketsItem,
                                            r'''$.priceChange1d''',
                                          ).abs().toStringAsFixed(3),
                                          style: FlutterFlowTheme.of(context)
                                              .bodyText1
                                              .override(
                                                fontFamily: 'Poppins',
                                                fontSize: 20,
                                              ),
                                    	),
                                  	],
                                	),
                              	],
                            	),
                 	         ),
                        	),
                      	),
                    	);
                  	},
                	),
              	),
            	).animateOnPageLoad(
                    animationsMap['listViewOnPageLoadAnimation']!),
          	],
            ),
          ),
        ),
      ),
    );
  }
}

Скільки часу знадобиться для реалізації такого простого проєкту? Що ж, у мене це зайняло майже 20 хвилин, але на першу спробу я витратила 1-2 години, бо мала зрозуміти, куди розмістити свій виклик API, як посилатися на певну змінну, ба навіть, де цю змінну створити і так далі.

Досвідчений розробник без проблем реалізує такий застосунок вручну за допомогою IDE за короткий проміжок часу, але перший проєкт протягом 1-2 годин? Звучить неймовірно. І, звичайно, з FlutterFlow вам не потрібно знову і знову писати якийсь шаблонний код. А працювати з інтерфейсом drag-n-drop набагато легше та цікавіше.

Чи допомогли мені знання про Flutter on Dart під час написання проєкту? Так, звісно. Чи це важливо? Ні, звісно. Усе, що вам потрібно зробити, — створити UI зі зручним інтерфейсом і написати мінімум коду для невеликих задач. Нетехнічні користувачі без проблем впораються з цим інструментом, особливо коли ми всі маємо Google і можемо знайти відповіді на все, що потрібно. Так, це займе не 1-2 години, але, без сумнівів, це значно швидше, ніж вчити мову програмування.

Поради початківцям

  1. Виберіть рекомендований браузер. Як зазначено в документації, оптимальним браузером для FlutterFlow є Google Chrome. Зі свого досвіду можу доповнити: використовуючи інші браузери, ви можете зіткнутися з проблемами, повʼязаними з продуктивністю та зручністю. Мені довелося перейти на Chrome у середині проєкту. Тому краще використовувати його з самого початку.
  2. Не пропускайте туторіал. Він досить простий і невеликий, але в ньому зібрані всі основні функції цього інструменту. Як на мене, він трохи застарілий, але це не завадить вам знайти всі необхідні функції та ознайомитися з платформою.
  3. Не пропускайте Firebase setup. Під час створення проєкту вам запропонують додати до нього Firebase. Це необов’язково, але без підключення до Firebase ви матимете обмежені можливості, і вам все одно доведеться його додавати. Тому краще все налаштувати від початку. До того ж розробники Flutterflow зробили це дуже простим, що радує.
  4. І обов’язково зберігайте документацію під рукою, щоб мати змогу підійти до розвʼязання проблеми якнайефективнішим чином.

Висновок

Якщо ви плануєте використовувати FlutterFlow для створення застосунків, оцініть свої вимоги та визначте, чи здатна платформа задовольнити ваші потреби. Також варто враховувати такі фактори, як масштабованість, безпека та продуктивність.

Не нехтуйте силою спілкування: проконсультуйтесь з досвідченими розробниками та запийте їхню думку стосовно того, чи доцільно використовувати FlutterFlow у вашому конкретному випадку.

Хай там як, я переконана, що FlutterFlow є чудовим інструментом для реалізації власних ідей у застосунках. Ця платформа розвивається з дивовижною швидкістю, і хоча в ній трапляються помилки, розробники швидко все виправляють і додають нові функції для вдосконалення продукту.

👍ПодобаєтьсяСподобалось25
До обраногоВ обраному8
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

При компіляції вилітають українські «і, ї,є» це лише у мене чи така бага?

Фігня це, краще привчайте себе писати в парі з ChatGPT. Вона непогано справляється з Dart, якщо не лінитись описувати завдання. Думаю що JS для неї ще простіше писати.

Перейшов нещодавно з React-native на Flutter, дуже сподобалось. А от Flutterflow якось не зайшов — не люблю усi цi low-code штуки.

Як з роботою на Флаттері ? Не гірше реакт нейтіва ?

Не гiрше. Спочатку було незвично, але через якийсь час звик i все чудово. Великий + що не треба використовувати JS :)

Цікава штука.

Якщо я один раз заюзаю платну підписку щоб зібрати проект в APK і стягнути сорси, чи можна для дрібних змін юзати безплатну версію чи білдати локально?

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

жахливий код...
мене одного крутить від:

                                             ),
                                    	),
                                  	],
                                	),
                              	],
                            	),
                 	         ),
                        	),
                      	),
                    	);
                  	},
                	),
              	),
            	).animateOnPageLoad(
                    animationsMap['listViewOnPageLoadAnimation']!),
          	],
            ),
          ),
        ),
      ),
    );
  }
}
?

Welcome to dart(:
Але незважаючи на таку собі мову, в цілому Flutter зараз мабуть найпростіший і найприємніший фреймворк для швидкого написання простеньких кросплатформених додатків.

Ну в цьому є своя специфіка ФлаттерФлоу, тому що на практиці все в одному методі не пишеться і тому немає такої вкладеності

Код з кодегенів не для читання)

Спочатку подумав, що це реалізація API Flow із Котліна. :)

Колись зробив невеликий пет-проект на флаттері. В цілому із всіх кросплатформ насьогодні — це найкраще спроектована і з найменшою кількістю костилів. Правда перехід на Дарт після Котліна, це як повернутися в часі до часів Андроїда на Джаві. Але ІМХО зараз андроїд девам мабуть перспективніше потенційний перехід на кросплатформений компоуз.

Підписатись на коментарі