Usando a CLI do "winapp" com o Flutter

Para obter um exemplo de trabalho completo, confira o Flutter Sample neste repositório.

Este guia demonstra como usar a winapp CLI com um aplicativo Flutter para adicionar a identidade do pacote e empacotar seu aplicativo como um MSIX.

A identidade do pacote é um conceito básico no modelo de Windows app. Ele permite que seu aplicativo acesse APIs de Windows específicas (como Notificações, Segurança, APIs de IA etc.), ter uma experiência de instalação/desinstalação limpa e muito mais.

Um build de Windows do Flutter padrão não tem a identidade do pacote. Este guia mostra como adicionar para depuração e, em seguida, empacotar para distribuição.

Pré-requisitos

  1. SDK do Flutter: instale o Flutter seguindo o guia oficial.

  2. CLI do winapp: instalar a winapp CLI por meio do winget (ou atualizar se já estiver instalado):

    winget install Microsoft.winappcli --source winget
    

1. Criar um novo aplicativo Flutter

Siga o guia nos documentos oficiais do Flutter para criar um novo aplicativo e executá-lo.

Você deve ver o aplicativo padrão de contador do Flutter.

2. Atualizar código para verificar identidade

Atualizaremos o aplicativo para verificar se ele está em execução com a identidade do pacote. Usaremos o Dart FFI para chamar a API Windows GetCurrentPackageFamilyName.

Primeiro, adicione o ffi pacote:

flutter pub add ffi

Em seguida, substitua o conteúdo de lib/main.dart pelo seguinte código. Esse código tenta recuperar a identidade do pacote atual usando a API Windows. Se for bem-sucedido, ele exibirá o Nome da Família de Pacotes na interface do usuário; caso contrário, ele mostrará "Não empacotado".

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. Executar sem identidade

Agora, crie e execute o aplicativo como de costume:

flutter build windows

Execute o executável diretamente (substitua flutter_app pelo nome do projeto se diferente):

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

Dica

A saída de build está na pasta x64 independentemente da arquitetura do computador. Isso é esperado para o build Windows do Flutter.

Você deve ver o aplicativo com um indicador laranja "Não empacotado". Isso confirma que o executável padrão está em execução sem nenhuma identidade de pacote.

4. Inicializar Project com a CLI do winapp

O comando winapp init configura tudo o que você precisa de uma só vez: manifesto do aplicativo, ativos e, opcionalmente, SDK do Aplicativo Windows cabeçalhos para o desenvolvimento do C++. O manifesto define a identidade do aplicativo (nome, editor, versão) que Windows usa para conceder acesso à API.

Execute o seguinte comando e siga os prompts:

winapp init

Quando solicitado:

  • Nome do pacote: Pressione Enter para aceitar o padrão (derivado do nome do projeto)
  • Nome do publicador: pressione Enter para aceitar o valor padrão ou insira seu nome
  • Versão: Pressione Enter para aceitar 1.0.0.0
  • Description: Pressione Enter para aceitar o padrão (aplicativo Windows)
  • Setup SDKs: selecione "SDKs estáveis" para baixar SDK do Aplicativo Windows e gerar cabeçalhos C++ (necessários para a etapa 6)

Este comando será:

  • Criar Package.appxmanifest — o manifesto que define a identidade do aplicativo
  • Criar Assets pasta — ícones necessários para empacotamento MSIX e envio para a Loja
  • Criar uma pasta .winapp com SDK do Aplicativo Windows cabeçalhos e bibliotecas
  • Criar um winapp.yaml arquivo de configuração para fixar versões do SDK

Você pode abrir Package.appxmanifest para personalizar ainda mais as propriedades, como o nome de exibição, o editor e os recursos.

5. Depurar com Identidade

Para testar recursos que exigem identidade (como Notificações) sem empacotar totalmente o aplicativo, você pode usar winapp run. Isso registra um pacote de layout solto (como uma instalação real de MSIX) e inicia o aplicativo em uma etapa. Nenhum certificado ou assinatura é necessário para depuração.

  1. Compile o aplicativo:

    flutter build windows
    
  2. Executar com identidade:

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

Dica

winapp run também registra o pacote em seu sistema. É por isso que o MSIX pode aparecer como "já instalado" quando você tentar instalá-lo posteriormente na etapa 7. Use winapp unregister para limpar pacotes de desenvolvimento quando terminar.

Agora você deve ver o aplicativo com um indicador verde mostrando:

Package Family Name: flutterapp.debug_xxxxxxxx

Isso confirma que seu aplicativo está em execução com uma identidade de pacote válida!

Dica

Para obter fluxos de trabalho avançados de depuração (anexando depuradores, configuração de IDE, depuração de inicialização), consulte o Guia de Depuração.

6. Usando SDK do Aplicativo Windows (opcional)

Se você selecionou configurar os SDKs durante winapp init, agora terá acesso aos cabeçalhos C++ do SDK do Aplicativo Windows na pasta .winapp/include. Como o runner do Windows do Flutter é C++, você pode chamar APIs do SDK do Aplicativo Windows a partir do código nativo e expô-las ao Dart por meio de um canal de método. Se você precisar apenas da identidade do pacote para distribuição, poderá pular para a etapa 7.

Vamos adicionar um exemplo simples que exibe a versão aplicativo do Windows Runtime.

Criar o plug-in 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

Edite windows/runner/CMakeLists.txt para fazer três alterações. Localize o add_executable bloco e adicione "winapp_sdk_plugin.cpp" à lista de arquivos 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"
)

Em seguida, adicione estas duas linhas no final do arquivo para vincular bibliotecas WinRT e incluir os cabeçalhos SDK do Aplicativo 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")

Registrar o plug-in

No windows/runner/flutter_window.cpp, adicione o include no topo do arquivo junto com os outros includes.

#include "winapp_sdk_plugin.h"

Em seguida, encontre a chamada RegisterPlugins em FlutterWindow::OnCreate() e adicione RegisterWinAppSdkPlugin na linha logo após ela.

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

Atualizar main.dart

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

import 'package:flutter/services.dart';

Adicione essa 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;
  }
}

_MyHomePageState Na 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;
      });
    });
  }

Por fim, exiba a versão de runtime no build método. Adicione esse widget dentro da Column lista de filhos, logo após o 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

Recompile o aplicativo:

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

Agora você deve ver uma saída 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 do Aplicativo Windows, incluindo:

  • winrt/ – Cabeçalhos de projeção do WinRT C++ para acessar APIs do Windows Runtime
  • cabeçalhos Microsoft.UI.*.h – WinUI 3 para componentes modernos da interface do usuário
  • MddBootstrap.h – inicialização SDK do Aplicativo Windows
  • WindowsAppSDK-VersionInfo.h - Informações de versão
  • E muitos outros componentes do Kit de Desenvolvimento de Aplicativos do Windows

Para obter um uso mais avançado de SDK do Aplicativo Windows, confira a documentação SDK do Aplicativo Windows.

7. Empacotar com MSIX

Quando estiver pronto para distribuir seu aplicativo, você poderá empacotá-lo como um MSIX usando o mesmo manifesto.

Preparar o diretório do pacote

Primeiro, crie seu aplicativo no modo de versão:

flutter build windows

Em seguida, crie um diretório com seus arquivos de versão:

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

A saída de build do Flutter Windows inclui o executável, flutter_windows.dll e uma pasta data — todos os quais são necessários.

Gerar um certificado de desenvolvimento

Antes de empacotar, você precisa de um certificado de desenvolvimento para assinatura. Gere um, se ainda não tiver feito isso.

winapp cert generate --if-exists skip

Assinar e empacotar

Agora você pode empacotar e assinar:

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

Observação: o comando pack usa automaticamente o Package.appxmanifest do seu diretório atual e copia-o para a pasta de destino antes do empacotamento.

Instalar o certificado

Antes de instalar o pacote MSIX, você precisa confiar no certificado de desenvolvimento em seu computador. Execute este comando como administrador (você só precisa fazer isso uma vez por certificado):

winapp cert install .\devcert.pfx

Instalar e executar

Dica

Se você usou winapp run na etapa 5, o pacote pode já estar registrado em seu sistema. Use winapp unregister primeiro para remover o registro de desenvolvimento e, em seguida, instale o pacote de lançamento.

Instale o pacote clicando duas vezes no arquivo gerado .msix ou usando o PowerShell:

Add-AppxPackage .\flutterapp.msix

Dica

O nome do arquivo MSIX inclui a versão e a arquitetura (por exemplo, flutterapplication1_1.0.0.0_x64.msix). Verifique o diretório para obter o nome de arquivo exato. Se você precisar reempacotar após alterações de código, incremente o Version em seu Package.appxmanifest — Windows requer um número de versão mais alto para atualizar um pacote instalado.

Dicas

  1. Quando estiver pronto para distribuição, você poderá assinar seu MSIX com um certificado de assinatura de código de uma Autoridade de Certificação para que os usuários não precisem instalar um certificado autoassinado.
  2. O serviço Assinatura Confiável do Azure é uma ótima maneira de gerenciar seus certificados com segurança e integrar a assinatura no pipeline de CI/CD.
  3. O Microsoft Store assinará o MSIX para você, não é necessário assinar antes do envio.

Próximas etapas