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
- Otimização de build Flutter Web: WASM / renderização CanvasKit / estratégia de URL / SEO
- Layout responsivo Web: MediaQuery / LayoutBuilder / design adaptativo de breakpoints
- Adaptação Desktop: gerenciamento de janelas com window_manager / barra de menu / atalhos de mouse e teclado
- Tratamento de diferenças de plataforma: kIsWeb / Platform.isAndroid imports condicionais e adaptação
- ShopApp: frontend de e-commerce responsivo na versão Web + painel administrativo na versão Desktop
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.
# Build para web
flutter build web --wasm
# Build para Windows
flutter build windows
# Build para macOS
flutter build macos
> 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
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]
> 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
> 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
# 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/"
> 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
> 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
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());
}
> 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
> 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
<!-- 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>
> 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.
5. Layout Responsivo
▶ Exemplo
> 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
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
)
> 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
> 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
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(),
),
],
);
}
}
> 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
> 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
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());
}
> 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
> 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
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,
),
);
}
}
> 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
> 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
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
]);
}
> 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
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!;
},
);
}
}
> 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
- O Flutter Web suporta mecanismos de renderização CanvasKit e WASM; WASM oferece o melhor desempenho
- A estratégia de URL por caminho é amigável para SEO, mas requer configuração de roteamento fallback no servidor
- LayoutBuilder + breakpoints alcançam layout adaptativo para três plataformas: Mobile/Tablet/Desktop
- window_manager gerencia tamanho de janela Desktop, barra de título e dimensões mínimas
- kIsWeb e Platform.isXxx detectam a plataforma para tratamento condicional de diferenças
📝 Exercícios
- 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. - 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.
- 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.