> Есть ли опыт работы с Flutter Web (iOS, Flutter, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: VK

Стек: iOS, Flutter, Swift

> Пример ответа

Короткий ответ

Да, есть опыт работы с Flutter Web. В рамках кросс-платформенной разработки я использовал Flutter Web для создания веб-версий мобильных приложений, а также для прототипирования и внутренних инструментов. Основной фокус - на производительности, адаптивности и корректной работе с рендерингом на разных браузерах. Также решал задачи с деплоем, роутингом и интеграцией с веб-API.

Подробное объяснение

Flutter Web - это не просто компиляция мобильного кода в браузер, а отдельная платформа со своими особенностями. Основные аспекты, с которыми я сталкивался:

  • Рендеринг: Flutter Web использует два рендерера - HTML (legacy) и CanvasKit (Skia). CanvasKit даёт более консистентный рендеринг с мобильной версией, но увеличивает размер бандла и требует WebAssembly. HTML-рендерер легче, но имеет ограничения по шрифтам, тексту и некоторым виджетам.
  • Размер бандла: стартовый размер приложения на Flutter Web заметно больше, чем у аналогичного React/Vue приложения. Это критично для первого впечатления пользователя. Я использовал tree shaking, отложенную загрузку (deferred loading) и настройку --web-renderer для оптимизации.
  • Роутинг: на мобильных платформах навигация работает через Navigator, но для Web важно использовать Router с поддержкой URL-адресов, чтобы работали прямые ссылки, кнопки "назад" и обновление страницы. Я переходил на go_router или beamer для корректной работы с браузерной историей.
  • Адаптивность: Flutter Web требует явной работы с размерами экрана, breakpoints и responsive-подходом. Я использовал LayoutBuilder, MediaQuery и кастомные виджеты для адаптации под десктоп, планшет и мобильный браузер.
  • Интеграция с веб-API: работа с dart:html (или package:web), доступ к localStorage, sessionStorage, работа с cookies, а также интеграция с внешними JS-библиотеками через js_interop.
  • Производительность: на слабых устройствах или в Safari могут быть проблемы с анимациями и скроллом. Я оптимизировал через RepaintBoundary, избегал тяжёлых теней и blur-эффектов, использовал ListView.builder вместо Column с большим количеством элементов.

На практике

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

  • Адаптация UI: мобильный интерфейс с bottom navigation превращался в side navigation на десктопе. Использовал NavigationRail и NavigationBar в зависимости от ширины экрана.
  • Роутинг: перешёл с Navigator.push на go_router с поддержкой query-параметров и deep linking. Это позволило делиться ссылками на конкретные задачи.
  • Загрузка данных: использовал FutureBuilder и StreamBuilder для работы с REST API, но на Web добавил кэширование в localStorage, чтобы ускорить повторные визиты.
  • Деплой: настроил сборку через flutter build web --web-renderer canvaskit --release и деплой на Nginx с правильными MIME-типами и кэшированием статики.
  • Проблемы: столкнулся с тем, что шрифты на Web рендерятся иначе, чем на мобильных. Решил через явное подключение шрифтов и настройку fontFamily в ThemeData. Также была проблема с TextField на iOS Safari - курсор прыгал при вводе. Решил через настройку scrollPadding и autocorrect: false.

Пример кода

Пример адаптивного роутинга с go_router и responsive-виджетом:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  final GoRouter _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const HomeScreen(),
      ),
      GoRoute(
        path: '/task/:id',
        builder: (context, state) => TaskScreen(
          taskId: state.pathParameters['id']!,
        ),
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
      title: 'Flutter Web App',
      theme: ThemeData(
        fontFamily: 'Roboto',
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LayoutBuilder(
        builder: (context, constraints) {
          final isDesktop = constraints.maxWidth > 800;
          if (isDesktop) {
            return Row(
              children: [
                const NavigationRail(
                  destinations: [
                    NavigationRailDestination(
                      icon: Icon(Icons.home),
                      label: Text('Home'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.settings),
                      label: Text('Settings'),
                    ),
                  ],
                  selectedIndex: 0,
                ),
                const VerticalDivider(thickness: 1, width: 1),
                Expanded(child: _buildContent(context)),
              ],
            );
          }
          return _buildContent(context);
        },
      ),
      bottomNavigationBar: LayoutBuilder(
        builder: (context, constraints) {
          if (constraints.maxWidth > 800) {
            return null;
          }
          return NavigationBar(
            destinations: const [
              NavigationDestination(
                icon: Icon(Icons.home),
                label: 'Home',
              ),
              NavigationDestination(
                icon: Icon(Icons.settings),
                label: 'Settings',
              ),
            ],
            selectedIndex: 0,
          );
        },
      ),
    );
  }

  Widget _buildContent(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () => context.go('/task/123'),
        child: const Text('Open task'),
      ),
    );
  }
}

Как отвечать на собеседовании

  • Начни с конкретики: укажи, в каких проектах использовал Flutter Web, какие задачи решал, какой рендерер выбирал и почему.
  • Подчеркни, что понимаешь отличия от мобильной разработки: размер бандла, роутинг, адаптивность, работа с браузерными API.
  • Расскажи о проблемах, с которыми сталкивался, и как их решал - это показывает практический опыт.
  • Если опыта мало, честно скажи об этом, но покажи понимание ключевых концепций: CanvasKit, go_router, LayoutBuilder, dart:html.
  • Упомяни, что Flutter Web - это не замена нативному Web, а инструмент для кросс-платформенной разработки, и важно понимать его ограничения.

Что проверяет интервьюер

  • Глубину понимания Flutter Web как отдельной платформы, а не просто "запустил на браузере".
  • Умение решать специфические проблемы: производительность, роутинг, адаптивность, интеграция с веб-экосистемой.
  • Практический опыт с деплоем, настройкой сборки и оптимизацией размера бандла.
  • Понимание trade-off между CanvasKit и HTML-рендерером.
  • Способность объяснить, когда Flutter Web уместен, а когда лучше использовать React/Vue или нативную веб-разработку.

Типичные ошибки

  • Утверждение, что Flutter Web работает "из коробки" так же, как мобильная версия - это не так, есть много нюансов.
  • Игнорирование размера бандла и производительности - на слабых устройствах приложение будет тормозить.
  • Использование Navigator вместо Router - ломается работа с URL, кнопкой "назад" и обновлением страницы.
  • Отсутствие адаптивности - интерфейс, свёрстанный под мобильные, выглядит нелепо на десктопе.
  • Незнание особенностей Safari: проблемы с WebGL, шрифтами, скроллом.
  • Попытка использовать dart:html напрямую без js_interop - это устаревший подход, который ломается в новых версиях Flutter.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью