Partager via


Utilisation de l’interface CLI winapp avec Flutter

Ce guide montre comment utiliser l’interface CLI winapp avec une application Flutter pour ajouter une identité de package et empaqueter votre application en tant que MSIX.

L’identité de package est un concept de base dans le modèle Windows app. Il permet à votre application d'accéder à des API Windows spécifiques (telles que les notifications, les API de sécurité, les API IA, etc.), d'avoir une expérience d'installation/désinstallation optimisée, et bien plus encore.

Prerequisites

  1. Kit de développement logiciel (SDK) Flutter : Installez Flutter en suivant le guide officiel.

  2. winapp CLI : Installez l’outil en ligne de commande winapp via winget :

    winget install Microsoft.winappcli --source winget
    

1. Créer une application Flutter

Suivez le guide de la documentation officielle Flutter pour créer une application et l’exécuter.

2. Mettre à jour le code pour vérifier l’identité

Ajouter le package ffi :

flutter pub add ffi

Replacez le contenu de lib/main.dart par le code suivant qui vérifie l'identité du package à l'aide de l'API Windows GetCurrentPackageFamilyName via 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. Exécuter sans identité

Générez et exécutez l’application :

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

Vous devez voir l’application avec un indicateur orange « Non empaqueté ».

4. Initialisez le projet avec l'outil CLI winapp

winapp init

Lorsque vous y êtes invité :

  • Nom du package : appuyez sur Entrée pour accepter la valeur par défaut
  • Publisher nom : appuyez sur Entrée pour accepter la valeur par défaut ou entrer votre nom
  • Version : appuyez sur Entrée pour accepter la version 1.0.0.0
  • Point d’entrée : appuyez sur Entrée pour accepter la valeur par défaut (flutter_app.exe)
  • Setup SDK : sélectionnez « Sdk stables » pour télécharger Windows App SDK et générer des en-têtes C++

5. Déboguer avec l’identité

  1. Générez l’application :

    flutter build windows
    
  2. Appliquer l’identité de débogage :

    winapp create-debug-identity .\build\windows\x64\runner\Release\flutter_app.exe
    
  3. Exécutez l’exécutable :

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

Vous devez voir l’application avec un indicateur vert montrant le nom de la famille de packages.

Note

Étant donné que l'exécutable est remplacé, vous devez réexécuter create-debug-identity après avoir exécuté flutter clean ou reconstruit.

6. Package avec MSIX

  1. Build pour la mise en production :

    flutter build windows
    
  2. Préparer le répertoire du package :

    mkdir dist
    copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
    
  3. Générez un certificat de développement :

    winapp cert generate --if-exists skip
    
  4. Empaqueter et signer :

    winapp pack .\dist --cert .\devcert.pfx
    
  5. Installez le certificat (exécuté en tant qu’administrateur) :

    winapp cert install .\devcert.pfx
    
  6. Installez le package :

    Add-AppxPackage .\flutter-app.msix
    

Conseil / Astuce

  • Le Microsoft Store signe MSIX pour vous, il n’est pas nécessaire de vous connecter avant la soumission.
  • Azure Signature approuvée est un excellent moyen de gérer les certificats en toute sécurité pour les pipelines CI/CD.