Flutter: Implantação Multiplataforma Web e Desktop

Escreva uma vez, execute em qualquer lugar — Web e Desktop levam seu código do mobile para o mundo.

📋 Pré-requisitos: Você deve ter dominado o seguinte primeiro

1. O Que Você Vai Aprender


2. Uma História de Ficar Só no Mobile

(1) A Dor: Crescimento de Usuários Mobile Estagnado

O DAU mobile do ShopApp do Bob caiu de 50% ao mês para 5%. Dados de mercado mostram: 40% dos pedidos de e-commerce vêm do desktop (com valor médio de pedido 2x maior), e SEO Web pode trazer tráfego gratuito. Mas Bob só tem uma base de código Flutter — construir Web/Desktop do zero levaria pelo menos 3 meses.

(2) Solução Multiplataforma do Flutter

O Flutter compila a mesma base de código para Web e Desktop — você só precisa lidar com adaptação de layout e funcionalidades específicas de plataforma.

BASH
# Build para web
flutter build web --wasm

# Build para Windows
flutter build windows

# Build para macOS
flutter build macos
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(3) O Resultado: 0 Desenvolvimento Extra + 3 Dias para Lançar a Versão Web

Bob gastou apenas 3 dias em adaptação de layout, e a versão Web estava no ar. O painel administrativo Desktop reutilizou 80% do código mobile e foi completado em 2 semanas.


3. Arquitetura de Implantação Multiplataforma

100%
graph TD
    SC[Single Codebase] --> WEB[Flutter Web]
    SC --> DSK[Desktop]
    WEB --> WASM[WASM / CanvasKit]
    WEB --> SEO2[URL Strategy / SEO]
    DSK --> WIN[Windows]
    DSK --> MAC[macOS]
    DSK --> LNX[Linux]
    DSK --> WM[window_manager]
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(1) Visão Geral de Diferenças entre Plataformas

Recurso Mobile Web Desktop
Entrada Toque Mouse + Teclado Mouse + Teclado
Tela Orientação fixa Tamanho variável Janela redimensionável
Sistema de arquivos Sandbox Nenhum Acesso completo
Push FCM/APNs Notification API Sem suporte nativo
Resolução 1x-4x Pixels CSS Alto DPI

4. Build e Otimização do Flutter Web

(1) Comparação de Mecanismos de Renderização Web

Mecanismo Primeiro Carregamento Desempenho de Renderização Compatibilidade Caso de Uso Recomendado
CanvasKit Maior (2MB+) Alto (GPU) Bom Padrão de produção
Renderizador HTML Menor Baixo (DOM) Regular Aplicativos leves
WASM (Skwasm) Médio Mais alto Flutter 3.22+ Padrão futuro

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Configuração de build Web

BASH
# Build com CanvasKit (padrão)
flutter build web

# Build com WASM (Flutter 3.22+, recomendado)
flutter build web --wasm

# Build com renderizador HTML
flutter build web --renderer html

# Base href personalizado para implantação em subdiretório
flutter build web --base-href "/shop/"
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(2) Estratégia de URL

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Estratégia de URL por caminho

DART
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/url_strategy.dart';

// ShopApp vem do projeto lib/app.dart

void main() {
  // Usar estratégia de URL por caminho (URLs limpas: /product/42)
  usePathUrlStrategy();

  // OU usar estratégia de URL hash (compatibilidade: /#/product/42)
  // useHashUrlStrategy();

  runApp(const ShopApp());
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
Estratégia Formato da URL Configuração do Servidor SEO
Caminho /product/42 Requer roteamento fallback Bom
Hash /#/product/42 Nenhuma configuração necessária Ruim

(3) Otimização de SEO Web

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Meta tags e semântica

HTML
<!-- web/index.html -->
`<head>`
  <meta name="description" content="ShopApp - Milhões de produtos, pagamento em USD">
  <meta name="keywords" content="ecommerce, loja, ofertas">
  <meta property="og:title" content="ShopApp">
  <meta property="og:description" content="Aplicativo de e-commerce multiplataforma">
  `<title>`ShopApp - Compre o Mundo</title>
</head>
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
💡 Dica: O Flutter Web é um SPA — rastreadores de mecanismos de busca têm suporte limitado para SPAs. Para páginas com muito conteúdo, considere uma solução SSR (como dart_frog) ou pré-renderização.


5. Layout Responsivo

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Breakpoints adaptativos com LayoutBuilder

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

// ResponsiveLayout: componente de layout adaptativo para três plataformas

class ResponsiveLayout extends StatelessWidget {
  final Widget mobile;
  final Widget tablet;
  final Widget desktop;

  const ResponsiveLayout({
    super.key,
    required this.mobile,
    required this.tablet,
    required this.desktop,
  });

  static bool isMobile(BuildContext context) => MediaQuery.sizeOf(context).width < 600;
  static bool isTablet(BuildContext context) =>
      MediaQuery.sizeOf(context).width >= 600 && MediaQuery.sizeOf(context).width < 1024;
  static bool isDesktop(BuildContext context) => MediaQuery.sizeOf(context).width >= 1024;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth >= 1024) return desktop;
        if (constraints.maxWidth >= 600) return tablet;
        return mobile;
      },
    );
  }
}

