{"id":22962402,"url":"https://github.com/zonble/flutter_windows_app_dev_slide","last_synced_at":"2026-02-08T12:03:21.104Z","repository":{"id":66712623,"uuid":"398788858","full_name":"zonble/flutter_windows_app_dev_slide","owner":"zonble","description":"A slide at https://zonble.github.io/flutter_windows_app_dev_slide/","archived":false,"fork":false,"pushed_at":"2021-11-20T20:45:40.000Z","size":121,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-01T19:51:36.402Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://zonble.github.io/flutter_windows_app_dev_slide/","language":"HTML","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zonble.png","metadata":{"files":{"readme":"README.html","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-22T12:01:40.000Z","updated_at":"2024-10-08T17:30:47.000Z","dependencies_parsed_at":"2023-07-22T01:47:09.975Z","dependency_job_id":null,"html_url":"https://github.com/zonble/flutter_windows_app_dev_slide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zonble/flutter_windows_app_dev_slide","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonble%2Fflutter_windows_app_dev_slide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonble%2Fflutter_windows_app_dev_slide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonble%2Fflutter_windows_app_dev_slide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonble%2Fflutter_windows_app_dev_slide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zonble","download_url":"https://codeload.github.com/zonble/flutter_windows_app_dev_slide/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonble%2Fflutter_windows_app_dev_slide/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29229371,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T12:03:03.049Z","status":"ssl_error","status_checked_at":"2026-02-08T12:02:56.077Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-14T19:16:42.415Z","updated_at":"2026-02-08T12:03:21.076Z","avatar_url":"https://github.com/zonble.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!DOCTYPE html\u003e\r\n\u003chtml lang=\"zh-TW\"\u003e\r\n\u003chead\u003e\r\n\u003cmeta charset=\"utf-8\"/\u003e\r\n\u003ctitle\u003e使用 Flutter 開發 Windows 應用\u003c/title\u003e\r\n\u003cmeta name=\"author\" content=\"Weizhong Yang a.k.a zonble\"/\u003e\r\n\u003cstyle type=\"text/css\"\u003e\r\n.underline { text-decoration: underline; }\r\n\u003c/style\u003e\r\n\u003clink rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/reveal.css\"/\u003e\r\n\r\n\u003clink rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/theme/white.css\" id=\"theme\"/\u003e\r\n\r\n\u003c!-- If the query includes 'print-pdf', include the PDF print sheet --\u003e\r\n\u003cscript\u003e\r\n    if( window.location.search.match( /print-pdf/gi ) ) {\r\n        var link = document.createElement( 'link' );\r\n        link.rel = 'stylesheet';\r\n        link.type = 'text/css';\r\n        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/print/pdf.css';\r\n        document.getElementsByTagName( 'head' )[0].appendChild( link );\r\n    }\r\n\u003c/script\u003e\r\n\u003cscript type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML\"\u003e\u003c/script\u003e\r\n\u003c/head\u003e\r\n\u003cbody\u003e\r\n\u003cdiv class=\"reveal\"\u003e\r\n\u003cdiv class=\"slides\"\u003e\r\n\u003csection id=\"sec-title-slide\"\u003e\r\n\u003ch1 class=\"title\"\u003e使用 Flutter 開發 Windows 應用\u003c/h1\u003e\u003ch2 class=\"author\"\u003eWeizhong Yang a.k.a zonble\u003c/h2\u003e\u003cp class=\"date\"\u003eCreated: 2021-08-25 Wed 02:19\u003c/p\u003e\r\n\u003c/section\u003e\r\n\r\n\u003csection\u003e\r\n\u003csection id=\"slide-1\"\u003e\r\n\u003ch2 id=\"1\"\u003e\u003cspan class=\"section-number-2\"\u003e1.\u003c/span\u003e 前言\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://flutter.dev/\"\u003eFlutter\u003c/a\u003e 目前除了行動平台，也支援 Windows、macOS、Linux 等桌面平台\u003c/li\u003e\r\n\u003cli\u003e今天就來講講我們實際使用 Flutter 開發 Windows 應用的親身經驗\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://github.com/zonble/flutter_windows_app_dev_slide\"\u003eSlide 網址\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e我們開發的東西，算是 AIoT 相關吧…\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-2\"\u003e\r\n\u003ch2 id=\"2\"\u003e\u003cspan class=\"section-number-2\"\u003e2.\u003c/span\u003e 關於我\u003c/h2\u003e\r\n\u003cdiv style=\"float: right; width: 200px;\"\u003e\r\n\r\n\u003cdiv id=\"orgc12d3b8\" class=\"figure\"\u003e\r\n\u003cp\u003e\u003cimg src=\"./zonble.jpeg\" alt=\"zonble.jpeg\" /\u003e\r\n\u003c/p\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cdiv style=\"float: left; width: 70%;\"\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eTwitter \u003ca href=\"https://twitter.com/zonble\"\u003e@zonble\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://developers.google.com/community/experts/directory/profile/profile-weizhong-yang\"\u003eFlutter GDE\u003c/a\u003e (2019-)\u003c/li\u003e\r\n\u003cli\u003e工作\r\n\u003cul\u003e\r\n\u003cli\u003e工作內容大抵上是 App 工程師\u003c/li\u003e\r\n\u003cli\u003eEngineer Manager @ Cerence Taipei (2020-)\u003c/li\u003e\r\n\u003cli\u003eiOS Developer Lead @ KKBOX (2011-2020)\u003c/li\u003e\r\n\u003cli\u003e一家兩人接案公司 (2007-2011)\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\u003c/div\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-2-1\"\u003e\r\n\u003ch3 id=\"2-1\"\u003e\u003cspan class=\"section-number-3\"\u003e2.1.\u003c/span\u003e 我做過的東西\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://www.cerence.com/cerence-products/cerence-ark\"\u003eCerence Ark Assitant\u003c/a\u003e SDK for iOS and Flutter\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://www.kkbox.com/service/kkboxkids/\"\u003eKKBOX Kids\u003c/a\u003e for iOS and Android\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://apps.apple.com/tw/app/kkbox-%E9%9F%B3%E6%A8%82%E7%84%A1%E9%99%90%E8%81%BD-lets-music/id1105332179?mt=12\"\u003eKKBOX for macOS\u003c/a\u003e, \u003ca href=\"https://apps.apple.com/tw/app/kkbox-%E9%9F%B3%E6%A8%82-podcast/id300915900\"\u003eiOS, watchOS, tvOS\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://zonble.gitbooks.io/kkbox-ios-dev/content/\"\u003eKKBOX iOS 開發基本教材\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003eYahoo 輸入法 for Windows and macOS、嘸蝦米輸入法 macOS、教育部台語輸入法\r\nmacOS 版\u003c/li\u003e\r\n\u003cli\u003e還有很多失敗作品\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-3\"\u003e\r\n\u003ch2 id=\"3\"\u003e\u003cspan class=\"section-number-2\"\u003e3.\u003c/span\u003e 背景\u003c/h2\u003e\r\n\u003cdiv style=\"float: right; width: 200px;\"\u003e\r\n\r\n\u003cdiv id=\"orgead4e89\" class=\"figure\"\u003e\r\n\u003cp\u003e\u003cimg src=\"./cerence.png\" alt=\"cerence.png\" /\u003e\r\n\u003c/p\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cdiv style=\"float: left; width: 70%;\"\u003e\r\n\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://www.cerence.com/\"\u003eCerence (NASDAQ: CRNC)\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e以語音為核心技術的 AIoT 公司\u003c/li\u003e\r\n\u003cli\u003e今年積極開發語音智慧電梯\u003c/li\u003e\r\n\u003cli\u003e智慧電梯是一個語音盒子，後面控制電梯的系統\u003c/li\u003e\r\n\u003cli\u003e這個盒子需要一套 PC 配置工具\u003c/li\u003e\r\n\u003cli\u003e我們使用 Flutter 開發\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\u003c/div\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-3-1\"\u003e\r\n\u003ch3 id=\"3-1\"\u003e\u003cspan class=\"section-number-3\"\u003e3.1.\u003c/span\u003e Demo\u003c/h3\u003e\r\n\u003ciframe width=\"800\" height=\"500\" src=\"https://www.youtube.com/embed/JpnwInksmgY\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen\u003e\u003c/iframe\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-3-2\"\u003e\r\n\u003ch3 id=\"3-2\"\u003e\u003cspan class=\"section-number-3\"\u003e3.2.\u003c/span\u003e 為什麼用 Flutter？\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e客戶一開始想要 Windows + iOS 這套組合\u003c/li\u003e\r\n\u003cli\u003e我們也不想要寫兩套 codebase\u003c/li\u003e\r\n\u003cli\u003eDesigner 做了一整套 Material Design 的畫面\u003c/li\u003e\r\n\u003cli\u003e我手上會的開發框架，只有 Flutter 可以符合這個需求\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-4\"\u003e\r\n\u003ch2 id=\"4\"\u003e\u003cspan class=\"section-number-2\"\u003e4.\u003c/span\u003e 這個 App 做了哪些事？\u003c/h2\u003e\r\n\u003cdiv class=\"outline-text-2\" id=\"text-4\"\u003e\r\n\u003c/div\u003e\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-4-1\"\u003e\r\n\u003ch3 id=\"4-1\"\u003e\u003cspan class=\"section-number-3\"\u003e4.1.\u003c/span\u003e 簡單來說\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e登入\u003c/li\u003e\r\n\u003cli\u003e察看設備訊息\u003c/li\u003e\r\n\u003cli\u003e編輯樓層語音指令\u003c/li\u003e\r\n\u003cli\u003e執行各項測試\u003c/li\u003e\r\n\u003cli\u003eOTA 軟體更新\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-4-2\"\u003e\r\n\u003ch3 id=\"4-2\"\u003e\u003cspan class=\"section-number-3\"\u003e4.2.\u003c/span\u003e 複雜來說\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e在本地端產生 private/public key，並且加密保存\u003c/li\u003e\r\n\u003cli\u003e在本地端產生 \u003ca href=\"https://en.wikipedia.org/wiki/X.509\"\u003eX.509\u003c/a\u003e CSR 與匯入 Certificate\u003c/li\u003e\r\n\u003cli\u003e使用 \u003ca href=\"https://developer.microsoft.com/en-us/microsoft-edge/webview2/\"\u003eWebView2\u003c/a\u003e 執行 Oauth authentication code flow 登入\u003c/li\u003e\r\n\u003cli\u003e使用 mTLS 連線登入\u003c/li\u003e\r\n\u003cli\u003e偵測 USB 連接狀態，判斷是否連接設備\u003c/li\u003e\r\n\u003cli\u003e透過 ADB forwarding 與設備做 socket 通訊\r\n\u003cul\u003e\r\n\u003cli\u003e包含檢測與 OTA\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e其他各種 HTTP 連線與 server 傳遞資料\u003c/li\u003e\r\n\u003cli\u003e用 Flutter GUI 編輯語音指令\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-4-3\"\u003e\r\n\u003ch3 id=\"4-3\"\u003e\u003cspan class=\"section-number-3\"\u003e4.3.\u003c/span\u003e App 的組成\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eFlutter Bloc 狀態與 GUI\u003c/li\u003e\r\n\u003cli\u003ePackages\r\n\u003cul\u003e\r\n\u003cli\u003eCerence API client\u003c/li\u003e\r\n\u003cli\u003eCernece OTA API client\u003c/li\u003e\r\n\u003cli\u003e設備 socket 通訊 client\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003ePlugins\r\n\u003cul\u003e\r\n\u003cli\u003eCerence 語音通訊協定 Flutter plugin\u003c/li\u003e\r\n\u003cli\u003eFlutter WebView2 plugin\u003c/li\u003e\r\n\u003cli\u003eFlutter USB notification plugin\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-5\"\u003e\r\n\u003ch2 id=\"5\"\u003e\u003cspan class=\"section-number-2\"\u003e5.\u003c/span\u003e 從 Mobile 到 Desktop\u003c/h2\u003e\r\n\u003cp\u003e\r\n今天會講到\r\n\u003c/p\u003e\r\n\r\n\u003cul\u003e\r\n\u003cli\u003eDesktop App 的通則\r\n\u003cul\u003e\r\n\u003cli\u003e善用 CLI 工具\u003c/li\u003e\r\n\u003cli\u003e特別注意 Undo\u003c/li\u003e\r\n\u003cli\u003eDesktop 專屬的 GUI\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eWindows 限定\r\n\u003cul\u003e\r\n\u003cli\u003eInstaller\u003c/li\u003e\r\n\u003cli\u003eWindows 上的 plug-in 開發\u003c/li\u003e\r\n\u003cli\u003e.Net\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-6\"\u003e\r\n\u003ch2 id=\"6\"\u003e\u003cspan class=\"section-number-2\"\u003e6.\u003c/span\u003e 在準備進入 Flutter 開發 Windows App 之前\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e只會 Dart/Flutter 可能不太夠\u003c/li\u003e\r\n\u003cli\u003e應該還是很有可能碰到\r\n\u003cul\u003e\r\n\u003cli\u003eCMake 語法\u003c/li\u003e\r\n\u003cli\u003eC/C++ 語言\u003c/li\u003e\r\n\u003cli\u003eC/C++ 編譯設定\u003c/li\u003e\r\n\u003cli\u003eNuget\u003c/li\u003e\r\n\u003cli\u003eWiX 或其他 Installer 開發工具\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-7\"\u003e\r\n\u003ch2 id=\"7\"\u003e\u003cspan class=\"section-number-2\"\u003e7.\u003c/span\u003e 善用 CLI 工具\u003c/h2\u003e\r\n\u003cdiv class=\"outline-text-2\" id=\"text-7\"\u003e\r\n\u003c/div\u003e\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-1\"\u003e\r\n\u003ch3 id=\"7-1\"\u003e\u003cspan class=\"section-number-3\"\u003e7.1.\u003c/span\u003e Mobile 上的習慣\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e我們習慣在 Mobile 上，用一個 app 做完所有事\r\n\u003cul\u003e\r\n\u003cli\u003eiOS 最初連把部分 code 搬到其他 framework 都不行，只能 static link\u003c/li\u003e\r\n\u003cli\u003e不能與其他 process 通訊，widget 只能夠透過共用檔案或 keychain 交換資訊\u003c/li\u003e\r\n\u003cli\u003eApp 之間可以透過 openURL: 通訊，但蘋果也大加限制\u003c/li\u003e\r\n\u003cli\u003eAndroid 則可以讓 App 與 Service 通訊\u003c/li\u003e\r\n\u003cli\u003e控制其他 app 則需要透過呼叫 activity 等方式\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-2\"\u003e\r\n\u003ch3 id=\"7-2\"\u003e\u003cspan class=\"section-number-3\"\u003e7.2.\u003c/span\u003e Desktop 上的 System Call\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eDesktop 平台可以盡情呼叫 system call\r\n\u003cul\u003e\r\n\u003cli\u003e執行其他的 CLI 程式\u003c/li\u003e\r\n\u003cli\u003e讀取 standard output 與 standard error 顯示\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-3\"\u003e\r\n\u003ch3 id=\"7-3\"\u003e\u003cspan class=\"section-number-3\"\u003e7.3.\u003c/span\u003e Process Class\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eDart 程式可以用 \u003ca href=\"https://api.dart.dev/stable/2.13.4/dart-io/Process-class.html\"\u003eProcess\u003c/a\u003e 執行外部命令，呼叫方式\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003cdiv class=\"org-src-container\"\u003e\r\n\r\n\u003cpre class=\"src src-dart\"\u003e  \u003cspan style=\"color: #51afef;\"\u003evar\u003c/span\u003e \u003cspan style=\"color: #dcaeea;\"\u003eresult\u003c/span\u003e = \u003cspan style=\"color: #51afef;\"\u003eawait\u003c/span\u003e \u003cspan style=\"color: #ECBE7B;\"\u003eProcess\u003c/span\u003e.run(\u003cspan style=\"color: #98be65;\"\u003e'ls'\u003c/span\u003e, [\u003cspan style=\"color: #98be65;\"\u003e'-l'\u003c/span\u003e]);\r\n\u003c/pre\u003e\r\n\u003c/div\u003e\r\n\r\n\u003cul\u003e\r\n\u003cli\u003e從 result 中可以讀取 stdout 與 std err\u003c/li\u003e\r\n\u003cli\u003e在 Windows 上，往往需要設置 PATH 變數或知道命令絕對路徑才能執行\u003c/li\u003e\r\n\u003cli\u003e可以用 \u003ca href=\"https://api.flutter.dev/flutter/dart-io/Platform/resolvedExecutable.html\"\u003ePlatform.resolvedExecutable\u003c/a\u003e 取得目前 app 執行檔位置，找到跟著一起發行\r\n的執行檔\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-4\"\u003e\r\n\u003ch3 id=\"7-4\"\u003e\u003cspan class=\"section-number-3\"\u003e7.4.\u003c/span\u003e Dart 也可以開發 CLI 工具\u003c/h3\u003e\r\n\u003cdiv class=\"outline-text-3\" id=\"text-7-4\"\u003e\r\n\u003c/div\u003e\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-4-1\"\u003e\r\n\u003ch4 id=\"7-4-1\"\u003e\u003cspan class=\"section-number-4\"\u003e7.4.1.\u003c/span\u003e Compile Exe\u003c/h4\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e在撰寫一些跟 GUI 無關的 code 的時候，我們也可以把這部分變成 CLI 工具\u003c/li\u003e\r\n\u003cli\u003e支援編譯出 Windows、macOS、Linux 的執行檔\u003c/li\u003e\r\n\u003cli\u003e執行檔中包含一套 Dart runtime\u003c/li\u003e\r\n\u003cli\u003e每個執行檔大約 5mb\u003c/li\u003e\r\n\u003cli\u003e搭配 \u003ca href=\"https://pub.dev/packages/args\"\u003eargs\u003c/a\u003e 套件處理參數\u003c/li\u003e\r\n\u003cli\u003e在開發桌面應用時，可以活用這個特性\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003cdiv class=\"org-src-container\"\u003e\r\n\r\n\u003cpre class=\"src src-shell\"\u003e\u0026amp; dart compile exe my_cli_cmd.dart\r\n\u003c/pre\u003e\r\n\u003c/div\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-4-2\"\u003e\r\n\u003ch4 id=\"7-4-2\"\u003e\u003cspan class=\"section-number-4\"\u003e7.4.2.\u003c/span\u003e 我們的使用場景\u003c/h4\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eWindows socket client 與 OEM 在設備端上的 server 同時開發\u003c/li\u003e\r\n\u003cli\u003eclient 的開發速度比 server 還快\u003c/li\u003e\r\n\u003cli\u003e我們先把 socket client 寫成 CLI 工具，提供給 OEM\r\n\u003cul\u003e\r\n\u003cli\u003e讓 OEM 確認我們送出的 bytes 是否正確\u003c/li\u003e\r\n\u003cli\u003e讓 OEM 驗證自己的 server 行為\u003c/li\u003e\r\n\u003cli\u003eQA 做整合測試時，有一套比 GUI 工具更透明的工具，確認是 client 還是 server\r\n的問題\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e如何保證 client 的正確？透過單元測試\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-7-4-3\"\u003e\r\n\u003ch4 id=\"7-4-3\"\u003e\u003cspan class=\"section-number-4\"\u003e7.4.3.\u003c/span\u003e 使用 Dart 撰寫 CLI 工具\u003c/h4\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e一定程度上比一些其他語言好寫\u003c/li\u003e\r\n\u003cli\u003eDart 在看到 Future 等非同步操作，會等到 Future 結束，才會結束整個程式\u003c/li\u003e\r\n\u003cli\u003e也就是：用 Dart 寫非同步 CLI，我們不用另外寫 message loop\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-8\"\u003e\r\n\u003ch2 id=\"8\"\u003e\u003cspan class=\"section-number-2\"\u003e8.\u003c/span\u003e Undo\u003c/h2\u003e\r\n\u003cp\u003e\r\nMobile 不常做，但在 Desktop 很重要\r\n\u003c/p\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-8-1\"\u003e\r\n\u003ch3 id=\"8-1\"\u003e\u003cspan class=\"section-number-3\"\u003e8.1.\u003c/span\u003e 為什麼要做 Undo？\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eMobile App 工程師通常比較不熟悉怎麼做 Undo\u003c/li\u003e\r\n\u003cli\u003eMobile App 比較沒有複雜的編輯功能，用戶也比較少用手機做複雜的編輯\u003c/li\u003e\r\n\u003cli\u003eDesktop 就要注意如何避免用戶誤刪\u003c/li\u003e\r\n\u003cli\u003e辛苦編輯的資料不小心消失，是糟糕的體驗\u003c/li\u003e\r\n\u003cli\u003e避免誤刪的手段：\r\n\u003cul\u003e\r\n\u003cli\u003e刪除前加上確認提示\u003c/li\u003e\r\n\u003cli\u003e製作垃圾桶或是 Undo 命令\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-8-2\"\u003e\r\n\u003ch3 id=\"8-2\"\u003e\u003cspan class=\"section-number-3\"\u003e8.2.\u003c/span\u003e 怎麼實做 Undo？\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e一般的作法是每次編輯之間要做 diff\u003c/li\u003e\r\n\u003cli\u003e編輯的時候存入與前一次之間的差異\u003c/li\u003e\r\n\u003cli\u003eUndo 時就是取消這次的差異，並且把這個差異變成 redo\u003c/li\u003e\r\n\u003cli\u003e偷懶的作法：把前一個狀態整個存起來，直接回到前一個狀態\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-8-3\"\u003e\r\n\u003ch3 id=\"8-3\"\u003e\u003cspan class=\"section-number-3\"\u003e8.3.\u003c/span\u003e Flutter 上實做 Undo\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eFlutter App 開發特別注重狀態管理（State Management）\u003c/li\u003e\r\n\u003cli\u003e常用 Pattern：\u003ca href=\"https://pub.dev/packages/flutter_redux\"\u003eRedux\u003c/a\u003e、\u003ca href=\"https://pub.dev/packages/provider\"\u003eProvider\u003c/a\u003e、\u003ca href=\"https://bloclibrary.dev/\"\u003eBLoC\u003c/a\u003e，等等\u003c/li\u003e\r\n\u003cli\u003e把狀態放在 Widget Tree 上層，下層監聽上層狀態改變，重建 widget tree\u003c/li\u003e\r\n\u003cli\u003e在狀態改變的時候，儲存前一個狀態\u003c/li\u003e\r\n\u003cli\u003eUndo 就是把前一個狀態拿回來變呈現在的狀態\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-9\"\u003e\r\n\u003ch2 id=\"9\"\u003e\u003cspan class=\"section-number-2\"\u003e9.\u003c/span\u003e Desktop 上的 GUI\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e鍵盤\u003c/li\u003e\r\n\u003cli\u003eScrollbar\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-9-1\"\u003e\r\n\u003ch3 id=\"9-1\"\u003e\u003cspan class=\"section-number-3\"\u003e9.1.\u003c/span\u003e Flutter 在 Windows 上的 Bug\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e左邊的 Shift 被當成 Capslock 了\u003c/li\u003e\r\n\u003cli\u003e打一個 email 會變成 zonble@GMAIL\u0026gt;COM\u003c/li\u003e\r\n\u003cli\u003eFlutter 2.0 ~ 2.2 都沒有修正\u003c/li\u003e\r\n\u003cli\u003e可以在最上層另外包一個 Widget 改變按鍵行為\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://github.com/flutter/flutter/issues/75675\"\u003e相關討論與修正方式\u003c/a\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-9-2\"\u003e\r\n\u003ch3 id=\"9-2\"\u003e\u003cspan class=\"section-number-3\"\u003e9.2.\u003c/span\u003e Scroll Bar\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e手機上都是用滑動手勢捲動頁面，Scroll Bar 只是視覺提示\u003c/li\u003e\r\n\u003cli\u003e在桌面平台上，就常常會透過滑鼠拖動 Scroll Bar 捲動頁面\u003c/li\u003e\r\n\u003cli\u003e如果你的 Scroll View （包括 ListView 等）不是全頁的，Flutter 無法幫你把\r\nScroll View 與 Scroll Bar 關連起來\u003c/li\u003e\r\n\u003cli\u003e必須從外部對 Scroll View 與 Scroll Bar 指定相同的 ScrollController\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-9-3\"\u003e\r\n\u003ch3 id=\"9-3\"\u003e\u003cspan class=\"section-number-3\"\u003e9.3.\u003c/span\u003e Scroll Bar\u003c/h3\u003e\r\n\u003cp\u003e\r\nFlutter 官方文件：\r\n\u003c/p\u003e\r\n\r\n\u003cp\u003e\r\n[\u003ca href=\"https://api.flutter.dev/flutter/material/Scrollbar/controller.html\"\u003ehttps://api.flutter.dev/flutter/material/Scrollbar/controller.html\u003c/a\u003e]\r\n\u003c/p\u003e\r\n\r\n\u003cp\u003e\r\nIf nothing is passed to controller, the default behavior is to automatically\r\nenable scrollbar dragging on the nearest ScrollController using\r\nPrimaryScrollController.of.\r\n\u003c/p\u003e\r\n\r\n\u003cp\u003e\r\n意思是，只要不是 PrimaryScrollController，如果不指定 controller 就會有問題\r\n\u003c/p\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-10\"\u003e\r\n\u003ch2 id=\"10\"\u003e\u003cspan class=\"section-number-2\"\u003e10.\u003c/span\u003e Installer\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e你的 Flutter App 還是需要 Installer 才能讓用戶安裝\u003c/li\u003e\r\n\u003cli\u003eFlutter SDK 中，並沒有跟 Installer 相關的部分\u003c/li\u003e\r\n\u003cli\u003e你還是要有 Installer 的 knowhow\u003c/li\u003e\r\n\u003cli\u003e公司說，我們沒錢買 Install Shield\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-1\"\u003e\r\n\u003ch3 id=\"10-1\"\u003e\u003cspan class=\"section-number-3\"\u003e10.1.\u003c/span\u003e 其他平台上的 Installer\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eAndroid、iOS、Android\r\n\u003cul\u003e\r\n\u003cli\u003e使用 Store 發行\u003c/li\u003e\r\n\u003cli\u003eStore 可以決定哪些設備可以安裝（OS 版本、32/64bit）\u003c/li\u003e\r\n\u003cli\u003e所有相依套件打包在一起\u003c/li\u003e\r\n\u003cli\u003e系統幫你安排安裝到指定的 sandbox 中\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eLinux\r\n\u003cul\u003e\r\n\u003cli\u003e沒什麼機會寫，暫不討論\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-2\"\u003e\r\n\u003ch3 id=\"10-2\"\u003e\u003cspan class=\"section-number-3\"\u003e10.2.\u003c/span\u003e Windows 上的 Installer\u003c/h3\u003e\r\n\u003cdiv class=\"outline-text-3\" id=\"text-10-2\"\u003e\r\n\u003c/div\u003e\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-2-1\"\u003e\r\n\u003ch4 id=\"10-2-1\"\u003e\u003cspan class=\"section-number-4\"\u003e10.2.1.\u003c/span\u003e Windows Installer 需要做的事 (1)\u003c/h4\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e用戶可以將 App 裝到任何位置\u003c/li\u003e\r\n\u003cli\u003e需要將安裝位置寫入 registry，日後才知道要去哪裡反安裝與更新\u003c/li\u003e\r\n\u003cli\u003e反安裝需要刪除 registry\u003c/li\u003e\r\n\u003cli\u003e指定 Program Menu 與桌面上要建立哪些捷徑\u003c/li\u003e\r\n\u003cli\u003e需要自己設定安裝條件（作業系統版本等）\u003c/li\u003e\r\n\u003cli\u003e可以允許用戶安裝部分功能\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-2-2\"\u003e\r\n\u003ch4 id=\"10-2-2\"\u003e\u003cspan class=\"section-number-4\"\u003e10.2.2.\u003c/span\u003e Windows Installer 需要做的事 (2)\u003c/h4\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e是裝給整台機器使用，還是只給單一用戶使用\u003c/li\u003e\r\n\u003cli\u003e相依套件可能要寫入系統目錄（C++ runtime、Web View 2）\u003c/li\u003e\r\n\u003cli\u003e安裝 Driver\u003c/li\u003e\r\n\u003cli\u003e裝完是否要重開機\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-3\"\u003e\r\n\u003ch3 id=\"10-3\"\u003e\u003cspan class=\"section-number-3\"\u003e10.3.\u003c/span\u003e WiX Toolset\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e使用 XML 表達安裝邏輯\u003c/li\u003e\r\n\u003cli\u003e可以產生兩類型的安裝程式\r\n\u003cul\u003e\r\n\u003cli\u003eMSI、MSU、MSP\u0026#x2026;\r\n\u003cul\u003e\r\n\u003cli\u003e安裝主程式\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eBootstrapper\r\n\u003cul\u003e\r\n\u003cli\u003e安裝 Dependency\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-4\"\u003e\r\n\u003ch3 id=\"10-4\"\u003e\u003cspan class=\"section-number-3\"\u003e10.4.\u003c/span\u003e MSI 要定義哪些東西？ (1)\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e要安裝的檔案\r\n\u003cul\u003e\r\n\u003cli\u003eRunner\u003c/li\u003e\r\n\u003cli\u003eDLL for plug-ins\u003c/li\u003e\r\n\u003cli\u003eAssets for the main bundle \u0026amp; plug-ins\u003c/li\u003e\r\n\u003cli\u003e桌面與開始工具列捷徑\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e檔案要裝到哪？\r\n\u003cul\u003e\r\n\u003cli\u003ePer machine 安裝，放在 C:\\Program Files\\ 下\u003c/li\u003e\r\n\u003cli\u003ePer user 安裝，放在 %USER%\\AppData\\Roaming\\ 下\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-5\"\u003e\r\n\u003ch3 id=\"10-5\"\u003e\u003cspan class=\"section-number-3\"\u003e10.5.\u003c/span\u003e MSI 要定義哪些東西？ (2)\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eRegistry 路徑\r\n\u003cul\u003e\r\n\u003cli\u003ePer machine 安裝，放在 HKLM\u003c/li\u003e\r\n\u003cli\u003ePer user 安裝，放在 HKCU\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e安裝限制\r\n\u003cul\u003e\r\n\u003cli\u003eFlutter app 只能夠在 64 位元 Windows 執行\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e升級相關： Update 用的 GUID\u003c/li\u003e\r\n\u003cli\u003e有些特殊檔案類型需要用 WiX Extension 處理\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://wixtoolset.org/documentation/manual/v3/xsd/difxapp/driver.html\"\u003edifx\u003c/a\u003e: 安裝 Driver\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-10-6\"\u003e\r\n\u003ch3 id=\"10-6\"\u003e\u003cspan class=\"section-number-3\"\u003e10.6.\u003c/span\u003e Flutter App 會需要的 Dependencies\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eMicrosoft Visual C++ Redistributable for Visual Studio 2019\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://support.microsoft.com/zh-tw/topic/%E6%9C%80%E6%96%B0%E6%94%AF%E6%8F%B4%E7%9A%84-visual-c-%E4%B8%8B%E8%BC%89-2647da03-1eea-4433-9aff-95f26a218cc0\"\u003e下載\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e安裝 Visual Studio 的時候，硬碟裡頭也會放一份\r\n\u003cul\u003e\r\n\u003cli\u003eC:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\YourVersionHere\\VC\\Redist\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eUniversal CRT - \u003ca href=\"https://support.microsoft.com/zh-tw/topic/windows-%E4%B8%AD%E9%80%9A%E7%94%A8-c-%E5%9F%B7%E8%A1%8C%E9%9A%8E%E6%AE%B5%E7%9A%84%E6%9B%B4%E6%96%B0-c0514201-7fe6-95a3-b0a5-287930f3560c\"\u003e下載\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e我們往往搞不清楚用戶的電腦上缺哪些 runtime，在不同電腦上多測試\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-11\"\u003e\r\n\u003ch2 id=\"11\"\u003e\u003cspan class=\"section-number-2\"\u003e11.\u003c/span\u003e 開發給 Windows 使用的 Flutter Plug-in\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e方式\r\n\u003col\u003e\r\n\u003cli\u003e透過 Dart 與 C 之間的 FFI\u003c/li\u003e\r\n\u003cli\u003e透過 Method Channel/Event Channel\u003c/li\u003e\r\n\r\n\u003c/ol\u003e\u003c/li\u003e\r\n\u003cli\u003e目前 Windows 上還不支援 Native View\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-11-1\"\u003e\r\n\u003ch3 id=\"11-1\"\u003e\u003cspan class=\"section-number-3\"\u003e11.1.\u003c/span\u003e Dart FFI\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://dart.dev/guides/libraries/c-interop\"\u003e相關文件: C interop using dart:ffi\u003c/a\u003e\u003c/li\u003e\r\n\u003cli\u003e從 Dart 中直接透過語法 briding 呼叫 win32 C API\u003c/li\u003e\r\n\u003cli\u003e全部使用 Dart 語法開發\u003c/li\u003e\r\n\u003cli\u003e但其實不好寫：從 Dart 中對應 C 的 signature 比想像中麻煩\u003c/li\u003e\r\n\u003cli\u003e從 C callback 回 Dart 也不好搞\u003c/li\u003e\r\n\u003cli\u003e可以參考 \u003ca href=\"https://pub.dev/packages/win32\"\u003ewin32 package\u003c/a\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-11-2\"\u003e\r\n\u003ch3 id=\"11-2\"\u003e\u003cspan class=\"section-number-3\"\u003e11.2.\u003c/span\u003e Flutter Plug-in on Windows\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e從 Dart 呼叫 Windows API 時透過 method channel\u003c/li\u003e\r\n\u003cli\u003e從 Windows 呼叫 Dart 可以用 event channel 或 method channel\u003c/li\u003e\r\n\u003cli\u003e在 Windows 上使用 C++ 開發\r\n\u003cul\u003e\r\n\u003cli\u003eFlutter plug-in 是 C++ class\u003c/li\u003e\r\n\u003cli\u003e開發時 override 掉 template method\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003e使用 CMake 工具建置編譯設定\u003c/li\u003e\r\n\u003cli\u003eDart 使用 UTF-8 編碼，Windows 使用 UTF-16 編碼，需要注意編碼轉換\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-11-3\"\u003e\r\n\u003ch3 id=\"11-3\"\u003e\u003cspan class=\"section-number-3\"\u003e11.3.\u003c/span\u003e 從 Windows 接收通知\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e其他平台的作法\r\n\u003cul\u003e\r\n\u003cli\u003eiOS/macOS: 對特定 API 接收 delegate 或 notification 的訊息，用 channel 送\r\n回 Flutter\u003c/li\u003e\r\n\u003cli\u003eAndroid： 對特定 API 接收 listener\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eWindows\r\n\u003cul\u003e\r\n\u003cli\u003e所有通知都在 winproc 中\u003c/li\u003e\r\n\u003cli\u003e可以想像成 iOS/macOS 的 runloop\u003c/li\u003e\r\n\u003cli\u003eplug-in 可以交換 winproc 的指標，處理想要的通知後，交給之前的 winproc 處理\u003c/li\u003e\r\n\u003cli\u003e\u003ca href=\"https://zonble.medium.com/%E9%96%8B%E7%99%BC-flutter-windows-plug-in-f84dbb90aff9\"\u003e我的筆記\u003c/a\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-12\"\u003e\r\n\u003ch2 id=\"12\"\u003e\u003cspan class=\"section-number-2\"\u003e12.\u003c/span\u003e .Net\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eFlutter App 是用 C/C++ 寫成，需要手動管理記憶體\u003c/li\u003e\r\n\u003cli\u003e雖然可以對 Flutter plug-in 加上編譯成 .Bet dll，但執行時會因為違法存取記憶體\r\n而 crash\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003csection id=\"slide-12-1\"\u003e\r\n\u003ch3 id=\"12-1\"\u003e\u003cspan class=\"section-number-3\"\u003e12.1.\u003c/span\u003e 可以呼叫 .Net 的方式\u003c/h3\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e\u003ca href=\"https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/clr-hosting-interfaces\"\u003eCLR Hosting\u003c/a\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e使用限制太大\u003c/li\u003e\r\n\u003cli\u003e可以指定要呼叫的 .DLL\u003c/li\u003e\r\n\u003cli\u003e只能夠執行 C# 的 class method\u003c/li\u003e\r\n\u003cli\u003e只能夠回傳一個整數\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eCOM 或著其他 IPC\r\n\u003cul\u003e\r\n\u003cli\u003e好像沒有必要把架構搞成這樣\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-13\"\u003e\r\n\u003ch2 id=\"13\"\u003e\u003cspan class=\"section-number-2\"\u003e13.\u003c/span\u003e 回到我們的專案\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003e其實技術本身沒有想像中花時間\u003c/li\u003e\r\n\u003cli\u003e有更多時間花在內部溝通上\u003c/li\u003e\r\n\u003cli\u003e團隊分散台北、上海、成都、福州，需要很努力的協作\u003c/li\u003e\r\n\u003cli\u003e但都讓我們累積了寶貴的經驗\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-14\"\u003e\r\n\u003ch2 id=\"14\"\u003e\u003cspan class=\"section-number-2\"\u003e14.\u003c/span\u003e Recap\u003c/h2\u003e\r\n\u003cul\u003e\r\n\u003cli\u003eDesktop App 的通則\r\n\u003cul\u003e\r\n\u003cli\u003e善用 CLI 工具\u003c/li\u003e\r\n\u003cli\u003e特別注意 Undo\u003c/li\u003e\r\n\u003cli\u003eDesktop 專屬的 GUI\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\u003cli\u003eWindows 限定\r\n\u003cul\u003e\r\n\u003cli\u003eInstaller\u003c/li\u003e\r\n\u003cli\u003eWindows 上的 plug-in 開發\u003c/li\u003e\r\n\u003cli\u003e.Net\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\u003c/li\u003e\r\n\r\n\u003c/ul\u003e\r\n\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003csection\u003e\r\n\u003csection id=\"slide-org88eb533\"\u003e\r\n\u003ch2 id=\"org88eb533\"\u003eThank You!\u003c/h2\u003e\r\n\u003c/section\u003e\r\n\u003c/section\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/lib/js/head.min.js\"\u003e\u003c/script\u003e\r\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/js/reveal.js\"\u003e\u003c/script\u003e\r\n\u003cscript\u003e\r\n// Full list of configuration options available here:\r\n// https://github.com/hakimel/reveal.js#configuration\r\nReveal.initialize({\r\n\r\ncontrols: true,\r\nprogress: true,\r\nhistory: false,\r\ncenter: true,\r\nslideNumber: false,\r\nrollingLinks: false,\r\nkeyboard: true,\r\nmouseWheel: false,\r\nfragmentInURL: false,\r\nhashOneBasedIndex: false,\r\npdfSeparateFragments: true,\r\noverview: true,\r\n\r\ntheme: Reveal.getQueryHash().theme, // available themes are in /css/theme\r\ntransition: Reveal.getQueryHash().transition || 'convex', // see README of reveal.js for options\r\ntransitionSpeed: 'default',\r\n\r\n// Optional libraries used to extend reveal.js\r\ndependencies: [\r\n { src: 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },\r\n { src: 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },\r\n { src: 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } },\r\n { src: 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/search/search.js', async: true, condition: function() { return !!document.body.classList; } },\r\n { src: 'https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } }]\r\n\r\n});\r\n\u003c/script\u003e\r\n\u003c/body\u003e\r\n\u003c/html\u003e\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzonble%2Fflutter_windows_app_dev_slide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzonble%2Fflutter_windows_app_dev_slide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzonble%2Fflutter_windows_app_dev_slide/lists"}