Usar a linha de comando winapp com Flutter

Para um exemplo completo e funcional, consulte o exemplo Flutter neste repositório.

Este guia demonstra como usar a winapp CLI com uma aplicação Flutter para adicionar identidade de pacote e empacotar a sua aplicação como MSIX.

A identidade de pacote é um conceito central no modelo de Windows app. Permite que a sua aplicação aceda a APIs específicas do Windows (como Notificações, Segurança, APIs de IA, etc.), tenha uma experiência limpa de instalação/desinstalação e muito mais.

Uma build padrão do Flutter Windows não tem identidade de pacote. Este guia mostra como adicioná-lo para depuração e depois empacotar para distribuição.

Pré-requisitos

  1. Flutter SDK: Instale o Flutter seguindo o guia oficial.

  2. Winapp CLI: Instalar a winapp CLI via winget (ou atualizar se já estiver instalado):

    winget install Microsoft.winappcli --source winget
    

1. Criar uma nova aplicação Flutter

Siga o guia na documentação oficial do Flutter para criar uma nova aplicação e executá-la.

Deverias ver a aplicação padrão do contador Futter.

2. Atualizar o código para verificar a identidade

Vamos atualizar a aplicação para verificar se está a correr com identidade de pacote. Vamos usar o Dart FFI para chamar a API Windows GetCurrentPackageFamilyName.

Primeiro, adicione o ffi pacote:

flutter pub add ffi

De seguida, substitua o conteúdo de lib/main.dart pelo seguinte código. Este código tenta recuperar a identidade atual do pacote usando a API do Windows. Se tiver sucesso, mostra o Nome da Família do Pacote na interface; caso contrário, mostra "Não embalado".

import 'dart:ffi';
import 'dart:io' show Platform;

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

/// Returns the Package Family Name if running with package identity, or null.
String? getPackageFamilyName() {
  if (!Platform.isWindows) return null;

  final kernel32 = DynamicLibrary.open('kernel32.dll');
  final getCurrentPackageFamilyName = kernel32.lookupFunction<
      Int32 Function(Pointer<Uint32>, Pointer<Uint16>),
      int Function(
          Pointer<Uint32>, Pointer<Uint16>)>('GetCurrentPackageFamilyName');

  final length = calloc<Uint32>();
  try {
    // First call to get required buffer length
    final result =
        getCurrentPackageFamilyName(length, Pointer<Uint16>.fromAddress(0));
    if (result != 122) return null; // ERROR_INSUFFICIENT_BUFFER = 122

    // Second call with buffer to get the name
    final namePtr = calloc<Uint16>(length.value);
    try {
      final result2 = getCurrentPackageFamilyName(length, namePtr);
      if (result2 == 0) {
        return namePtr.cast<Utf16>().toDartString(); // ERROR_SUCCESS = 0
      }
      return null;
    } finally {
      calloc.free(namePtr);
    }
  } finally {
    calloc.free(length);
  }
}

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late final String? _packageFamilyName;

  @override
  void initState() {
    super.initState();
    _packageFamilyName = getPackageFamilyName();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              padding: const EdgeInsets.all(16),
              margin: const EdgeInsets.only(bottom: 24),
              decoration: BoxDecoration(
                color: _packageFamilyName != null
                    ? Colors.green.shade50
                    : Colors.orange.shade50,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(
                  color: _packageFamilyName != null
                      ? Colors.green
                      : Colors.orange,
                ),
              ),
              child: Text(
                _packageFamilyName != null
                    ? 'Package Family Name:\n$_packageFamilyName'
                    : 'Not packaged',
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.bodyLarge,
              ),
            ),
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

3. Correr sem identidade

Agora, construa e execute a aplicação como de costume:

flutter build windows

Executa o executável diretamente (substitui flutter_app pelo nome do teu projeto se for diferente):

.\build\windows\x64\runner\Release\flutter_app.exe

Sugestão

A saída da build está na pasta x64, independentemente da arquitetura da tua máquina — isto é esperado para a build Windows do Flutter.

Deverias ver a aplicação com um indicador laranja de "Não embalado". Isto confirma que o executável padrão está a correr sem qualquer identidade de pacote.

4. Inicializar o Project com o winapp CLI

O comando winapp init configura tudo o que precisas de uma só vez: manifesto da app, assets e, opcionalmente, cabeçalhos SDK de Aplicações Windows para desenvolvimento em C++. O manifesto define a identidade da sua aplicação (nome, publicador, versão) que o Windows usa para conceder acesso à API.

Execute o seguinte comando e siga as indicações:

winapp init

Quando solicitado:

  • Nome do pacote: Pressione Enter para aceitar o padrão (derivado do nome do seu projeto)
  • Nome do Publicador: Pressione Enter para aceitar o padrão ou inserir o seu nome
  • Versão: Pressione Enter para aceitar 1.0.0.0
  • Description: Pressione Enter para aceitar a aplicação padrão (Windows Application)
  • Setup SDKs: Selecione "Stable SDKs" para descarregar SDK de Aplicações Windows e gerar cabeçalhos C++ (necessário para o passo 6)

Este comando irá fazer:

  • Criar Package.appxmanifest — o manifesto que define a identidade da sua aplicação
  • Criar Assets pasta — ícones necessários para o empacotamento MSIX e envio para a loja
  • Crie uma pasta .winapp com cabeçalhos e bibliotecas SDK de Aplicações Windows
  • Criar um winapp.yaml ficheiro de configuração para fixar versões do SDK

Pode abrir Package.appxmanifest para personalizar ainda mais propriedades como o nome de visualização, publicador e capacidades.

5. Depuração com Identidade

Para testar funcionalidades que exigem identidade (como Notificações) sem embalar totalmente a aplicação, pode usar winapp run. Isto regista um pacote de layout solto (tal como numa instalação real de MSIX) e inicia a aplicação num só passo. Não é necessário nenhum certificado ou assinatura para a depuração.

  1. Constrói a aplicação:

    flutter build windows
    
  2. Executar com identidade:

    winapp run .\build\windows\x64\runner\Release
    

Sugestão

winapp run Também regista o pacote no seu sistema. É por isso que o MSIX pode aparecer como "já instalado" quando tenta instalá-lo mais tarde, no passo 7. Use winapp unregister para limpar os pacotes de desenvolvimento ao terminar.

Agora deve ver a aplicação com um indicador verde a mostrar:

Package Family Name: flutterapp.debug_xxxxxxxx

Isto confirma que a sua aplicação está a correr com uma identidade de pacote válida!

Sugestão

Para fluxos de trabalho avançados de depuração (anexação de depuradores, configuração do IDE, depuração de arranque), consulte o Guia de Depuração.

6. Utilização do SDK de Aplicações Windows (Opcional)

Se escolheste configurar os SDKs durante winapp init, agora tens acesso a cabeçalhos SDK de Aplicações Windows C++ na pasta .winapp/include. Como o runner do Windows do Fluter é C++, podes chamar APIs do SDK de Aplicações Windows a partir de código nativo e expô-las ao Dart através de um canal de método. Se só precisares de identidade de pacote para distribuição, podes saltar para o passo 7.

Vamos acrescentar um exemplo simples que mostra a versão do Aplicação do Windows Runtime.

Criar o plugin nativo

Criar windows/runner/winapp_sdk_plugin.h:

#ifndef RUNNER_WINAPP_SDK_PLUGIN_H_
#define RUNNER_WINAPP_SDK_PLUGIN_H_

#include <flutter/flutter_engine.h>

// Registers a method channel for querying Windows App SDK info.
void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine);

#endif  // RUNNER_WINAPP_SDK_PLUGIN_H_

Criar windows/runner/winapp_sdk_plugin.cpp:

#include "winapp_sdk_plugin.h"

#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <winrt/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.h>

#include <string>

