<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: 徐胖胖</title>
    <description>The latest articles on Forem by 徐胖胖 (@johnshu).</description>
    <link>https://forem.com/johnshu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2339027%2F31980263-255f-45b5-a0c7-83e49885e26b.jpg</url>
      <title>Forem: 徐胖胖</title>
      <link>https://forem.com/johnshu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnshu"/>
    <language>en</language>
    <item>
      <title>GitHub Action Bot 冷知識</title>
      <dc:creator>徐胖胖</dc:creator>
      <pubDate>Wed, 18 Mar 2026 15:46:05 +0000</pubDate>
      <link>https://forem.com/johnshu/github-action-bot-leng-zhi-shi-j1h</link>
      <guid>https://forem.com/johnshu/github-action-bot-leng-zhi-shi-j1h</guid>
      <description>&lt;h2&gt;
  
  
  讓 GitHub Actions Bot 的 Commit 變成 Verified
&lt;/h2&gt;

&lt;p&gt;今天才知道，原來 GitHub Actions 自動 commit 的時候，是可以讓那個 commit 顯示 &lt;code&gt;verified&lt;/code&gt; 標籤的。&lt;/p&gt;

&lt;p&gt;關鍵就是把 git 的 user email 設成 GitHub Actions bot 專屬的 email：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config user.name &lt;span class="s2"&gt;"github-actions[bot]"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"41898282+github-actions[bot]@users.noreply.github.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;設完之後正常 commit 就好，GitHub 會自動認得這是它家的 bot，直接幫你掛上 verified。&lt;/p&gt;

&lt;h3&gt;
  
  
  建立 PR 也適用
&lt;/h3&gt;

&lt;p&gt;如果你跟我一樣，除了 commit 還需要 action 自動開 PR，一樣可以用這組 email。像我是用 &lt;code&gt;peter-evans/create-pull-request&lt;/code&gt; 這個 action：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Pull Request&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peter-evans/create-pull-request@v8&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;committer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-actions[bot] &amp;lt;41898282+github-actions[bot]@users.noreply.github.com&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-actions[bot] &amp;lt;41898282+github-actions[bot]@users.noreply.github.com&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  那個 &lt;code&gt;41898282&lt;/code&gt; 是什麼？
&lt;/h3&gt;

&lt;p&gt;這串數字是 GitHub Actions bot 的 user ID。每個 bot 帳號都有自己的 ID，email 格式就是 &lt;code&gt;{id}+{username}@users.noreply.github.com&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;這個 ID 看起來沒有公開的官方文件記錄，不過可以直接用 GitHub API 查：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# GitHub Actions bot&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/users/github-actions%5Bbot%5D | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'"id"|"login"'&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; "login": "github-actions[bot]",&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; "id": 41898282,&lt;/span&gt;

&lt;span class="c"&gt;# Dependabot 也有自己的&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/users/dependabot%5Bbot%5D | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'"id"|"login"'&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; "login": "dependabot[bot]",&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; "id": 49699333,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;所以如果你用的是 Dependabot 或其他 bot，用同樣的方式查一下 ID 就能組出對應的 email。&lt;/p&gt;

&lt;p&gt;當啷～ 這...真他喵的是個天才！！🤪&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Flutter Android compile toolchain 版本相容性</title>
      <dc:creator>徐胖胖</dc:creator>
      <pubDate>Thu, 12 Mar 2026 15:06:25 +0000</pubDate>
      <link>https://forem.com/johnshu/flutter-android-compile-toolchain-ban-ben-xiang-rong-xing-387j</link>
      <guid>https://forem.com/johnshu/flutter-android-compile-toolchain-ban-ben-xiang-rong-xing-387j</guid>
      <description>&lt;p&gt;每次升級 Flutter 專案的 Android compile toolchain，都要在 Gradle、AGP、Kotlin、JDK 這四個東西之間抓交集，版本一個沒對上就是滿滿的紅字。這篇把我查過的版本搭配關係整理起來，每次要升級的時候可以參考。&lt;/p&gt;




&lt;h2&gt;
  
  
  目前建議的版本組合（2026-03）
&lt;/h2&gt;

&lt;p&gt;先講結論，目前 Flutter 專案用這組是最穩的：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套件&lt;/th&gt;
&lt;th&gt;建議版本&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AGP&lt;/td&gt;
&lt;td&gt;8.10.0&lt;/td&gt;
&lt;td&gt;Flutter 完整支援的最新 AGP 8.x 穩定版&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;8.13&lt;/td&gt;
&lt;td&gt;搭配 AGP 8.10 的最新穩定 Gradle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;2.2.x&lt;/td&gt;
&lt;td&gt;搭配 AGP 8.10 的穩定選擇；2.3.0 可用但 R8 完整支援需 AGP 8.13+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;AGP 8.x 的標準要求，穩定且 CI 廣泛支援&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;如果需要 Kotlin 2.3 的 R8 完整支援，可以考慮把 AGP 升到 8.13+（需 Gradle 8.13）。&lt;/p&gt;




&lt;h2&gt;
  
  
  這些版本到底怎麼互卡的
&lt;/h2&gt;

&lt;p&gt;Android compile toolchain 裡，這幾個套件的版本是環環相扣的。單看兩兩對照表很容易忽略整體搭配，所以先搞清楚底層到上層的決定依賴關係會比較好做決定。&lt;/p&gt;

&lt;h3&gt;
  
  
  依賴方向
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flutter SDK
  * 決定最低 Kotlin 版本
  * 決定最低 Gradle 版本（透過內建的 Gradle wrapper）

AGP (Android Gradle Plugin)
  * 決定最低 Gradle 版本
  * 決定最低 Kotlin 版本
  * 決定最低 JDK 版本

Kotlin (KGP)
  * 決定可用的 Gradle 版本範圍（有上限與下限）

Gradle
  * 決定可用的 JDK 版本範圍（有上限與下限）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;重點是：&lt;strong&gt;Gradle 版本是整條鏈的交集中心&lt;/strong&gt;。AGP 卡 Gradle 的下限，Kotlin 卡 Gradle 的上限，最終選的 Gradle 版本必須同時滿足兩邊。JDK 則由 Gradle 版本決定支援範圍。&lt;/p&gt;

&lt;h3&gt;
  
  
  升級的時候版本怎麼選
&lt;/h3&gt;

&lt;p&gt;不管從哪個版本開始動，最終都要回到同一個問題：找出所有套件的交集。以下提供兩條常見的查找路徑。&lt;/p&gt;

&lt;h4&gt;
  
  
  路徑 A：想升級 AGP
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;情境：AGP 要從 8.7 升到 8.10&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1 : 查 AGP 對 Gradle 的最低要求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「AGP &amp;lt;-&amp;gt; Gradle 版本對照表」：AGP 8.10 需要 Gradle &amp;gt;= 8.11.1。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 : 查 AGP 對 Kotlin 的最低要求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「AGP &amp;lt;-&amp;gt; Kotlin 最低版本需求表」：AGP 8.10 需要 Kotlin &amp;gt;= 2.0.0。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 : 用 Step 1 的 Gradle 版本，反查 Kotlin 上限&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「Kotlin &amp;lt;-&amp;gt; Gradle 版本對照表」，看哪些 Kotlin 版本吃得下 Gradle 8.11+：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kotlin 2.1.0 支援 Gradle 最高 8.11 -- 剛好壓線&lt;/li&gt;
&lt;li&gt;Kotlin 2.1.20 支援 Gradle 最高 8.12 -- 可以&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以 Kotlin 至少要 2.1.0 以上才能搭 Gradle 8.11。結合 Step 2 的下限（&amp;gt;= 2.0.0），&lt;strong&gt;實際下限是 2.1.0&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 : 查 Flutter SDK 對 Kotlin 的要求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flutter 3.29+ 要求 Kotlin &amp;gt;= 2.0.0，不會進一步限縮。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 : 用最終的 Gradle 版本，查 JDK 支援範圍&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「Gradle JDK 相容性」：Gradle 8.11 支援 JDK 8-23，AGP 8.x 要求 JDK &amp;gt;= 17。取交集：&lt;strong&gt;JDK 17-23&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最終結果&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套件&lt;/th&gt;
&lt;th&gt;可用範圍&lt;/th&gt;
&lt;th&gt;建議選擇&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AGP&lt;/td&gt;
&lt;td&gt;8.10（目標）&lt;/td&gt;
&lt;td&gt;8.10.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;&amp;gt;= 8.11.1&lt;/td&gt;
&lt;td&gt;8.13（取最新穩定版）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;&amp;gt;= 2.1.0&lt;/td&gt;
&lt;td&gt;2.2.x（穩定版）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;17 - 23&lt;/td&gt;
&lt;td&gt;17（穩定且廣泛支援）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  路徑 B：想升級 Kotlin
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;情境：Kotlin 要從 1.9.20 升到 2.1.0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1 : 查 Kotlin 對 Gradle 的版本範圍&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「Kotlin &amp;lt;-&amp;gt; Gradle 版本對照表」：Kotlin 2.1.0 支援 Gradle 7.6.3 - 8.11。&lt;strong&gt;Gradle 不能超過 8.11。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 : 用 Gradle 上限，反查可用的 AGP 最高版本&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「AGP &amp;lt;-&amp;gt; Gradle 版本對照表」，找 Gradle &amp;lt;= 8.11 能撐住的 AGP：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AGP 8.10 需要 Gradle &amp;gt;= 8.11.1 -- 8.11 不夠，不行&lt;/li&gt;
&lt;li&gt;AGP 8.9 需要 Gradle &amp;gt;= 8.11.1 -- 一樣不夠&lt;/li&gt;
&lt;li&gt;AGP 8.8 需要 Gradle &amp;gt;= 8.10.2 -- 可以&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以搭 Kotlin 2.1.0 的話，AGP 最高只能用到 8.8。想用 AGP 8.10 就得改用 Kotlin 2.1.20+（支援 Gradle 最高 8.12）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 : 確認 AGP 對 Kotlin 的最低要求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;翻「AGP &amp;lt;-&amp;gt; Kotlin 最低版本需求表」：AGP 8.8 需要 Kotlin &amp;gt;= 1.9.20 -- Kotlin 2.1.0 沒問題。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 : 確認 JDK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gradle 8.11 支援 JDK 8-23，AGP 8.x 要求 JDK &amp;gt;= 17，取交集：&lt;strong&gt;JDK 17-23&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最終結果&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套件&lt;/th&gt;
&lt;th&gt;可用範圍&lt;/th&gt;
&lt;th&gt;建議選擇&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;2.1.0（目標）&lt;/td&gt;
&lt;td&gt;2.1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;7.6.3 - 8.11&lt;/td&gt;
&lt;td&gt;8.11（取上限以支援最新 AGP）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AGP&lt;/td&gt;
&lt;td&gt;&amp;lt;= 8.8&lt;/td&gt;
&lt;td&gt;8.8（Gradle 8.11 下的最新）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;17 - 23&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  快速檢查清單
&lt;/h3&gt;

&lt;p&gt;拿到一組版本後，跑這四個檢查就知道能不能用：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AGP 版本 =&amp;gt; Gradle 版本 &amp;gt;= AGP 要求的最低版本？&lt;/li&gt;
&lt;li&gt;AGP 版本 =&amp;gt; Kotlin 版本 &amp;gt;= AGP 要求的最低版本？&lt;/li&gt;
&lt;li&gt;Kotlin 版本 =&amp;gt; Gradle 版本在 KGP 支援的範圍內（注意上限）？&lt;/li&gt;
&lt;li&gt;Gradle 版本 =&amp;gt; JDK 版本在 Gradle 支援的範圍內，且 &amp;gt;= AGP 要求的最低 JDK？&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;四項全過就沒問題。&lt;/p&gt;




&lt;h2&gt;
  
  
  各套件版本對照表
&lt;/h2&gt;

&lt;p&gt;以下是各對照表的詳細數據，上面查版本的時候翻這裡。&lt;/p&gt;




&lt;h2&gt;
  
  
  AGP &amp;lt;-&amp;gt; Gradle 版本對照表
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AGP 版本&lt;/th&gt;
&lt;th&gt;最低 Gradle 版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9.1&lt;/td&gt;
&lt;td&gt;9.3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9.0&lt;/td&gt;
&lt;td&gt;9.1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.13&lt;/td&gt;
&lt;td&gt;8.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.12&lt;/td&gt;
&lt;td&gt;8.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.11&lt;/td&gt;
&lt;td&gt;8.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.10&lt;/td&gt;
&lt;td&gt;8.11.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.9&lt;/td&gt;
&lt;td&gt;8.11.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;td&gt;8.10.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;8.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.1&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.4&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;來源: &lt;a href="https://developer.android.com/build/releases/about-agp" rel="noopener noreferrer"&gt;About Android Gradle plugin&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Kotlin (KGP) &amp;lt;-&amp;gt; Gradle 版本對照表
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kotlin 版本&lt;/th&gt;
&lt;th&gt;最低 Gradle 版本&lt;/th&gt;
&lt;th&gt;最高 Gradle 版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2.1.20&lt;/td&gt;
&lt;td&gt;7.6.3&lt;/td&gt;
&lt;td&gt;8.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.1.0&lt;/td&gt;
&lt;td&gt;7.6.3&lt;/td&gt;
&lt;td&gt;8.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0.21&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;8.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0.20&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;8.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0.0&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.9.0&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Kotlin 2.2+ / 2.3+ 對應的 Gradle 相容範圍請參考 &lt;a href="https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin" rel="noopener noreferrer"&gt;Kotlin 官方最新文件&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;來源: &lt;a href="https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin" rel="noopener noreferrer"&gt;Configure a Gradle project - Kotlin&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Kotlin &amp;lt;-&amp;gt; AGP / R8 最低版本需求表
&lt;/h2&gt;

&lt;p&gt;這張表列的是各 Kotlin 版本需要的最低 AGP 版本，主要影響 R8 code shrinking 的正確性。如果專案沒開 R8/ProGuard，編譯可能還是會過，但不保證所有功能正常。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kotlin 版本&lt;/th&gt;
&lt;th&gt;最低 AGP 版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;td&gt;9.1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.3&lt;/td&gt;
&lt;td&gt;8.13.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.2&lt;/td&gt;
&lt;td&gt;8.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.1&lt;/td&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.9&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;td&gt;7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;來源: &lt;a href="https://developer.android.com/build/kotlin-support" rel="noopener noreferrer"&gt;Kotlin support - Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  AGP &amp;lt;-&amp;gt; Kotlin 最低版本需求表
&lt;/h2&gt;

&lt;p&gt;這張是從 AGP 的角度看，各 AGP 版本要求的最低 Kotlin 版本。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AGP 版本&lt;/th&gt;
&lt;th&gt;最低 Kotlin 版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9.0+&lt;/td&gt;
&lt;td&gt;2.0.0（且不再使用 kotlin-android plugin）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.10&lt;/td&gt;
&lt;td&gt;2.0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.9&lt;/td&gt;
&lt;td&gt;2.0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;1.9.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;1.9.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;1.8.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.1&lt;/td&gt;
&lt;td&gt;1.8.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;td&gt;1.8.20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;來源: &lt;a href="https://developer.android.com/build/kotlin-support" rel="noopener noreferrer"&gt;Kotlin support - Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  JDK 版本需求
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AGP 版本&lt;/th&gt;
&lt;th&gt;最低 JDK 版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9.x&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.x&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.x&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gradle 本身的 JDK 相容性：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gradle 版本&lt;/th&gt;
&lt;th&gt;支援 JDK 範圍&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8.10+&lt;/td&gt;
&lt;td&gt;8 - 23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.5 - 8.9&lt;/td&gt;
&lt;td&gt;8 - 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.0 - 8.4&lt;/td&gt;
&lt;td&gt;8 - 21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;來源: &lt;a href="https://developer.android.com/build/jdks" rel="noopener noreferrer"&gt;Java versions in Android builds&lt;/a&gt;、&lt;a href="https://docs.gradle.org/current/userguide/compatibility.html" rel="noopener noreferrer"&gt;Gradle Compatibility Matrix&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Flutter 的部分
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Flutter 3.29+ 要求 Kotlin 2.0.0 以上&lt;/li&gt;
&lt;li&gt;Flutter 3.24+ 要求 Kotlin 1.8.0 以上&lt;/li&gt;
&lt;li&gt;建議跟著 Flutter stable channel 的預設 Kotlin 版本走&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;來源: &lt;a href="https://docs.flutter.dev/release/breaking-changes/kotlin-version" rel="noopener noreferrer"&gt;Flutter - Required Kotlin version&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  補充：AGP 9.0 -- Flutter 還沒準備好
&lt;/h2&gt;

&lt;p&gt;AGP 9.0 在 2026 年 1 月正式出了，改動不小。但截至目前（2026-03），&lt;strong&gt;Flutter 還沒完整支援 AGP 9.0，不建議在 Flutter 專案裡升上去&lt;/strong&gt;。&lt;/p&gt;

&lt;h3&gt;
  
  
  AGP 9.0 要求的版本
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;套件&lt;/th&gt;
&lt;th&gt;最低版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;9.1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;2.0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  AGP 9.0 改了什麼
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;內建 Kotlin 支援（Built-in Kotlin）&lt;/strong&gt;：AGP 9.0 預設啟用內建 Kotlin 編譯，不再需要也不能用 &lt;code&gt;kotlin-android&lt;/code&gt; plugin。原本的 &lt;code&gt;kotlinOptions&lt;/code&gt; 要改成 &lt;code&gt;android { kotlin { compilerOptions { ... } } }&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;新版 DSL&lt;/strong&gt;：AGP 9.0 只用新版 DSL 介面，舊版 DSL 類型讀不到了。可以暫時在 &lt;code&gt;gradle.properties&lt;/code&gt; 加 &lt;code&gt;android.newDsl=false&lt;/code&gt; 撐一下，但這個選項到 AGP 10.0 就會被拔掉。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gradle 大版本跳躍&lt;/strong&gt;：AGP 9.0 要求 Gradle 9.1+，直接跳了一個大版本，其他 Gradle plugin 的相容性可能會炸。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Flutter 目前的狀況
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;有用到 plugin 的 Flutter 專案跟 AGP 9.0 不相容（&lt;a href="https://github.com/flutter/flutter/issues/181383" rel="noopener noreferrer"&gt;flutter/flutter#181383&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;Flutter 團隊在做 DSL 遷移跟向下相容性驗證（&lt;a href="https://github.com/flutter/flutter/issues/175688" rel="noopener noreferrer"&gt;flutter/flutter#180137&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;官方遷移文件有了，但明確寫著「手動遷移、尚不支援 plugin」：&lt;a href="https://docs.flutter.dev/release/breaking-changes/migrate-to-agp-9" rel="noopener noreferrer"&gt;Migrating Flutter Android app to AGP 9.0.0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  要升級嗎？
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;不要！等 Flutter 官方說支援了再升 AGP 9.0&lt;/li&gt;
&lt;li&gt;如果是沒有用 plugin 的純 Flutter 專案，可以照官方遷移文件試試看，但風險自負&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  參考資料
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/build/releases/about-agp" rel="noopener noreferrer"&gt;About Android Gradle plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/build/releases/gradle-plugin" rel="noopener noreferrer"&gt;Android Gradle plugin release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/build/releases/agp-9-0-0-release-notes" rel="noopener noreferrer"&gt;AGP 9.0 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/build/kotlin-support" rel="noopener noreferrer"&gt;Kotlin support - Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/build/jdks" rel="noopener noreferrer"&gt;Java versions in Android builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kotlinlang.org/docs/gradle-configure-project.html" rel="noopener noreferrer"&gt;Configure a Gradle project - Kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flutter.dev/release/breaking-changes/kotlin-version" rel="noopener noreferrer"&gt;Flutter - Required Kotlin version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flutter.dev/release/breaking-changes/migrate-to-agp-9" rel="noopener noreferrer"&gt;Flutter - Migrating to AGP 9.0.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gradle.org/current/userguide/compatibility.html" rel="noopener noreferrer"&gt;Gradle Compatibility Matrix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本文使用 Claude 輔助整理&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
    </item>
    <item>
      <title>Flutter 多環境配置完整指南</title>
      <dc:creator>徐胖胖</dc:creator>
      <pubDate>Fri, 23 Jan 2026 05:24:25 +0000</pubDate>
      <link>https://forem.com/johnshu/flutter-duo-huan-jing-pei-zhi-wan-zheng-zhi-nan-5h8l</link>
      <guid>https://forem.com/johnshu/flutter-duo-huan-jing-pei-zhi-wan-zheng-zhi-nan-5h8l</guid>
      <description>&lt;h2&gt;
  
  
  概述
&lt;/h2&gt;

&lt;h3&gt;
  
  
  什麼是多環境配置
&lt;/h3&gt;

&lt;p&gt;多環境配置讓你能在同一個 App 專案中建立不同的環境版本（如 Development、Staging、Production），每個環境可以有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不同的 &lt;strong&gt;API 端點&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;不同的 &lt;strong&gt;App 名稱&lt;/strong&gt;（例如：「MyApp [DEV]」vs「MyApp」）&lt;/li&gt;
&lt;li&gt;不同的 &lt;strong&gt;Bundle/Application ID&lt;/strong&gt;（可同時安裝在同一裝置）&lt;/li&gt;
&lt;li&gt;不同的 &lt;strong&gt;App Icon&lt;/strong&gt; 和 &lt;strong&gt;Launch Image&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;不同的 &lt;strong&gt;Firebase 專案配置&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;不同的 &lt;strong&gt;功能開關&lt;/strong&gt;（例如：開發環境顯示除錯工具）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  為什麼需要多環境
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;開發與生產環境隔離&lt;/strong&gt;：避免測試資料污染正式環境&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;同時安裝多個版本&lt;/strong&gt;：開發人員可在同一裝置安裝 dev 和 prod 版本進行比對&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;視覺區別&lt;/strong&gt;：透過不同的 icon 和 app 名稱快速識別環境&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;安全性&lt;/strong&gt;：正式環境關閉除錯功能和敏感日誌&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD 自動化&lt;/strong&gt;：不同 branch 自動建置對應環境&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  本指南涵蓋的配置項目
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Dart Define 配置（環境變數注入）&lt;/li&gt;
&lt;li&gt;Android 多環境配置（Application ID、App Icon、Firebase）&lt;/li&gt;
&lt;li&gt;iOS 多環境配置（Bundle ID、App Icon、Launch Image、Firebase）&lt;/li&gt;
&lt;li&gt;自動化腳本設計&lt;/li&gt;
&lt;li&gt;CI/CD 整合&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  核心機制
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Dart Define 配置 🔷 Flutter 專屬
&lt;/h3&gt;

&lt;p&gt;Flutter 提供 &lt;code&gt;--dart-define-from-file&lt;/code&gt; 參數，讓你從 JSON 檔案載入環境變數。&lt;/p&gt;

&lt;h4&gt;
  
  
  使用 &lt;code&gt;--dart-define-from-file&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;建立環境配置檔案：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build_config/development.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ENV"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ENABLE_DEVELOPER_LOGGER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LOG_LEVEL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ASSETS_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://assets.example.com/dev"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;build_config/production.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ENV"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ENABLE_DEVELOPER_LOGGER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LOG_LEVEL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ASSETS_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://assets.example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  建置指令
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development&lt;/span&gt;
flutter run &lt;span class="nt"&gt;--dart-define-from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build_config/development.json

&lt;span class="c"&gt;# Production&lt;/span&gt;
flutter build apk &lt;span class="nt"&gt;--dart-define-from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build_config/production.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  在 Dart 程式碼中讀取環境變數
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ENV'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;defaultValue:&lt;/span&gt; &lt;span class="s"&gt;'development'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;enableDevLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ENABLE_DEVELOPER_LOGGER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;defaultValue:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;apiBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'API_BASE_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;defaultValue:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;isDevelopment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'development'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;isProduction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'production'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Android 多環境配置
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.1 Application ID Suffix ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;在 &lt;code&gt;android/app/build.gradle.kts&lt;/code&gt; 中設定 &lt;code&gt;applicationIdSuffix&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;
        &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".dev"&lt;/span&gt;  &lt;span class="c1"&gt;// Development: com.example.myapp.dev&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;原理說明&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android 使用 Application ID 識別不同的 App&lt;/li&gt;
&lt;li&gt;加上 suffix 後，development 和 production 版本視為不同的 App&lt;/li&gt;
&lt;li&gt;可以同時安裝在同一裝置上&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 Android 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;任何使用 Gradle 建置的 Android 專案&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.2 App 名稱動態設定 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;使用 Gradle 的 &lt;code&gt;resValue&lt;/code&gt; 機制動態設定 app 名稱：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;resValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"MyApp [DEV]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;AndroidManifest.xml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
    &lt;span class="na"&gt;android:label=&lt;/span&gt;&lt;span class="s"&gt;"@string/app_name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 Android 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.3 App Icon 切換 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;使用 Gradle 的 &lt;code&gt;sourceSets&lt;/code&gt; 機制：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;envSourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="s"&gt;"src/dev"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"src/prod"&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/main/res"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$envSourceDir/res"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;資源目錄結構&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/
├── dev/res/
│   └── mipmap-*/
│       ├── ic_launcher.png          # Dev 環境的 icon
│       ├── ic_launcher_foreground.png
│       └── ic_launcher_background.png
└── prod/res/
    └── mipmap-*/
        ├── ic_launcher.png          # Prod 環境的 icon
        ├── ic_launcher_foreground.png
        └── ic_launcher_background.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;優點&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build 時自動選擇對應目錄&lt;/li&gt;
&lt;li&gt;不需要手動複製檔案&lt;/li&gt;
&lt;li&gt;資源隔離清晰&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 Android 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.4 Firebase 配置切換 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;使用 Gradle Task 在 build 前自動複製對應的 &lt;code&gt;google-services.json&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"copyGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doLast&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="s"&gt;"config/dev"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"config/prod"&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sourceFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$sourceDir/google-services.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;targetFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"google-services.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sourceFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Copied google-services.json from $sourceDir"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;GradleException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"google-services.json not found in $sourceDir"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 確保在處理 google-services 之前先複製檔案&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenTaskAdded&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"processDebugGoogleServices"&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"processReleaseGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"copyGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;檔案結構&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/
├── config/
│   ├── dev/google-services.json      # Dev 環境的 Firebase 配置
│   └── prod/google-services.json     # Prod 環境的 Firebase 配置
└── google-services.json              # 動態生成（已加入 .gitignore）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 Android 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.5 讀取 Flutter Dart Define 🔷 Flutter 專屬
&lt;/h4&gt;

&lt;p&gt;Android Gradle 可以讀取 Flutter 傳遞的 &lt;code&gt;dart-defines&lt;/code&gt; 參數：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Base64&lt;/span&gt;

&lt;span class="c1"&gt;// 定義預設配置（Development 環境）&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;dartEnvironmentVariables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;".dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"MyApp [DEV]"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 注入 dart-define 變數&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dart-defines"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dartDefines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dart-defines"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="n"&gt;dartDefines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decodedEntry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pair&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decodedEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"ENV"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"development"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".dev"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MyApp [DEV]"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="s"&gt;"production"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MyApp"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 環境判斷助手函數&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;
        &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;resValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"MyApp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;envSourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="s"&gt;"src/dev"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"src/prod"&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/main/res"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$envSourceDir/res"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;原理說明&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter 在執行 &lt;code&gt;flutter build&lt;/code&gt; 時會將 &lt;code&gt;dart-defines&lt;/code&gt; 傳遞給 Gradle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dart-defines&lt;/code&gt; 是 Base64 編碼的 key=value 對，以逗號分隔&lt;/li&gt;
&lt;li&gt;Gradle 解碼後根據 &lt;code&gt;ENV&lt;/code&gt; 值設定對應的配置&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. iOS 多環境配置
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.1 Bundle ID Suffix ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;iOS 使用 &lt;strong&gt;xcconfig 檔案&lt;/strong&gt; 來設定 Bundle ID suffix。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Flutter/Debug.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "AppConfig.xcconfig"
#include "Generated.xcconfig"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Flutter/Release.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "AppConfig.xcconfig"
#include "Generated.xcconfig"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Flutter/AppConfig.xcconfig&lt;/code&gt;（動態生成）&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_CONFIG_SUFFIX=.dev
APP_CONFIG_NAME=MyApp [DEV]
APP_CONFIG_ICON_NAME=AppIcon-Dev
APP_CONFIG_LAUNCH_IMAGE=LaunchImage-Dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Runner.xcodeproj/project.pbxproj&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRODUCT_BUNDLE_IDENTIFIER = "com.example.myapp$(APP_CONFIG_SUFFIX)";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;原理說明&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;xcconfig 檔案提供 Xcode 建置變數&lt;/li&gt;
&lt;li&gt;在 Bundle Identifier 中引用 &lt;code&gt;$(APP_CONFIG_SUFFIX)&lt;/code&gt; 變數&lt;/li&gt;
&lt;li&gt;Development: &lt;code&gt;com.example.myapp.dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Production: &lt;code&gt;com.example.myapp&lt;/code&gt;（suffix 為空字串）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.2 App 名稱動態設定 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;在 &lt;code&gt;Info.plist&lt;/code&gt; 中引用 xcconfig 變數：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Runner/Info.plist&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleDisplayName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$(APP_CONFIG_NAME)&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;AppConfig.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_CONFIG_NAME=MyApp [DEV]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.3 App Icon 切換 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;在 Xcode 中建立多個 AppIcon Set，透過 xcconfig 變數控制使用哪一個：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;資源目錄結構&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/Runner/Assets.xcassets/
├── AppIcon-Dev.appiconset/          # Dev 環境的 icon
│   ├── Contents.json
│   └── Icon-App-*.png
└── AppIcon-Prod.appiconset/         # Prod 環境的 icon
    ├── Contents.json
    └── Icon-App-*.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;AppConfig.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_CONFIG_ICON_NAME=AppIcon-Dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Xcode 設定&lt;/strong&gt;：&lt;br&gt;
在 Xcode 的 Build Settings 中設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASSETCATALOG_COMPILER_APPICON_NAME = $(APP_CONFIG_ICON_NAME)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.4 Launch Image 切換 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;Launch Image 的配置較複雜，因為 &lt;code&gt;LaunchScreen.storyboard&lt;/code&gt; 無法直接引用環境變數。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解決方案&lt;/strong&gt;：使用 Build Phase Script 動態複製對應的 launch image。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;資源目錄結構&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/Runner/Assets.xcassets/
├── LaunchImage-Dev.imageset/        # Dev 環境的 launch image
│   ├── Contents.json
│   └── *.png
├── LaunchImage-Prod.imageset/       # Prod 環境的 launch image
│   ├── Contents.json
│   └── *.png
└── LaunchImage.imageset/            # 動態生成（已加入 .gitignore）
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Build Phase Script&lt;/strong&gt;（&lt;code&gt;scripts/copy_launch_image.sh&lt;/code&gt;）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# 讀取 xcconfig 中的 APP_CONFIG_LAUNCH_IMAGE 變數&lt;/span&gt;
&lt;span class="nv"&gt;LAUNCH_IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_CONFIG_LAUNCH_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LAUNCH_IMAGE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: APP_CONFIG_LAUNCH_IMAGE not set"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# 複製對應的 imageset&lt;/span&gt;
&lt;span class="nv"&gt;SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRCROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Runner/Assets.xcassets/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LAUNCH_IMAGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.imageset"&lt;/span&gt;
&lt;span class="nv"&gt;TARGET_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRCROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Runner/Assets.xcassets/LaunchImage.imageset"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: &lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt; not found"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SOURCE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Copied launch image from &lt;/span&gt;&lt;span class="nv"&gt;$LAUNCH_IMAGE_NAME&lt;/span&gt;&lt;span class="s2"&gt; to LaunchImage"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Xcode Build Phase 設定&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 Xcode 中打開 Runner target 的 Build Phases&lt;/li&gt;
&lt;li&gt;新增 "Run Script" phase（必須在 "Run Script (Flutter build)" 之前）&lt;/li&gt;
&lt;li&gt;名稱設為 "Copy Launch Image"&lt;/li&gt;
&lt;li&gt;Script 內容：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRCROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/../scripts/copy_launch_image.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;LaunchScreen.storyboard&lt;/code&gt;&lt;/strong&gt; 引用通用的 &lt;code&gt;LaunchImage&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;imageView&lt;/span&gt; &lt;span class="na"&gt;image=&lt;/span&gt;&lt;span class="s"&gt;"LaunchImage"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.5 Firebase 配置切換 ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;使用 Script 自動複製對應的 &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;檔案結構&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/
├── config/
│   ├── Dev/GoogleService-Info.plist      # Dev 環境的 Firebase 配置
│   └── Prod/GoogleService-Info.plist     # Prod 環境的 Firebase 配置
└── Runner/
    └── GoogleService-Info.plist          # 動態生成（已加入 .gitignore）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;在生成配置的 script 中處理&lt;/strong&gt;（見 3.6）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3.6 動態生成 xcconfig ⭐️ 通用技巧
&lt;/h4&gt;

&lt;p&gt;建立一個 Shell Script 來動態生成 &lt;code&gt;AppConfig.xcconfig&lt;/code&gt;，並同時複製 Firebase 配置。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scripts/generate_app_config.sh&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nv"&gt;ENV_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;development&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# 取得專案根目錄&lt;/span&gt;
&lt;span class="nv"&gt;SCRIPT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 從 project.pbxproj 提取 base bundle identifier&lt;/span&gt;
&lt;span class="nv"&gt;PBXPROJ_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/ios/Runner.xcodeproj/project.pbxproj"&lt;/span&gt;
&lt;span class="nv"&gt;BASE_BUNDLE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s1"&gt;'PRODUCT_BUNDLE_IDENTIFIER'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PBXPROJ_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'$'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENV_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".dev"&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyApp [DEV]"&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_ICON_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"AppIcon-Dev"&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_LAUNCH_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LaunchImage-Dev"&lt;/span&gt;
        &lt;span class="nv"&gt;firebase_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Dev/GoogleService-Info.plist"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MyApp"&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_ICON_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"AppIcon-Prod"&lt;/span&gt;
        &lt;span class="nv"&gt;APP_CONFIG_LAUNCH_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LaunchImage-Prod"&lt;/span&gt;
        &lt;span class="nv"&gt;firebase_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Prod/GoogleService-Info.plist"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error: Unknown environment &lt;/span&gt;&lt;span class="nv"&gt;$ENV_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
        &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="c"&gt;# 計算 Bundle ID&lt;/span&gt;
&lt;span class="nv"&gt;BUNDLE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASE_BUNDLE_ID&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;APP_CONFIG_SUFFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 生成 AppConfig.xcconfig&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/ios/Flutter/AppConfig.xcconfig"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
APP_CONFIG_SUFFIX=&lt;/span&gt;&lt;span class="nv"&gt;$APP_CONFIG_SUFFIX&lt;/span&gt;&lt;span class="sh"&gt;
APP_CONFIG_NAME=&lt;/span&gt;&lt;span class="nv"&gt;$APP_CONFIG_NAME&lt;/span&gt;&lt;span class="sh"&gt;
APP_CONFIG_ICON_NAME=&lt;/span&gt;&lt;span class="nv"&gt;$APP_CONFIG_ICON_NAME&lt;/span&gt;&lt;span class="sh"&gt;
APP_CONFIG_LAUNCH_IMAGE=&lt;/span&gt;&lt;span class="nv"&gt;$APP_CONFIG_LAUNCH_IMAGE&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# 複製對應的 Firebase 配置&lt;/span&gt;
&lt;span class="nv"&gt;src_firebase_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/ios/config/&lt;/span&gt;&lt;span class="nv"&gt;$firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src_firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error: Firebase config file not found: &lt;/span&gt;&lt;span class="nv"&gt;$src_firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src_firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/ios/Runner/GoogleService-Info.plist"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Generated AppConfig.xcconfig for environment: &lt;/span&gt;&lt;span class="nv"&gt;$ENV_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BUNDLE_ID=&lt;/span&gt;&lt;span class="nv"&gt;$BUNDLE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 匯出 BUNDLE_ID 到 CI 環境（如果在 CI 中執行）&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BUNDLE_ID=&lt;/span&gt;&lt;span class="nv"&gt;$BUNDLE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_ENV&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;使用方式&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development&lt;/span&gt;
./scripts/generate_app_config.sh development

&lt;span class="c"&gt;# Production&lt;/span&gt;
./scripts/generate_app_config.sh production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;為什麼不使用 Xcode Pre-build Action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;很多人會想在 Xcode 的 Build Phases 中加入 Pre-build Action 來執行 &lt;code&gt;generate_app_config.sh&lt;/code&gt;，但這樣做有幾個問題：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CI/CD 環境不一致&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 CI/CD 中通常使用 &lt;code&gt;flutter build&lt;/code&gt; 指令，而非直接用 Xcode build&lt;/li&gt;
&lt;li&gt;Pre-build Action 只在透過 Xcode build 時執行&lt;/li&gt;
&lt;li&gt;CI/CD 需要在 &lt;code&gt;flutter build&lt;/code&gt; 之前手動執行 script&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;環境參數傳遞困難&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-build Action 無法輕易接收外部環境變數&lt;/li&gt;
&lt;li&gt;需要額外的機制來告訴 script 要 build 哪個環境&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地開發體驗不一致&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;開發者可能使用 VS Code、Android Studio 或 Xcode&lt;/li&gt;
&lt;li&gt;只有 Xcode 會執行 Pre-build Action&lt;/li&gt;
&lt;li&gt;其他工具需要手動執行 script&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;推薦做法&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;本地開發&lt;/strong&gt;：開發者在切換環境時手動執行 &lt;code&gt;./scripts/generate_app_config.sh &amp;lt;environment&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt;：在 workflow 中明確執行 script，確保環境正確&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這樣的設計讓配置流程清晰、可測試、可重用，且不依賴特定的 IDE 或建置工具。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;為什麼不使用 FlutterFire CLI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FlutterFire CLI 提供 &lt;code&gt;flutterfire configure&lt;/code&gt; 指令來自動產生 Firebase 配置，但在多環境配置中有以下限制：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;不支援動態環境切換&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FlutterFire CLI 需要每次手動執行並選擇 Firebase 專案&lt;/li&gt;
&lt;li&gt;無法根據 build 指令自動切換配置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置重複且難以維護&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每個環境需要分別執行 &lt;code&gt;flutterfire configure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;產生的配置檔案分散，不易統一管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CI/CD 整合複雜&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;需要在 CI 中安裝 FlutterFire CLI&lt;/li&gt;
&lt;li&gt;需要處理 Firebase 認證&lt;/li&gt;
&lt;li&gt;執行時間較長&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bundle ID 管理困難&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FlutterFire CLI 會修改 Xcode 專案設定&lt;/li&gt;
&lt;li&gt;可能與自訂的 Bundle ID suffix 機制衝突&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;手動管理的優勢&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ 配置檔案版本控制在 &lt;code&gt;ios/config/&lt;/code&gt; 目錄&lt;/li&gt;
&lt;li&gt;✅ 透過簡單的 &lt;code&gt;cp&lt;/code&gt; 指令切換配置&lt;/li&gt;
&lt;li&gt;✅ 不需要額外的工具或認證&lt;/li&gt;
&lt;li&gt;✅ CI/CD 執行快速且穩定&lt;/li&gt;
&lt;li&gt;✅ 完全掌控配置切換邏輯&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;適用場景&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;原生 iOS 開發&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;React Native&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Flutter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  實作步驟
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Android 完整設定步驟
&lt;/h3&gt;

&lt;h4&gt;
  
  
  步驟 1：建立環境配置檔案
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build_config/
├── development.json
└── production.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 2：修改 &lt;code&gt;build.gradle.kts&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Base64&lt;/span&gt;

&lt;span class="c1"&gt;// 定義預設配置&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;dartEnvironmentVariables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;".dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"MyApp [DEV]"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 讀取 dart-defines&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dart-defines"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dartDefines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dart-defines"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="n"&gt;dartDefines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decodedEntry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pair&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decodedEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"ENV"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"development"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".dev"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MyApp [DEV]"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="s"&gt;"production"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
                    &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MyApp"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;

&lt;span class="c1"&gt;// 複製 Firebase 配置的 Task&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"copyGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doLast&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="s"&gt;"config/dev"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"config/prod"&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sourceFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$sourceDir/google-services.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;targetFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"google-services.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sourceFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenTaskAdded&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"processDebugGoogleServices"&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"processReleaseGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"copyGoogleServices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;
        &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_SUFFIX"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;resValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"app_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dartEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"APP_CONFIG_NAME"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"MyApp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;envSourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="s"&gt;"src/dev"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"src/prod"&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/main/res"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$envSourceDir/res"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 3：建立資源目錄結構
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/
├── config/
│   ├── dev/google-services.json
│   └── prod/google-services.json
└── src/
    ├── dev/res/mipmap-*/
    └── prod/res/mipmap-*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 4：更新 &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Android 動態生成的檔案
android/app/google-services.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 5：驗證配置
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 測試 Development build&lt;/span&gt;
flutter build apk &lt;span class="nt"&gt;--dart-define-from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build_config/development.json

&lt;span class="c"&gt;# 檢查生成的 APK Application ID&lt;/span&gt;
&lt;span class="c"&gt;# 應該是 com.example.myapp.dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  iOS 完整設定步驟
&lt;/h3&gt;

