Menggunakan winapp CLI dengan Flutter

Panduan ini menunjukkan cara menggunakan CLI winapp dengan aplikasi Flutter untuk menambahkan identitas paket dan mengemas aplikasi Anda sebagai MSIX.

Identitas paket adalah konsep inti dalam model Windows app. Ini memungkinkan aplikasi Anda untuk mengakses API Windows tertentu (seperti Pemberitahuan, Keamanan, API AI, dll.), merasakan pengalaman pemasangan/penghapusan instalasi yang lebih bersih, dan banyak lagi.

Prasyarat

  1. Flutter SDK: Instal Flutter mengikuti panduan resmi.

  2. winapp CLI: Instal winapp CLI melalui winget:

    winget install Microsoft.winappcli --source winget
    

1. Buat aplikasi Flutter baru

Ikuti panduan di dokumen Flutter resmi untuk membuat aplikasi baru dan menjalankannya.

2. Perbarui kode untuk memeriksa identitas

ffi Tambahkan paket tersebut:

flutter pub add ffi

Ganti konten lib/main.dart dengan kode berikut yang memeriksa identitas paket menggunakan WINDOWS GetCurrentPackageFamilyName API melalui 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. Jalankan tanpa identitas

Buat dan jalankan aplikasi:

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

Anda akan melihat aplikasi dengan indikator oranye "Tidak dikemas".

4. Inisialisasi project dengan winapp CLI

winapp init

Ketika diminta:

  • Nama paket: Tekan Enter untuk menerima default
  • Nama penerbit: Tekan Enter untuk menerima pengaturan standar atau masukkan nama Anda
  • Versi: Tekan Enter untuk menerima 1.0.0.0
  • Titik masuk: Tekan Enter untuk menerima default (flutter_app.exe)
  • Setup SDK: Pilih "SDK Stabil" untuk mengunduh Windows App SDK dan menghasilkan header C++

5. Debug menggunakan identitas

  1. Buat aplikasi:

    flutter build windows
    
  2. Terapkan identitas debug:

    winapp create-debug-identity .\build\windows\x64\runner\Release\flutter_app.exe
    
  3. Jalankan executable:

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

Anda akan melihat aplikasi dengan indikator hijau yang menunjukkan Nama Paket Keluarga.

Nota

Setelah menjalankan flutter clean atau membangun kembali, Anda harus menjalankan create-debug-identity kembali karena executable diganti.

6. Paket dengan MSIX

  1. Bangun untuk rilis:

    flutter build windows
    
  2. Siapkan direktori paket:

    mkdir dist
    copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
    
  3. Buat sertifikat pengembangan:

    winapp cert generate --if-exists skip
    
  4. Paket dan tanda tangan:

    winapp pack .\dist --cert .\devcert.pfx
    
  5. Instal sertifikat (jalankan sebagai administrator):

    winapp cert install .\devcert.pfx
    
  6. Instal paket:

    Add-AppxPackage .\flutter-app.msix
    

Tip

  • Microsoft Store menandatangani MSIX untuk Anda, tidak perlu menandatangani sebelum pengiriman.
  • Azure Trusted Signing adalah cara yang bagus untuk mengelola sertifikat dengan aman untuk pipelines CI/CD.