void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine) {
  auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
      engine->messenger(), "com.example/winapp_sdk",
      &flutter::StandardMethodCodec::GetInstance());

  channel->SetMethodCallHandler(
      [](const flutter::MethodCall<flutter::EncodableValue>& call,
         std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
        if (call.method_name() == "getRuntimeVersion") {
          try {
            // Flutter already initializes COM in main.cpp, so we skip
            // winrt::init_apartment() here — the apartment is already set up.
            auto version = winrt::Microsoft::Windows::ApplicationModel::
                WindowsAppRuntime::RuntimeInfo::AsString();
            std::string versionStr = winrt::to_string(version);
            result->Success(flutter::EncodableValue(versionStr));
          } catch (const winrt::hresult_error& e) {
            result->Error("WINRT_ERROR", winrt::to_string(e.message()));
          } catch (...) {
            result->Error("UNKNOWN_ERROR",
                          "Failed to get Windows App Runtime version");
          }
        } else {
          result->NotImplemented();
        }
      });

  // prevent channel destruction by releasing ownership
  channel.release();
}

Atualizar CMakeLists.txt

Editar windows/runner/CMakeLists.txt para realizar três modificações. Encontre o bloco add_executable e adicione "winapp_sdk_plugin.cpp" à lista de ficheiros de origem:

add_executable(${BINARY_NAME} WIN32
  "flutter_window.cpp"
  "main.cpp"
  "utils.cpp"
  "win32_window.cpp"
  "winapp_sdk_plugin.cpp"       # <-- add this line
  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
  "Runner.rc"
  "runner.exe.manifest"
)

Depois adiciona estas duas linhas no final do ficheiro para ligar as bibliotecas do WinRT e inclui os cabeçalhos do SDK de Aplicações Windows:

# Link Windows Runtime libraries for WinRT
target_link_libraries(${BINARY_NAME} PRIVATE "WindowsApp.lib")

# Windows App SDK headers from winapp CLI
target_include_directories(${BINARY_NAME} PRIVATE
  "${CMAKE_SOURCE_DIR}/../.winapp/include")

Registar o Plugin

Em windows/runner/flutter_window.cpp, adicione o include no topo do ficheiro com os outros includes.

#include "winapp_sdk_plugin.h"

Depois, encontre a RegisterPlugins chamada em FlutterWindow::OnCreate() e adicione RegisterWinAppSdkPlugin na linha logo a seguir:

  RegisterPlugins(flutter_controller_->engine());
  RegisterWinAppSdkPlugin(flutter_controller_->engine());  // <-- add this line

Atualizar main.dart

Adicione a seguinte importação no topo de lib/main.dart, juntamente com as importações existentes:

import 'package:flutter/services.dart';

Adicione esta função abaixo da função existente getPackageFamilyName() (fora de qualquer classe):

/// Queries the Windows App Runtime version via a native method channel.
Future<String?> getWindowsAppRuntimeVersion() async {
  if (!Platform.isWindows) return null;
  try {
    const channel = MethodChannel('com.example/winapp_sdk');
    final version = await channel.invokeMethod<String>('getRuntimeVersion');
    return version;
  } catch (_) {
    return null;
  }
}

Na _MyHomePageState classe, adicione um novo campo ao lado do existente _packageFamilyName:

  late final String? _packageFamilyName;
  String? _runtimeVersion;         // <-- add this line

Atualize initState() para chamar a nova função.

  @override
  void initState() {
    super.initState();
    _packageFamilyName = getPackageFamilyName();
    // Fetch the runtime version asynchronously
    getWindowsAppRuntimeVersion().then((version) {
      setState(() {
        _runtimeVersion = version;
      });
    });
  }

Finalmente, mostrar a versão em tempo de execução no build método. Adicione este widget dentro da lista de Column filhos, logo a seguir a Container, que mostra a identidade do pacote.

            if (_runtimeVersion != null)
              Padding(
                padding: const EdgeInsets.only(bottom: 16),
                child: Text(
                  'Windows App Runtime: $_runtimeVersion',
                  style: Theme.of(context).textTheme.bodyLarge,
                ),
              ),

Compilar e Executar

Reconstrua a aplicação:

flutter build windows
winapp run .\build\windows\x64\runner\Release

Agora deverá ver resultados como:

Package Family Name: flutterapp.debug_xxxxxxxx
Windows App Runtime: 8000.731.1532.0

O diretório .winapp/include contém todos os cabeçalhos necessários para SDK de Aplicações Windows, incluindo:

  • winrt/ - Cabeçalhos de projeção C++ do WinRT para acesso às APIs do Windows Runtime
  • Microsoft.UI.*.h - Cabeçalhos WinUI 3 para componentes modernos de UI
  • MddBootstrap.h - Inicialização do SDK do Windows App
  • WindowsAppSDK-VersionInfo.h - Informação de versão
  • E muitos mais componentes do SDK de Aplicações Windows

Para uso mais avançado de SDK de Aplicações Windows, consulte a documentação SDK de Aplicações Windows.

7. Pacote com MSIX

Quando estiver pronto para distribuir a sua aplicação, pode empacotá-la como MSIX usando o mesmo manifesto.

Preparar o Diretório de Pacotes

Primeiro, constrói a tua aplicação em modo de lançamento:

flutter build windows

Depois, crie um diretório com os seus ficheiros de lançamento:

mkdir dist
copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse

A saída da compilação do Flutter Windows inclui o executável flutter_windows.dll e uma pasta data — todas necessárias.

Gerar um Certificado de Desenvolvimento

Antes de empacotar, precisa de um certificado de desenvolvimento para assinar. Gera uma, caso ainda não o tenha feito:

winapp cert generate --if-exists skip

Assinar e Embalar

Agora pode embalar e assinar:

winapp pack .\dist --cert .\devcert.pfx

Nota: O pack comando usa automaticamente o Package.appxmanifest do teu diretório atual e copia-o para a pasta de destino antes de ser embalado.

Instalar o Certificado

Antes de poderes instalar o pacote MSIX, precisas de confiar no certificado de desenvolvimento da tua máquina. Executa este comando como administrador (só precisas de o fazer uma vez por certificado):

winapp cert install .\devcert.pfx

Instalar e Executar

Sugestão

Se usou winapp run no passo 5, a encomenda pode já estar registada no seu sistema. Use winapp unregister primeiro para remover o registo de desenvolvimento, depois instale o pacote de lançamento.

Instale o pacote fazendo duplo clique no ficheiro gerado .msix , ou usando o PowerShell:

Add-AppxPackage .\flutterapp.msix

Sugestão

O nome do ficheiro MSIX inclui a versão e a arquitetura (por exemplo, flutterapplication1_1.0.0.0_x64.msix). Verifique o seu diretório para o nome exato do ficheiro. Se precisares de reempacotar após alterações de código, incrementa o Version no teu Package.appxmanifest — Windows requer um número de versão superior para atualizar um pacote instalado.

Tips

  1. Quando estiver pronto para a distribuição, pode assinar o seu MSIX com um certificado de assinatura de código de uma Autoridade Certificadora, para que os seus utilizadores não tenham de instalar um certificado auto-assinado.
  2. O serviço Assinatura Confiável do Azure é uma excelente forma de gerir os seus certificados de forma segura e integrar a assinatura no seu pipeline CI/CD.
  3. A Microsoft Store assina o MSIX por si, não precisa de assinar antes de submeter.

Próximas Etapas

  • Distribua via winget: Submeta o seu MSIX ao Repositório Comunitário Windows Gestor de Pacotes
  • Publicar no Microsoft Store: Use winapp store para submeter o seu pacote
  • Configurar CI/CD: Use o GitHub Action para automatizar o empacotamento no seu pipeline
  • Explore Windows APIs: Com a identidade do pacote, pode agora usar Notifications, on-device AI e outras APIs dependentes da identidade