&lt;h4&gt;
  
  
  步驟 1：建立環境配置檔案
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build_config/
├── development.json
└── production.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 2：建立 xcconfig 檔案
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Flutter/Debug.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "AppConfig.xcconfig"
#include "Generated.xcconfig"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;ios/Flutter/Release.xcconfig&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include "AppConfig.xcconfig"
#include "Generated.xcconfig"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 3：修改 &lt;code&gt;project.pbxproj&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;在 Xcode 中設定 Bundle Identifier：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRODUCT_BUNDLE_IDENTIFIER = "com.example.myapp$(APP_CONFIG_SUFFIX)";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或直接編輯 &lt;code&gt;ios/Runner.xcodeproj/project.pbxproj&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRODUCT_BUNDLE_IDENTIFIER = "com.example.myapp$(APP_CONFIG_SUFFIX)";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 4：修改 &lt;code&gt;Info.plist&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;CFBundleDisplayName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;$(APP_CONFIG_NAME)&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 5：建立資源目錄結構
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/
├── config/
│   ├── Dev/GoogleService-Info.plist
│   └── Prod/GoogleService-Info.plist
└── Runner/
    └── Assets.xcassets/
        ├── AppIcon-Dev.appiconset/
        ├── AppIcon-Prod.appiconset/
        ├── LaunchImage-Dev.imageset/
        └── LaunchImage-Prod.imageset/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 6：建立 &lt;code&gt;generate_app_config.sh&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;參考 3.6 動態生成 xcconfig。&lt;/p&gt;

&lt;h4&gt;
  
  
  步驟 7：建立 &lt;code&gt;copy_launch_image.sh&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;參考 3.4 Launch Image 切換。&lt;/p&gt;

&lt;h4&gt;
  
  
  步驟 8：新增 Xcode Build Phase
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;打開 Xcode 專案&lt;/li&gt;
&lt;li&gt;選擇 Runner target → Build Phases&lt;/li&gt;
&lt;li&gt;點擊 "+" → "New Run Script Phase"&lt;/li&gt;
&lt;li&gt;名稱設為 "Copy Launch Image"&lt;/li&gt;
&lt;li&gt;將此 phase 拖曳到 "Run Script (Flutter build)" 之前&lt;/li&gt;
&lt;li&gt;Script 內容：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRCROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/../scripts/copy_launch_image.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 9：設定 Xcode Build Settings
&lt;/h4&gt;

&lt;p&gt;在 Xcode 的 Build Settings 中搜尋 "ASSETCATALOG_COMPILER_APPICON_NAME"，設定為：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$(APP_CONFIG_ICON_NAME)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 10：更新 &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iOS 動態生成的檔案
ios/Flutter/AppConfig.xcconfig
ios/Runner/GoogleService-Info.plist
ios/Runner/Assets.xcassets/LaunchImage.imageset/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  步驟 11：驗證配置
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 測試 Development build&lt;/span&gt;
./scripts/generate_app_config.sh development
flutter build ios &lt;span class="nt"&gt;--dart-define-from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build_config/development.json

&lt;span class="c"&gt;# 檢查生成的 Bundle ID&lt;/span&gt;
&lt;span class="c"&gt;# 應該是 com.example.myapp.dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  整合 CI/CD
&lt;/h3&gt;

&lt;h4&gt;
  
  
  GitHub Actions 範例
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;staging&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-android&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Flutter&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;subosito/flutter-action@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter pub get&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build APK (Development)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/staging'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;KEY_STORE_FILE_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_STORE_FILE_PATH }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_STORE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_STORE_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_ALIAS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_ALIAS }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_PASSWORD }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;flutter build apk \&lt;/span&gt;
            &lt;span class="s"&gt;--dart-define-from-file=build_config/development.json&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build AAB (Production)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;KEY_STORE_FILE_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_STORE_FILE_PATH }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_STORE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_STORE_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_ALIAS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_ALIAS }}&lt;/span&gt;
          &lt;span class="na"&gt;KEY_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KEY_PASSWORD }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;flutter build appbundle \&lt;/span&gt;
            &lt;span class="s"&gt;--dart-define-from-file=build_config/production.json&lt;/span&gt;

  &lt;span class="na"&gt;build-ios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Flutter&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;subosito/flutter-action@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter pub get&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate iOS Config (Development)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/staging'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/generate_app_config.sh development&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate iOS Config (Production)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/generate_app_config.sh production&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build IPA&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref == 'refs/heads/main' &amp;amp;&amp;amp; 'production' || 'development' }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;flutter build ipa \&lt;/span&gt;
            &lt;span class="s"&gt;--dart-define-from-file=build_config/$ENVIRONMENT.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  注意事項
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;iOS 必須先執行 &lt;code&gt;generate_app_config.sh&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android 會自動從 dart-defines 判斷環境&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signing 配置需要設定環境變數或 secrets&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firebase 配置檔案需要事先準備好並加入版本控制&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  進階技巧
&lt;/h2&gt;

&lt;h3&gt;
  
  
  自動化腳本設計
&lt;/h3&gt;

&lt;h4&gt;
  
  
  從 &lt;code&gt;project.pbxproj&lt;/code&gt; 提取 Bundle ID
&lt;/h4&gt;

&lt;p&gt;為了避免重複配置，我們從 &lt;code&gt;project.pbxproj&lt;/code&gt; 動態提取 base Bundle ID：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PBXPROJ_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/ios/Runner.xcodeproj/project.pbxproj"&lt;/span&gt;
&lt;span class="nv"&gt;BASE_BUNDLE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="s1"&gt;'PRODUCT_BUNDLE_IDENTIFIER'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PBXPROJ_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'$'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;優點&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;單一配置來源（&lt;code&gt;project.pbxproj&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;Script 自動適應 Bundle ID 變更&lt;/li&gt;
&lt;li&gt;不需要硬編碼 Bundle ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  錯誤處理最佳實務
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;  &lt;span class="c"&gt;# 遇到錯誤立即停止&lt;/span&gt;

&lt;span class="c"&gt;# 檢查檔案是否存在&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src_firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error: Firebase config file not found: &lt;/span&gt;&lt;span class="nv"&gt;$src_firebase_config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# 檢查參數是否正確&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENV_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error: Unknown environment &lt;/span&gt;&lt;span class="nv"&gt;$ENV_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; [development|production]"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
        &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  避免重複配置
&lt;/h3&gt;

&lt;h4&gt;
  
  
  單一配置來源原則
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;不好的做法&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/Flutter/
├── AppConfig-dev.xcconfig     # 重複配置
├── AppConfig-prod.xcconfig    # 重複配置
└── AppConfig.xcconfig         # 動態生成
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;好的做法&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ios/Flutter/
└── AppConfig.xcconfig         # 唯一配置來源（動態生成）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;為什麼移除靜態 xcconfig 檔案&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置重複&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;靜態檔案和動態生成的檔案內容重複&lt;/li&gt;
&lt;li&gt;修改時需要同時更新多處&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;容易出錯&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;開發者可能忘記更新靜態檔案&lt;/li&gt;
&lt;li&gt;靜態檔案和動態生成的內容可能不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;混亂的優先順序&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果同時存在靜態檔案和動態檔案，Xcode 會優先使用哪一個？&lt;/li&gt;
&lt;li&gt;難以預測和除錯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  動態生成 vs 靜態檔案
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;動態生成&lt;/th&gt;
&lt;th&gt;靜態檔案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;配置來源&lt;/td&gt;
&lt;td&gt;script 中的邏輯&lt;/td&gt;
&lt;td&gt;多個 xcconfig 檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可測試性&lt;/td&gt;
&lt;td&gt;✅ 可以獨立測試 script&lt;/td&gt;
&lt;td&gt;❌ 需要實際 build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可重用性&lt;/td&gt;
&lt;td&gt;✅ 可應用於其他專案&lt;/td&gt;
&lt;td&gt;❌ 需要複製檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;錯誤處理&lt;/td&gt;
&lt;td&gt;✅ Script 可以驗證配置&lt;/td&gt;
&lt;td&gt;❌ Xcode 錯誤訊息不清楚&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD 整合&lt;/td&gt;
&lt;td&gt;✅ 明確的執行步驟&lt;/td&gt;
&lt;td&gt;❌ 依賴檔案是否存在&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;結論&lt;/strong&gt;：使用動態生成 + 單一配置來源，讓配置清晰且易於維護。&lt;/p&gt;




