Примеры собственного кода

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

внимание
Ограничение для функций

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

Вот что доступно собственной функции — этим набором и приходится обходиться:

import 'dart:convert';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'lat_lng.dart';
import 'place.dart';
import 'uploaded_file.dart';
import '/backend/backend.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '/backend/schema/structs/index.dart';
import '/backend/schema/enums/enums.dart';
import '/auth/firebase_auth/auth_util.dart';

Сгенерированные классы FlutterFlow

Пока вы собираете приложение, FlutterFlow генерирует полноценный проект на Flutter. В нём есть свои классы, закрывающие типовые задачи:

  • КнопкиFFButton с готовым оформлением и поведением.
  • Google PlacesFFPlace с названием, адресом и координатами места.
  • Загруженные файлыFFUploadedFile с именем файла, байтами и ссылкой.
совет
Что такое класс

Класс — чертёж объекта: он описывает свойства (данные) и методы (действия над ними).

Например:

  • у класса Car могут быть свойства color и speed и методы drive() и stop();
  • у класса FFPlace — свойства address и latLng и методы для работы с ними.

Такие классы почти всегда начинаются с FF или FlutterFlow. Наберите эти буквы в редакторе — подсказка покажет доступные.

suggestions-dropdown.png

Компонент внутри собственного виджета

внимание
Только для неизменного содержимого

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

В собственном виджете можно вернуть готовый компонент FlutterFlow — не переписывая его содержимое кодом. Скажем, вы делаете диалог на пакете с pub.dev: его содержимым может стать компонент, собранный на холсте.

совет
Импорты

Импорт класса компонента FlutterFlow добавляет сам.

return-widget-custom-code.png

Тема оформления в своём виджете

Цвета в собственном виджете лучше брать из темы, а не прописывать значениями: тогда виджет не выпадет из общего оформления и переживёт смену палитры.

Доступ к теме даёт FlutterFlowTheme.of(context): оттуда берутся и цвета (primary, primaryBackground, ваши собственные), и текстовые стили (bodyLarge, bodyMedium).

совет
Импорты

Для доступа к FlutterFlowTheme нужен импорт import '../flutter_flow/flutter_flow_theme.dart';.

class CustomButton extends StatefulWidget {
  final String label;

  CustomButton({required this.label});

  @override
  _CustomButtonState createState() => _CustomButtonState();
}

class _CustomButtonState extends State<CustomButton> {
  bool isPressed = false;

  void toggleButton() {
    setState(() {
      isPressed = !isPressed;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: isPressed
            ? FlutterFlowTheme.of(context).primary // цвет при нажатии
            : FlutterFlowTheme.of(context).secondaryBackground, // обычный цвет
        foregroundColor: FlutterFlowTheme.of(context).secondaryText, // цвет текста
      ),
      onPressed: toggleButton,
      child: Text(
        widget.label,
        style: FlutterFlowTheme.of(context).bodyText1, // стиль текста из темы
      ),
    );
  }
}

Работа с App State из кода

Состояние приложения обычно читают и меняют действиями, но иногда это нужно прямо в коде — когда важен порядок операций. Класс FFAppState даёт и доступ к значениям, и вспомогательные методы.

совет
Импорты

Для доступа к FFAppState нужен импорт import '../../flutter_flow/flutter_flow_util.dart';.

  • Чтение значения

Future getCartItems() async {
  // Читаем текущее содержимое корзины из AppState
  final currentCartItems = FFAppState().cartItems;
  print('Current Cart Items: $currentCartItems');
}
  • Запись значения
Future enableDarkMode() async {
  // Включаем тёмную тему
  FFAppState().update(() {
    FFAppState().enableDarkMode = true;
  });
  print('Dark mode enabled');
}
  • Работа со списками

У FFAppState есть готовые методы для списков — подробнее о сгенерированном классе см. здесь.

Future addLocation(LatLng value) async {
  // Добавить точку в список LatLng
  FFAppState().addToLatLngList(value);
}

Future removeLocation(LatLng value) async {
  // Удалить конкретную точку из списка
  FFAppState().removeFromLatLngList(value);
}

Future removeLocationAtIndex(int index) async {
  // Удалить точку по индексу
  FFAppState().removeAtIndexFromLatLngList(index);
}

Future updateLocationAtIndex(int index, LatLng Function(LatLng) updateFn) async {
  // Изменить точку по индексу
  FFAppState().updateLatLngListAtIndex(index, updateFn);
}

Future insertLocationAtIndex(int index, LatLng value) async {
  // Вставить точку по индексу
  FFAppState().insertAtIndexInLatLngList(index, value);
}

Свои типы данных

Для каждого собственного типа данных FlutterFlow генерирует класс <Имя>Struct. В коде такие объекты создают, читают, меняют и возвращают обратно в действие. Разберём на примере ProductStruct.

Пример 1: создание объекта

// Новый экземпляр ProductStruct
final newProduct = ProductStruct(
    productId: '123',
    name: 'Example Product',
    description: 'A sample product description.',
    category: 'Electronics',
    subCategory: 'Mobile Phones',
    price: PriceStruct(amount: 299.99, currency: 'USD'),
    sizes: ['Small', 'Medium', 'Large'],
    colors: [ColorsStruct(colorName: 'Red', colorHex: '#FF0000')],
    images: [ImagesStruct(thumbnail: 'https://example.com/image.jpg')],
    stockStatus: StockStatusStruct(xs: 0, small: 2),
    reviews: [ReviewsStruct(rating: 4, comment: 'Great product!')],
);

