Compartir a través de


Uso de la CLI de winapp con Flutter

En esta guía se muestra cómo usar la CLI de winapp con una aplicación flutter para agregar la identidad del paquete y empaquetar la aplicación como MSIX.

La identidad del paquete es un concepto básico en el modelo de Windows app. Permite a tu aplicación acceder a APIs específicas de Windows (como notificaciones, seguridad, APIs de IA, etc.), tener una experiencia de instalación y desinstalación limpia, etc.

Prerrequisitos

  1. SDK de Flutter: instale Flutter siguiendo la guía oficial.

  2. CLI de winapp: Instale el CLI mediante Winget:

    winget install Microsoft.winappcli --source winget
    

1. Creación de una nueva aplicación flutter

Siga la guía de la documentación oficial de Flutter para crear una nueva aplicación y ejecutarla.

2. Actualización del código para comprobar la identidad

Incorporación del paquete ffi:

flutter pub add ffi

Reemplace el contenido de lib/main.dart por el código siguiente que comprueba la identidad del paquete mediante la API de Windows GetCurrentPackageFamilyName a través de Dart FFI:

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

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

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 {
    final result =
        getCurrentPackageFamilyName(length, Pointer<Uint16>.fromAddress(0));
    if (result != 122) return null; // ERROR_INSUFFICIENT_BUFFER = 122

    final namePtr = calloc<Uint16>(length.value);
    try {
      final result2 = getCurrentPackageFamilyName(length, namePtr);
      if (result2 == 0) {
        return namePtr.cast<Utf16>().toDartString();
      }
      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. Ejecutar sin identidad

Compile y ejecute la aplicación:

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

Debería ver la aplicación con un indicador naranja "No empaquetado".

4. Inicie el proyecto con la CLI de winapp

winapp init

Cuando se le solicite,

  • Nombre del paquete: presione Entrar para aceptar el valor predeterminado.
  • Nombre del editor: Presione Intro para aceptar el valor predeterminado o escriba su nombre.
  • Versión: presione Entrar para aceptar 1.0.0.0.
  • Punto de entrada: presione Entrar para aceptar el valor predeterminado (flutter_app.exe)
  • Setup SDK: seleccione "SDK estables" para descargar Windows App SDK y generar encabezados de C++

5. Depurar con identidad

  1. Compile la aplicación:

    flutter build windows
    
  2. Aplicar la identidad de depuración:

    winapp create-debug-identity .\build\windows\x64\runner\Release\flutter_app.exe
    
  3. Ejecute el archivo ejecutable:

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

Debería ver la aplicación con un indicador verde que muestra el nombre de familia del paquete.

Nota:

Después de ejecutar flutter clean o volver a generar, tendrá que volver a ejecutar create-debug-identity, ya que el ejecutable ha sido reemplazado.

6. Empaquetar con MSIX

  1. Compilación para lanzamiento:

    flutter build windows
    
  2. Prepare el directorio del paquete:

    mkdir dist
    copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
    
  3. Genere un certificado de desarrollo:

    winapp cert generate --if-exists skip
    
  4. Empaquetar y firmar:

    winapp pack .\dist --cert .\devcert.pfx
    
  5. Instale el certificado (ejecute como administrador):

    winapp cert install .\devcert.pfx
    
  6. Instale el paquete:

    Add-AppxPackage .\flutter-app.msix
    

Sugerencia

  • La Microsoft Store firma el MSIX por ti, no es necesario firmar antes de la submisión.
  • Azure Firma de confianza es una excelente manera de administrar certificados de forma segura para los pipelines de CI/CD.