// Uso
ResponsiveLayout(
  mobile: const MobileProductGrid(crossAxisCount: 2),
  tablet: const TabletProductGrid(crossAxisCount: 3),
  desktop: const DesktopProductLayout(), // Barra lateral + grid
)
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Layout da homepage Web do ShopApp

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

// CategorySidebar / CartPanel / productCards vêm do projeto ShopApp

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ShopApp'),
        actions: [
          // Apenas desktop: barra de busca no AppBar
          if (ResponsiveLayout.isDesktop(context))
            const SizedBox(width: 300, child: SearchBar()),
          IconButton(icon: const Icon(Icons.shopping_cart), onPressed: () {}),
          IconButton(icon: const Icon(Icons.person), onPressed: () {}),
        ],
      ),
      body: ResponsiveLayout(
        mobile: _buildMobileLayout(),
        tablet: _buildTabletLayout(),
        desktop: _buildDesktopLayout(),
      ),
    );
  }

  Widget _buildDesktopLayout() {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // Categorias na barra lateral
        SizedBox(
          width: 240,
          child: CategorySidebar(),
        ),
        // Conteúdo principal
        Expanded(
          child: GridView.count(
            crossAxisCount: 4,
            children: productCards,
          ),
        ),
        // Painel do carrinho (apenas desktop)
        SizedBox(
          width: 300,
          child: CartPanel(),
        ),
      ],
    );
  }
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

6. Adaptação Desktop

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: window_manager para gerenciamento de janelas

DART
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';

// Dependência: window_manager: ^0.3.0 (adicione às dependências)
// ShopApp vem do projeto lib/app.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await windowManager.ensureInitialized();

  WindowOptions windowOptions = const WindowOptions(
    size: Size(1200, 800),
    minimumSize: Size(800, 600),
    center: true,
    backgroundColor: Colors.transparent,
    skipTaskbar: false,
    titleBarStyle: TitleBarStyle.normal,
    title: 'ShopApp',
  );

  windowManager.waitUntilReadyToShow(windowOptions, () async {
    await windowManager.show();
    await windowManager.focus();
  });

  runApp(const ShopApp());
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Atalhos de teclado

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

// Dependência: go_router: ^13.0.0 (adicione às dependências)
// showSearch / router vêm do projeto ShopApp