Пример 2: чтение полей

Из готового объекта можно достать значение и вернуть его в вызывающее действие.

  • Одно поле

Тип возврата String?, потому что значение может отсутствовать.

// Вернуть название товара
String? getProductName(ProductStruct product) {
    return product.name;
}
  • Проверка на наличие значения
// Есть ли у товара описание
bool hasDescription(ProductStruct product) {
    return product.description != null;
}
  • Список из вложенных объектов
// Собрать тексты всех отзывов
List<String> getProductReviewComments(ProductStruct product) {
  return product.reviews?.map((review) => review.comment ?? '').toList() ?? [];
}

Пример 3: изменение полей

Объект можно править перед записью в базу или передачей в действие.

  • Простое поле
// Обновить название товара
Future updateProductName(ProductStruct product, String newProductName) {
  product.productName = newProductName;
}
  • Вложенный объект

Здесь меняется цена, которая сама является объектом PriceStruct.

// Обновить цену товара
Future updateProductPrice(ProductStruct product, double newAmount, String currency) {
    if (product.price != null) {
        // Меняем только сумму
        product.price!.amount = newAmount;
    } else {
        // Если цены нет — создаём объект целиком
        product.price = PriceStruct(
            amount: newAmount,
            currency: currency,
    );
    }
}
  • Список вложенных объектов
Future addNewReviews(ProductStruct product) {
  product.reviews ??= []; // создаём список, если его ещё нет
  product.reviews!.addAll([
    ReviewStruct(rating: 5, comment: 'Excellent product!'),
    ReviewStruct(rating: 4, comment: 'Good quality, but a bit expensive.'),
    ReviewStruct(rating: 3, comment: 'Satisfactory, meets expectations.'),
  ]);
}

Или если список отзывов приходит в действие параметром:

Future addDynamicReviews(ProductStruct product, List<ReviewStruct> newReviews) {
  product.reviews ??= []; // создаём список, если его ещё нет
  product.reviews!.addAll(newReviews);
}

Данные авторизации Firebase

При работе с Firebase Authentication в коде доступны сведения о текущем пользователе.

Проверка подтверждённой почты:

if (currentUserEmailVerified) {
  // Действие для подтверждённых пользователей
}

Путь к папке пользователя:

String directoryPath = '/users/' + currentUserUid + '/files';

Что ещё доступно:

  • currentUserEmail — адрес почты;
  • currentUserUid — уникальный идентификатор;
  • currentUserDisplayName — отображаемое имя;
  • currentUserPhoto — ссылка на аватар;
  • currentPhoneNumber — телефон, если он есть;
  • currentJwtToken — JWT-токен для защищённых запросов;
  • currentUserEmailVerified — подтверждена ли почта.

Значения окружения разработки

Если в проекте используются окружения разработки, FlutterFlow генерирует класс FFDevEnvironmentValues — по тому окружению, которое выбрано на момент генерации кода.

Future getWebhookId() async {
  // Add your function code here!
  return FFDevEnvironmentValues().webhookId;
}

Библиотеки в собственном коде

Из проекта, подключившего библиотеку, доступны её состояние, значения и виджеты.

Значения библиотеки

Для библиотек генерируется класс FFLibraryValues — по аналогии с FFAppState и FFDevEnvironmentValues. Через него читаются Library Values.

Future getSchema(StateStruct? syncStatus) async {
  print(FFLibraryValues().schema);
}

Собственный код библиотеки

При подключении библиотеки FlutterFlow сам добавляет нужные импорты, и её код становится доступен вашему.

Для библиотеки с идентификатором проекта library_hybw3o добавится такой импорт:

import 'package:library_hybw3o/flutter_flow/custom_functions.dart' as library_hybw3o_functions;

После этого её функции вызываются из ваших:

int getRandomIndex(List<int> indexList) {
    final item = library_hybw3o_functions.getRandomItem(); // функция из библиотеки
    // получаем случайный индекс
    final randomNumber = math.Random();
    return ...
}

Импорт вручную

Если импорт не появился сам, добавьте его сами и задайте псевдоним.

Пример: подключаем собственные действия библиотеки к своему виджету.

// Свой импорт
import 'package:library_hybw3o/custom_code/actions/index.dart' as library_hybw3o_actions; // задаём псевдоним

// Код виджета
class CustomDialog extends StatefulWidget {
  const CustomDialog({
    super.key,
    this.width,
    this.height,
  });

  final double? width;
  final double? height;

  @override
  State<CustomDialog> createState() => _CustomDialogState();
}

class _CustomDialogState extends State<CustomDialog> {
    @override
    void initState() {
        library_hybw3o_actions.getSchema(StateStruct()); // вызываем действие библиотеки
        super.initState();
    }
    @override
        Widget build(BuildContext context) {
            return Container(height: 50, width: 50);
    }
}
перевод официальной документации FlutterFlow

обновлено

ESC