&lt;h2&gt;
  
  
  常見問題與陷阱
&lt;/h2&gt;

&lt;h3&gt;
  
  
  FlutterFire CLI 的限制
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：為什麼不推薦使用 FlutterFire CLI 的 &lt;code&gt;flutterfire configure&lt;/code&gt;？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不支援動態環境切換&lt;/li&gt;
&lt;li&gt;配置重複且難以維護&lt;/li&gt;
&lt;li&gt;CI/CD 整合複雜&lt;/li&gt;
&lt;li&gt;Bundle ID 管理困難&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;推薦做法&lt;/strong&gt;：手動管理 Firebase 配置檔案，使用 script 自動複製。&lt;/p&gt;

&lt;p&gt;詳見 3.6 為什麼不使用 FlutterFire CLI。&lt;/p&gt;

&lt;h3&gt;
  
  
  Xcode Pre-build Action vs 獨立 Script
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：為什麼不在 Xcode Build Phase 中執行 &lt;code&gt;generate_app_config.sh&lt;/code&gt;？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CI/CD 環境不一致（&lt;code&gt;flutter build&lt;/code&gt; 不會執行 Pre-build Action）&lt;/li&gt;
&lt;li&gt;環境參數傳遞困難&lt;/li&gt;
&lt;li&gt;本地開發體驗不一致（只有 Xcode 會執行）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;推薦做法&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;本地開發：手動執行 script&lt;/li&gt;
&lt;li&gt;CI/CD：在 workflow 中明確執行 script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;詳見 3.6 為什麼不使用 Xcode Pre-build Action。&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Phase 執行順序
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：Launch Image 的 Copy Script 應該放在哪個 Build Phase？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;答案&lt;/strong&gt;：必須在 "Run Script (Flutter build)" &lt;strong&gt;之前&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;正確順序&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;[CP] Check Pods Manifest.lock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Copy Launch Image&lt;/code&gt;&lt;/strong&gt; ← 在這裡&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Run Script&lt;/code&gt; (Flutter build)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sources&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Resources&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter build 會處理 Assets.xcassets&lt;/li&gt;
&lt;li&gt;如果 LaunchImage.imageset 不存在，build 會失敗&lt;/li&gt;
&lt;li&gt;因此必須先複製 launch image&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  快取問題處理
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：修改 Launch Image 或 App Icon 後，裝置上顯示的還是舊的圖片。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：iOS 會快取 launch screen 和 app icon。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解決方法&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;建立清除快取 script&lt;/strong&gt;（&lt;code&gt;scripts/clear_ios_cache.sh&lt;/code&gt;）：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Cleaning iOS build cache..."&lt;/span&gt;
   &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; build/
   &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ios/build/
   &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ios/Pods/
   &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .dart_tool/build/

   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done! Please:"&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"1. Delete the app from your device"&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"2. flutter clean"&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"3. flutter pub get"&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"4. Rebuild the app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;執行步驟&lt;/strong&gt;：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./scripts/clear_ios_cache.sh
   flutter clean
   flutter pub get
   &lt;span class="c"&gt;# 完全刪除裝置上的 app&lt;/span&gt;
   &lt;span class="c"&gt;# 重新 build 並安裝&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  多環境新增
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：如何新增一個新環境（例如 Staging）？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;build_config/&lt;/code&gt; 新增 &lt;code&gt;staging.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;build.gradle.kts&lt;/code&gt; 的 &lt;code&gt;when&lt;/code&gt; 區塊新增 staging case&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;android/app/src/&lt;/code&gt; 新增 &lt;code&gt;staging/res/&lt;/code&gt; 目錄&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;android/app/config/&lt;/code&gt; 新增 &lt;code&gt;staging/google-services.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;iOS&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;build_config/&lt;/code&gt; 新增 &lt;code&gt;staging.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;generate_app_config.sh&lt;/code&gt; 的 &lt;code&gt;case&lt;/code&gt; 區塊新增 staging case&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Assets.xcassets&lt;/code&gt; 新增 &lt;code&gt;AppIcon-Staging.appiconset&lt;/code&gt; 和 &lt;code&gt;LaunchImage-Staging.imageset&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;ios/config/&lt;/code&gt; 新增 &lt;code&gt;Staging/GoogleService-Info.plist&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  環境參數遺漏
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;：忘記執行 &lt;code&gt;generate_app_config.sh&lt;/code&gt; 會怎樣？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;影響&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS build 會使用上次生成的配置&lt;/li&gt;
&lt;li&gt;可能導致環境錯誤（例如想 build prod 但實際使用 dev 配置）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;解決方法&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 CI/CD workflow 中加入驗證步驟&lt;/li&gt;
&lt;li&gt;本地開發建立 alias 或 script wrapper&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;驗證範例&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 驗證 AppConfig.xcconfig 是否存在且內容正確&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"ios/Flutter/AppConfig.xcconfig"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: AppConfig.xcconfig not found"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please run: ./scripts/generate_app_config.sh &amp;lt;environment&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# 驗證環境配置是否匹配&lt;/span&gt;
&lt;span class="nv"&gt;EXPECTED_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENVIRONMENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".dev"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ACTUAL_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;APP_CONFIG_SUFFIX ios/Flutter/AppConfig.xcconfig | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'='&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EXPECTED_SUFFIX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_SUFFIX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Environment mismatch"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Expected: &lt;/span&gt;&lt;span class="nv"&gt;$ENVIRONMENT&lt;/span&gt;&lt;span class="s2"&gt; (suffix: '&lt;/span&gt;&lt;span class="nv"&gt;$EXPECTED_SUFFIX&lt;/span&gt;&lt;span class="s2"&gt;')"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Actual: suffix '&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_SUFFIX&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  參考資源
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thiele.dev/blog/flutter-dart-define-part-2-dev-and-prod-package-names-and-bundle-ids/" rel="noopener noreferrer"&gt;Flutter dart-define Part 2 - Dev and Prod Package Names and Bundle IDs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dart.dev/effective-dart" rel="noopener noreferrer"&gt;Effective Dart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flutter.dev/testing/build-modes" rel="noopener noreferrer"&gt;Flutter Build Modes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/studio/build" rel="noopener noreferrer"&gt;Android Build Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project" rel="noopener noreferrer"&gt;Xcode Build Configuration Files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>ios</category>
      <category>android</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