class ShortcutWrapper extends StatelessWidget {
  final Widget child;
  const ShortcutWrapper({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: {
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): const Intent(#search),
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyK): const Intent(#cart),
        LogicalKeySet(LogicalKeyboardKey.escape): const Intent(#back),
      },
      child: Actions(
        actions: {
          Intent(#search): CallbackAction(onInvoke: (_) => showSearch(context)),
          Intent(#cart): CallbackAction(onInvoke: (_) => context.go('/cart')),
          Intent(#back): CallbackAction(onInvoke: (_) => Navigator.maybePop(context)),
        },
        child: child,
      ),
    );
  }
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

7. Tratamento de Diferenças entre Plataformas

▶ Exemplo

TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Imports condicionais e detecção de plataforma

DART
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;

// Nota: dart:io não está disponível na Web — projetos reais precisam de imports condicionais
// NativePayButton / CreditCardOption / PayPalOption vêm do projeto ShopApp

class PlatformHelper {
  static bool get isWeb => kIsWeb;
  static bool get isMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS);
  static bool get isDesktop => !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);
  static bool get isApple => !kIsWeb && (Platform.isIOS || Platform.isMacOS);
  static bool get supportsNativePay => isApple || (!kIsWeb && Platform.isAndroid);
}

// Uso no widget
Widget buildPaymentOptions() {
  return Column(children: [
    if (PlatformHelper.supportsNativePay)
      NativePayButton(),
    CreditCardOption(),
    if (!PlatformHelper.isWeb)
      PayPalOption(), // PayPal SDK não disponível na web
  ]);
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

8. Exemplo Completo: Ponto de Entrada Multiplataforma do ShopApp

DART
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:window_manager/window_manager.dart';
import 'dart:io' show Platform;

// Dependências: flutter_riverpod: ^2.0.0, firebase_core: ^2.0.0, window_manager: ^0.3.0
// DefaultFirebaseOptions / initHive / PlatformHelper / ShortcutWrapper / router / ShopApp vêm do projeto ShopApp
// themeModeNotifierProvider / shopAppLightTheme / shopAppDarkTheme vêm do módulo de tema do ShopApp

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Inicialização específica por plataforma
  if (PlatformHelper.isDesktop) {
    await _initDesktop();
  }

  // Inicialização comum
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  await initHive();

  runApp(ProviderScope(child: const ShopApp()));
}

Future<void> _initDesktop() async {
  await windowManager.ensureInitialized();
  const windowOptions = WindowOptions(
    size: Size(1200, 800),
    minimumSize: Size(800, 600),
    title: 'ShopApp Admin',
  );
  windowManager.waitUntilReadyToShow(windowOptions, () async {
    await windowManager.show();
  });
}

class ShopApp extends ConsumerWidget {
  const ShopApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(themeModeNotifierProvider);
    return MaterialApp.router(
      title: PlatformHelper.isDesktop ? 'ShopApp Admin' : 'ShopApp',
      theme: shopAppLightTheme,
      darkTheme: shopAppDarkTheme,
      themeMode: themeMode,
      routerConfig: router,
      builder: (context, child) {
        // Desktop: adicionar atalhos
        if (PlatformHelper.isDesktop) {
          return ShortcutWrapper(child: child!);
        }
        return child!;
      },
    );
  }
}
TEXT
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

❓ Perguntas Frequentes

P: O Flutter Web é adequado para sites com SEO? R: Não muito. O Flutter Web é um SPA — rastreadores de mecanismos de busca têm dificuldade em indexá-lo. Para sites de conteúdo com SEO, use Nuxt/Next.js. O Flutter Web é mais adequado para aplicativos do tipo ferramenta.

P: Qual a diferença entre WASM e CanvasKit? R: CanvasKit renderiza usando JS + WebGL; WASM usa C++ compilado para WebAssembly com melhor desempenho. Flutter 3.22+ recomenda WASM.

P: Aplicativos Desktop podem ser publicados na Mac App Store? R: Sim. Você precisa de uma conta Apple Developer, usar Xcode Archive para empacotar como DMG/PKG, e enviar via App Store Connect para revisão.

P: Como compartilhar código entre Web e Mobile? R: 90% do código é totalmente compartilhado. Apenas funcionalidades específicas de plataforma (pagamento/push/sistema de arquivos) precisam de lógica condicional. Use PlatformHelper para encapsular diferenças de plataforma.

P: Quais são os melhores breakpoints para layout responsivo? R: Mobile < 600px, Tablet 600-1024px, Desktop > 1024px. Consulte a especificação de breakpoints do Material Design.

P: Como o desempenho de aplicativos Desktop se compara ao nativo? R: O desempenho do Flutter Desktop é próximo ao nativo (renderização GPU), mas a inicialização é mais lenta que a nativa (precisa carregar o motor). Supera o Electron em cenários de animação complexa.


📖 Resumo


📝 Exercícios

  1. Básico (dificuldade ⭐): Faça build de uma versão Web com flutter build web, execute no navegador, e configure a estratégia de URL por caminho.
  2. Intermediário (dificuldade ⭐⭐): Implemente ResponsiveLayout com adaptação para três plataformas: mobile grid de 2 colunas, tablet 3 colunas, desktop barra lateral + 4 colunas.
  3. Desafio (dificuldade ⭐⭐⭐): Implemente uma versão multiplataforma completa do ShopApp: frontend de e-commerce Web responsivo + painel administrativo Desktop (window_manager + atalhos de teclado + barra de menu), compartilhando 90% do código.

← Aula Anterior | Próxima Aula →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%