Condividi tramite


Uso dell'interfaccia della riga di comando di winapp con Flutter

Questa guida illustra come usare l'interfaccia della riga di comando di winapp con un'applicazione Flutter per aggiungere l'identità del pacchetto e creare un pacchetto dell'app come MSIX.

L'identità del pacchetto è un concetto di base nel modello di Windows app. Consente all'applicazione di access API Windows specifiche (ad esempio Notifiche, Sicurezza, API di intelligenza artificiale e così via), avere un'esperienza di installazione/disinstallazione pulita e altro ancora.

Prerequisiti

  1. Flutter SDK: installare Flutter seguendo la guida ufficiale.

  2. CLI winapp: installare il winapp CLI tramite winget:

    winget install Microsoft.winappcli --source winget
    

1. Creare una nuova app Flutter

Seguire la guida alla documentazione ufficiale di Flutter per creare una nuova applicazione ed eseguirla.

2. Aggiornare il codice per verificare l'identità

Aggiungere il pacchetto ffi:

flutter pub add ffi

Sostituire il contenuto di lib/main.dart con il codice seguente che verifica l'identità del pacchetto usando l'API Windows GetCurrentPackageFamilyName tramite 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. Eseguire senza identità

Compilare ed eseguire l'app:

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

Dovrebbe essere visualizzata l'app con un indicatore arancione "Non incluso nel pacchetto".

4. Inizializzare il progetto con la CLI di winapp

winapp init

Quando richiesto:

  • Nome pacchetto: premere INVIO per accettare l'impostazione predefinita
  • Publisher nome: premere INVIO per accettare il valore predefinito o immettere il nome
  • Versione: premere INVIO per accettare 1.0.0.0
  • Punto di ingresso: premere INVIO per accettare il valore predefinito (flutter_app.exe)
  • Setup SDKs: Selezionare "SDK Stabili" per scaricare Windows App SDK e generare intestazioni C++

5. Eseguire il debug con l'identità

  1. Compilare l'app:

    flutter build windows
    
  2. Applicare l'identità di debug:

    winapp create-debug-identity .\build\windows\x64\runner\Release\flutter_app.exe
    
  3. Eseguire il file eseguibile:

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

Verrà visualizzata l'app con un indicatore verde che mostra il nome della famiglia di pacchetti.

Annotazioni

Dopo l'esecuzione flutter clean o la ricompilazione, è necessario eseguire nuovamente l'esecuzione create-debug-identity perché l'eseguibile viene sostituito.

6. Pacchetto con MSIX

  1. Compilazione per il rilascio:

    flutter build windows
    
  2. Preparare la directory del pacchetto:

    mkdir dist
    copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
    
  3. Generare un certificato di sviluppo:

    winapp cert generate --if-exists skip
    
  4. Pacchetto e firma:

    winapp pack .\dist --cert .\devcert.pfx
    
  5. Installare il certificato (esegui come amministratore):

    winapp cert install .\devcert.pfx
    
  6. Installare il pacchetto:

    Add-AppxPackage .\flutter-app.msix
    

Suggerimento

  • Microsoft Store firma MSIX per te, non è necessario firmare prima dell'invio.
  • Azure Trusted Signing è un ottimo modo per gestire i certificati in modo sicuro per le pipeline CI/CD.