「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfrastructure.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。

-[[戻る>Flutter]]
--...[[Flutterの5thステップ]]
--Flutterのファースト・ステップ2
--[[Flutterのセカンド・ステップ2]]
--[[Flutterのサード・ステップ2]]

*目次 [#d0705168]
#contents

*概要 [#x79dd04e]
2026年、リブートはFlutterデスクトップ(Windows)で実施
-この選択肢は、単純にAndroid/iOSの準備が難しいケースが多いため。
-(コレは、一先ず、WebAPIを経由した簡単なCRUD処理の実装と、OAuth系認証・認可処理を実装できれば良いため)
-Windowsでは、Android/iOSほど「カスタムURLスキームからの移行圧力」は強くなく、myapp:// 方式が今でも主流

*詳細 [#b3a57dbc]

**システム要件 [#e740e7e8]
-Windows 10/11(64bit)
-ストレージ:10GB以上の空き
-RAM:8GB以上推奨

**インストール [#gd3d7a56]

***Flutter SDK [#x75e9ac9]
-flutter.dev から Flutter SDK をダウンロード
-%USERPROFILE%\develop\flutter\ などのパスに解凍
-環境変数 PATH に %USERPROFILE%\develop\flutter\bin を追加
-CMD / PowerShellで確認
 flutter --version
 dart --version

***Visual Studio [#l16d83af]
-Visual Studio のインストールで「C++によるデスクトップ開発」 ワークロードを選択
-「MSVC v143(または最新)」「Windows SDK」コンポーネントが含まれていることを確認

***VS Code [#b5c05e19]
-VS Codeのインストール
-拡張機能 Flutter(Dart含む)をインストール

**設定 [#r9e5e8d7]
CMD / PowerShellで

***有効化 [#qa8b8e13]
Windowsデスクトップ対応を有効化
 flutter config --enable-windows-desktop

***確認 [#e693fbe3]
依存関係の確認
 flutter doctor

**開発 [#afe00c72]
CMD / PowerShellで

***作成 [#t4b49c03]
 flutter create my_app

***編集 [#ta991b13]
 cd my_app

***実行 [#u2c7682a]
 flutter run -d windows

***デバッグ [#kea768df]
VS Code上から、

-以下から、デバイスを Windows (desktop) に切り替え。
--右下のステータスバー(No Device)
--若しくはコマンドパレット(Ctrl+Shift+P)→ Flutter: Select Device

-.dart ファイルにブレークポイントを設定

-「デバッグと実行」(Ctrl+Shift+D / Cmd+Shift+D) から「▶」再生ボタン または F5 →「Dart&Flutter」で、デバッグ接続

*導入 [#s915c677]
-AI活用(https://claude.ai/chat)で、殆どWebを参照せず、作業を進めることが出来た(Sonnet4.6)。
-先ずは、[[3分割レイアウトのCounter>Reactのファースト・ステップ2]]を作成してみる(flutter create でも Counter が生成される)。

**ツール類の導入手順 [#h9f89f3f]
[[ココまでの導入手順>#b3a57dbc]]もAIに生成させている。

**レイアウト変更 [#c0f7556b]
以下はプロンプト

-1
 以下で生成したコードを修正して3分割のヘッダ、左メニュー、メインエリアの画面を作成したい。
 なお、左メニューのリンクを押下するとメインエリアが画面遷移するものとする。
 
 flutter create my_app
 
 修正内容を教えてください。

-2~
上記の出力が、全て lib/main.dart にまとめられていたため
 ページのファイルは分割したい。

以下は、出力から修正ポイントを抜粋したもの。

***フォルダ構成 [#ae76d40f]
 lib/
 ├── main.dart
 ├── layout/
 │     └── main_layout.dart
 └── pages/
       ├── page_home.dart
       ├── page_about.dart
       └── page_settings.dart

***main・layout [#p1811bf1]
-main.dart
 import 'package:flutter/material.dart';
 import 'layout/main_layout.dart';
 
 void main() {
   runApp(const MyApp());
 }
 
 class MyApp extends StatelessWidget {
   const MyApp({super.key});
 
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'My App',
       theme: ThemeData(
         colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
         useMaterial3: true,
       ),
       home: const MainLayout(),
     );
   }
 }

-layout/main_layout.dart
 import 'package:flutter/material.dart';
 import '../pages/page_home.dart';
 import '../pages/page_about.dart';
 import '../pages/page_settings.dart';
 import '../pages/page_contact.dart';
 
 class MainLayout extends StatefulWidget {
   const MainLayout({super.key});
 
   @override
   State<MainLayout> createState() => _MainLayoutState();
 }
 
 class _MainLayoutState extends State<MainLayout> {
   int _selectedIndex = 0;
 
   final List<Map<String, dynamic>> _menuItems = [
     {'icon': Icons.home, 'label': 'ホーム'},
     {'icon': Icons.info, 'label': '概要'},
     {'icon': Icons.settings, 'label': '設定'},
   ];
 
   final List<Widget> _pages = const [
     PageHome(),
     PageAbout(),
     PageSettings(),
   ];
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       appBar: AppBar(
         backgroundColor: Theme.of(context).colorScheme.primary,
         title: const Text(
           'My Application',
           style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
         ),
         actions: [
           IconButton(
             icon: const Icon(Icons.notifications, color: Colors.white),
             onPressed: () {},
           ),
           IconButton(
             icon: const Icon(Icons.account_circle, color: Colors.white),
             onPressed: () {},
           ),
         ],
       ),
       body: Row(
         children: [
           NavigationRail(
             selectedIndex: _selectedIndex,
             onDestinationSelected: (int index) {
               setState(() {
                 _selectedIndex = index;
               });
             },
             labelType: NavigationRailLabelType.all,
             destinations: _menuItems.map((item) {
               return NavigationRailDestination(
                 icon: Icon(item['icon'] as IconData),
                 label: Text(item['label'] as String),
               );
             }).toList(),
           ),
           const VerticalDivider(thickness: 1, width: 1),
           Expanded(
             child: _pages[_selectedIndex],
           ),
         ],
       ),
     );
   }
 }

***pages [#xf1f3342]
ホントは、page_contact.dartもあったが割愛。

-page_home.dart
-page_about.dart
-page_settings.dart


**Counter画面 [#ze2cbf5e]
もともとあったIncrement機能が消失したので、page_counter.dartとして追加。

 既存のIncrement機能が消失したのでCounterとして追加して下さい。
 ```
 import 'package:flutter/material.dart';
 
 void main() {
   runApp(const MyApp());
 }
 
 class MyApp extends StatelessWidget {
   const MyApp({super.key});
 
   // This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Flutter Demo',
       theme: ThemeData(
         // This is the theme of your application.
         //
         // TRY THIS: Try running your application with "flutter run". You'll see
         // the application has a purple toolbar. Then, without quitting the app,
         // try changing the seedColor in the colorScheme below to Colors.green
         // and then invoke "hot reload" (save your changes or press the "hot
         // reload" button in a Flutter-supported IDE, or press "r" if you used
         // the command line to start the app).
         //
         // Notice that the counter didn't reset back to zero; the application
         // state is not lost during the reload. To reset the state, use hot
         // restart instead.
         //
         // This works for code too, not just values: Most code changes can be
         // tested with just a hot reload.
         colorScheme: .fromSeed(seedColor: Colors.deepPurple),
       ),
       home: const MyHomePage(title: 'Flutter Demo Home Page'),
     );
   }
 }
 
 class MyHomePage extends StatefulWidget {
   const MyHomePage({super.key, required this.title});
 
   // This widget is the home page of your application. It is stateful, meaning
   // that it has a State object (defined below) that contains fields that affect
   // how it looks.
 
   // This class is the configuration for the state. It holds the values (in this
   // case the title) provided by the parent (in this case the App widget) and
   // used by the build method of the State. Fields in a Widget subclass are
   // always marked "final".
 
   final String title;
 
   @override
   State<MyHomePage> createState() => _MyHomePageState();
 }
 
 class _MyHomePageState extends State<MyHomePage> {
   int _counter = 0;
 
   void _incrementCounter() {
     setState(() {
       // This call to setState tells the Flutter framework that something has
       // changed in this State, which causes it to rerun the build method below
       // so that the display can reflect the updated values. If we changed
       // _counter without calling setState(), then the build method would not be
       // called again, and so nothing would appear to happen.
       _counter++;
     });
   }
 
   @override
   Widget build(BuildContext context) {
     // This method is rerun every time setState is called, for instance as done
     // by the _incrementCounter method above.
     //
     // The Flutter framework has been optimized to make rerunning build methods
     // fast, so that you can just rebuild anything that needs updating rather
     // than having to individually change instances of widgets.
     return Scaffold(
       appBar: AppBar(
         // TRY THIS: Try changing the color here to a specific color (to
         // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
         // change color while the other colors stay the same.
         backgroundColor: Theme.of(context).colorScheme.inversePrimary,
         // Here we take the value from the MyHomePage object that was created by
         // the App.build method, and use it to set our appbar title.
         title: Text(widget.title),
       ),
       body: Center(
         // Center is a layout widget. It takes a single child and positions it
         // in the middle of the parent.
         child: Column(
           // Column is also a layout widget. It takes a list of children and
           // arranges them vertically. By default, it sizes itself to fit its
           // children horizontally, and tries to be as tall as its parent.
           //
           // Column has various properties to control how it sizes itself and
           // how it positions its children. Here we use mainAxisAlignment to
           // center the children vertically; the main axis here is the vertical
           // axis because Columns are vertical (the cross axis would be
           // horizontal).
           //
           // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
           // action in the IDE, or press "p" in the console), to see the
           // wireframe for each widget.
           mainAxisAlignment: .center,
           children: [
             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),
       ),
     );
   }
 }
 ```

***Layout [#x570320f]
main_layout.dart~

_menuItems と _pages にCounterを追記

***pages [#z65213bb]
page_counter.dart
 import 'package:flutter/material.dart';
 
 class PageCounter extends StatefulWidget {
   const PageCounter({super.key});
 
   @override
   State<PageCounter> createState() => _PageCounterState();
 }
 
 class _PageCounterState extends State<PageCounter> {
   int _counter = 0;
 
   void _incrementCounter() {
     setState(() {
       _counter++;
     });
   }
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       body: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             const Icon(Icons.touch_app, size: 64, color: Colors.teal),
             const SizedBox(height: 16),
             const Text('ボタンを押した回数:'),
             Text(
               '$_counter',
               style: Theme.of(context).textTheme.headlineMedium,
             ),
           ],
         ),
       ),
       floatingActionButton: FloatingActionButton(
         onPressed: _incrementCounter,
         tooltip: 'Increment',
         child: const Icon(Icons.add),
       ),
     );
   }
 }

**ヘッダ、左メニューの切出 [#w247e2ee]

-プロンプト
 ヘッダ、左メニューを切り出したい。
 ちなみに同様のReactプロジェクトでは、pagesではなくcomponentに切り出されていた。
 
 import 'package:flutter/material.dart';
 import '../pages/page_home.dart';
 import '../pages/page_about.dart';
 import '../pages/page_settings.dart';
 import '../pages/page_counter.dart';
 
 class MainLayout extends StatefulWidget {
   const MainLayout({super.key});
 
   @override
   State<MainLayout> createState() => _MainLayoutState();
 }
 
 class _MainLayoutState extends State<MainLayout> {
   int _selectedIndex = 0;
 
   final List<Map<String, dynamic>> _menuItems = [
     {'icon': Icons.home, 'label': 'ホーム'},
     {'icon': Icons.info, 'label': '概要'},
     {'icon': Icons.settings, 'label': '設定'},
     {'icon': Icons.touch_app,    'label': 'カウンター'},
   ];
 
   final List<Widget> _pages = const [
     PageHome(),
     PageAbout(),
     PageSettings(),
     PageCounter(),
   ];
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       appBar: AppBar(
         backgroundColor: Theme.of(context).colorScheme.primary,
         title: const Text(
           'My Application',
           style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
         ),
         actions: [
           IconButton(
             icon: const Icon(Icons.notifications, color: Colors.white),
             onPressed: () {},
           ),
           IconButton(
             icon: const Icon(Icons.account_circle, color: Colors.white),
             onPressed: () {},
           ),
         ],
       ),
       body: Row(
         children: [
           NavigationRail(
             selectedIndex: _selectedIndex,
             onDestinationSelected: (int index) {
               setState(() {
                 _selectedIndex = index;
               });
             },
             labelType: NavigationRailLabelType.all,
             destinations: _menuItems.map((item) {
               return NavigationRailDestination(
                 icon: Icon(item['icon'] as IconData),
                 label: Text(item['label'] as String),
               );
             }).toList(),
           ),
           const VerticalDivider(thickness: 1, width: 1),
           Expanded(
             child: _pages[_selectedIndex],
           ),
         ],
       ),
     );
   }
 }

-出力(構成)
 lib/
 ├── components/
 │    ├── app_header.dart
 │    └── side_navigation.dart
 ├── pages/
 │    ├── page_home.dart
 │    ├── page_about.dart
 │    ├── page_settings.dart
 │    └── page_counter.dart
 └── layouts/
       └── main_layout.dart

***補足的に [#ec05e263]
-プロンプト
 「components」「pages」は慣例的ですか?

-出力~
pages → screens、components → widgets、page_home → home_screen
 lib/
 ├── screens/       # ← pages/ の代わり
 │    ├── home.dart
 │    ├── about.dart
 │    ├── settings.dart
 │    └── counter.dart
 ├── widgets/       # ← components/ の代わり
 │   ├── app_header.dart
 │   └── side_navigation.dart

※ 今回は、「_screens」は付与しない方向性で修正

***ファイル修正 [#dd6e961f]
-main_layout.dart
 import 'package:flutter/material.dart';
 import '../widgets/app_header.dart';
 import '../widgets/side_navigation.dart';
 import '../screens/home.dart';
 import '../screens/about.dart';
 import '../screens/settings.dart';
 import '../screens/counter.dart';
 
 class MainLayout extends StatefulWidget {
   const MainLayout({super.key});
 
   @override
   State<MainLayout> createState() => _MainLayoutState();
 }
 
 class _MainLayoutState extends State<MainLayout> {
   int _selectedIndex = 0;
 
   final List<Map<String, dynamic>> _menuItems = [
     {'icon': Icons.home, 'label': 'ホーム'},
     {'icon': Icons.info, 'label': '概要'},
     {'icon': Icons.settings, 'label': '設定'},
     {'icon': Icons.touch_app,    'label': 'カウンター'},
   ];
 
   final List<Widget> _pages = const [
     ScreenHome(),
     ScreenAbout(),
     ScreenSettings(),
     ScreenCounter(),
   ];
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       appBar: const AppHeader(),
       body: Row(
         children: [
           SideNavigation(
             selectedIndex: _selectedIndex,
             onDestinationSelected: (int index) {
               setState(() {
                 _selectedIndex = index;
               });
             },
             menuItems: _menuItems,
           ),
           const VerticalDivider(thickness: 1, width: 1),
           Expanded(
             child: _pages[_selectedIndex],
           ),
         ],
       ),
     );
   }
 }

-app_header.dart
 import 'package:flutter/material.dart';
 
 class AppHeader extends StatelessWidget implements PreferredSizeWidget {
   const AppHeader({super.key});
 
   @override
   Widget build(BuildContext context) {
     return AppBar(
       backgroundColor: Theme.of(context).colorScheme.primary,
       title: const Text(
         'My Application',
         style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
       ),
       actions: [
         IconButton(
           icon: const Icon(Icons.notifications, color: Colors.white),
           onPressed: () {},
         ),
         IconButton(
           icon: const Icon(Icons.account_circle, color: Colors.white),
           onPressed: () {},
         ),
       ],
     );
   }
 
   @override
   Size get preferredSize => const Size.fromHeight(kToolbarHeight);
 }

-side_navigation.dart
 import 'package:flutter/material.dart';
 
 class SideNavigation extends StatelessWidget {
   final int selectedIndex;
   final ValueChanged<int> onDestinationSelected;
   final List<Map<String, dynamic>> menuItems;
 
   const SideNavigation({
     super.key,
     required this.selectedIndex,
     required this.onDestinationSelected,
     required this.menuItems,
   });
 
   @override
   Widget build(BuildContext context) {
     return NavigationRail(
       selectedIndex: selectedIndex,
       onDestinationSelected: onDestinationSelected,
       labelType: NavigationRailLabelType.all,
       destinations: menuItems.map((item) {
         return NavigationRailDestination(
           icon: Icon(item['icon'] as IconData),
           label: Text(item['label'] as String),
         );
       }).toList(),
     );
   }
 }

*参考 [#bd14ad50]
-Flutterを手動でインストールする~
https://docs.flutter.dev/install/manual

-Flutterをデスクトップアプリ開発におすすめしない話 - izanami~
https://izanami.dev/post/47b04883-8ea4-4282-85d9-d684bec4d0a1
-デスクトップアプリ(Windows)の開発にFlutterを使うべき? : r/FlutterDev~
https://www.reddit.com/r/FlutterDev/comments/1c9gwpx/should_i_use_flutter_for_desktop_app_windows/?tl=ja
-デスクトップ開発ならFlutterとElectronの友達は何が違うんだ #Flutter - Qiita~
https://qiita.com/UBinKitte/items/ce0596c18ab9fd1d03c7

-Flutterを手動でインストールする~
https://docs.flutter.dev/install/manual

-FrontendTemplates/UI/XPlat at develop · OpenTouryoProject/FrontendTemplates~
https://github.com/OpenTouryoProject/FrontendTemplates/tree/develop/UI/XPlat/

--https://github.com/OpenTouryoProject/FrontendTemplates/tree/develop/UI/XPlat/flutter_template
--https://github.com/OpenTouryoProject/FrontendTemplates/tree/develop/UI/XPlat/old/flutter_template

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS