.NET MAUI の Android プロジェクトで Plugin.InAppBilling を v8.0.5 から v9.1.0 に上げた際、「Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx のバージョン競合」により NuGet のリストアが失敗する事象が報告されています。本記事では原因の構造を図解レベルで分解し、最短で確実に解消する手順と、将来の再発を封じる 中央管理(Directory.Packages.props)・キャッシュ対策・CI での固定化までまとめて解説します。
症状:Plugin.InAppBilling を 9.1.0 へ更新するとリストアに失敗する
対象プロジェクトは .NET MAUI(Android ターゲット)。Plugin.InAppBilling を v9.1.0 に更新した直後、NuGet のリストアが以下のようなエラーで停止します。
NU1107: Version conflict detected for Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx.
ProjectAndroid -> Plugin.InAppBilling 9.1.0 -> Xamarin.AndroidX.Preference 1x.x -> Xamarin.AndroidX.Fragment.Ktx (=> LiveData 2.9.2.*)
ProjectAndroid -> Microsoft.Maui.Core xxx -> Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx (<= 2.8.7.*)
Try referencing Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx 2.9.2.1 directly in the project.
要点は、上位(Microsoft.Maui.Core)側が 2.8.7 系を要求している一方で、別系統(Preference → Fragment.Ktx)から 2.9.2 系が求められ、解決不能になっていることです。
原因の正体:依存グラフの「上限制約」×「最新系要求」の衝突
NuGet の解決は “近接優先(nearest-wins)” と “上位の明示バージョンが優先” のルールで動きます。ところが、上限付きの範囲指定(例:< 2.8.8)と、より新しい系列(例:>= 2.9.2.1)が 別経路 から同じパッケージへ向かうと、トップレベルで明示しない限り矛盾が解けません。
| 依存元 | 経路 | 対象パッケージ | 要求バージョン | 状況 |
|---|---|---|---|---|
| Microsoft.Maui.Core | 直接 | Lifecycle.LiveData.Core.Ktx | 2.8.7 系(上限付き) | 古い系列に固定されやすい |
| Xamarin.AndroidX.Preference | Preference → Fragment.Ktx → … | Lifecycle.LiveData.Core.Ktx | 2.9.2 系(最新系) | 新しい系列を要求 |
| Plugin.InAppBilling 9.1.0 | 上記を経由 | — | — | グラフ内で矛盾が顕在化 |
このような「系列違い」の衝突は、AndroidX の世代がズレていると起こりがちです。LiveData.Core.Ktx と LiveData.Core は密に連動するため、どちらか一方だけを上げても安定しないケースがあります。
解決策(最短・確実):LiveData を 2.9.2.1 に明示固定して全体を揃える
プロジェクトに次の2 パッケージを トップレベルで明示追加し、2.9.2.1 に統一します。
Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx 2.9.2.1
Xamarin.AndroidX.Lifecycle.LiveData.Core 2.9.2.1
GUI の場合
- 「NuGet パッケージの管理」から上記 2 パッケージを検索
- バージョンに 2.9.2.1 を指定してインストール
CLI の場合
dotnet add ProjectAndroid package Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx --version 2.9.2.1
dotnet add ProjectAndroid package Xamarin.AndroidX.Lifecycle.LiveData.Core --version 2.9.2.1
合わせて実施:クリーン & リビルド
bin/objフォルダーを削除dotnet clean→dotnet build(または IDE の「クリーン」「ビルド」)
最後に:Plugin.InAppBilling を 9.1.0 へ
dotnet add ProjectAndroid package Plugin.InAppBilling --version 9.1.0
依存グラフ全体が LiveData 2.9.2.1 に揃うため、競合は解消されます。
なぜこの方法で安定するのか(NuGet の挙動を理解する)
- トップレベルでの明示は強い:プロジェクト直下に追加したバージョンは、同一パッケージの転送依存より優先されます。
- “上限制約” との決別:2.8.7 系に付いていた上限(例:
< 2.8.8)が事実上無効化され、2.9.2.1 に統一されます。 - ペア運用:Ktx と非 Ktx(Core)は同一系列で揃えるのが鉄則。片方だけ上げると AAR の ABI ズレでビルド・実行時不具合の温床になります。
作業クイックリファレンス(最小手順の表)
| 手順 | コマンド/操作 | 目的 | 注意点 |
|---|---|---|---|
| LiveData 明示追加 | dotnet add ... Core.Ktx 2.9.2.1dotnet add ... Core 2.9.2.1 | 系列統一(2.9.2.1) | Ktx と Core をペアで入れる |
| クリーン | dotnet clean / bin/obj 削除 | 古い AAR / キャッシュを排除 | CI でも毎回実施を推奨 |
| 更新 | dotnet add ... InAppBilling 9.1.0 | 目的パッケージの適用 | Restore 後にビルドで検証 |
再発防止:Directory.Packages.props で AndroidX 世代を一括固定
複数プロジェクトで AndroidX の世代ズレが起きないよう、中央管理を導入します。ソリューション直下に Directory.Packages.props を置くと、全プロジェクトで同一バージョンを共有できます。
セットアップ例
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
以降、各 .csproj では Version を書かずに PackageReference だけを追加します。世代を変える必要が生じたら、この props を 1 箇所更新するだけで全体に反映できます。
ビルドキャッシュ対策:古い AAR を完全に捨てる
- IDE でのキャッシュクリア:ツール > オプション > Xamarin > Android Settings の Clear Java Build Cache を実行。
- ローカルの bin/obj:手作業で削除し、
dotnet restore→buildの順で再生成。 - CI でのキャッシュキー:AndroidX の系列(例:
androidx-2.9.2.1)をキャッシュキーに含めると、世代切替時に確実に無効化されます。
別解と判断基準:どのタイミングで MAUI を上げるべきか
根本的には、Microsoft.Maui.* 自体を最新安定版へ上げると、AndroidX の依存が新系列に揃い、競合が発生しにくくなります。とはいえ、MAUI のメジャー更新は UI / ビルドパイプラインにも影響するため、プロダクトのリリースサイクルと相談が必要です。次の基準で判断すると安全です。
- サードパーティ(例:InAppBilling)が新系列 AndroidX を前提に進化している。
- アプリ側で他の AndroidX.* も 2.9 系以上へ寄せたい。
- CI・ストア配信(AAB 生成)・minSdk/targetSdk の更新計画がある。
当面は本記事のピン留め方式(2.9.2.1 を明示)で凌ぎ、期が熟したら MAUI を更新して 中央管理の定義を縮小する、という二段構えが実務では現実的です。
よくある落とし穴と回避策
- LiveData.Core だけ上げる/Ktx を忘れる:実行時に
NoSuchMethodErrorやクラス解決エラーが起きることがあります。必ずペアで同一バージョンに。 - 旧キャッシュで誤検知:ビルドは通るのにランタイムでクラッシュ…大抵は AAR の取り違え。キャッシュクリアは面倒でも毎回やると安定します。
- プロジェクト間のズレ:ソリューション内に複数の Android プロジェクトがある場合、片方だけが 2.9 系だと再現性が崩れます。Directory.Packages.props で一括固定を。
- “Xamarin.AndroidX.*” と “AndroidX.*” の混在:MAUI/Xamarin.Android の NuGet は Xamarin.AndroidX.* 系に統一しましょう。別命名のパッケージが混じると依存解決の枝が増え、事故率が上がります。
検証手順(最小リプロダクション)
問題の再現・解消を CI で自動検証するための最小構成例です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-android</TargetFrameworks>
<UseMaui>true</UseMaui>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" />
<PackageReference Include="Plugin.InAppBilling" Version="9.1.0" />
<!-- 解決策:LiveData を明示揃え -->
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData.Core" Version="2.9.2.1" />
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx" Version="2.9.2.1" />
</ItemGroup>
</Project>
実行コマンド例:
dotnet nuget locals all --clear
dotnet restore -v minimal
dotnet build -c Release
これでリストアが安定すれば、依存競合は解けています。もし他パッケージで同様の競合が出たら、同じ発想で「トップレベルに明示追加して系列を合わせる」ことで大抵は解決します。
CI/CD への落とし込み(GitHub Actions / Azure Pipelines のヒント)
- 復元の再現性:
Directory.Packages.propsを導入し、nuget.configに社内フィード等があるなら順序を固定。 - キャッシュキー:
~/.nuget/packagesのキャッシュはhashFiles('Directory.Packages.props')を鍵にし、系列変更時に自動破棄。 - Restore の安定化:
dotnet restore --disable-parallelや--forceをトラブル時の暫定策として用意。 - ストアビルド:
bundle(AAB)生成まで通し、minSdk/targetSdk がリリースポリシーに合致するかを合わせて検証。
トラブルシューティング:それでも直らない時にチェックするポイント
- 依存グラフを可視化:
dotnet list package --include-transitiveを実行し、LiveData 系がすべて2.9.2.1に揃っているかを確認。 - クリーンの徹底:
bin/objを削除。IDE のビルドキャッシュもクリア。 - 他の AndroidX の世代:Fragment.Ktx や Preference など、隣接パッケージのバージョンが極端に古くないか。
- ABI/NDK の絡み:ネイティブ関連の設定変更(AOT/LLVM、コード縮小)でビルドが壊れていないか。
背景理解:AndroidX と KTX の関係を簡潔に
- AndroidX:旧 Support Library の後継。モジュールごとに独立更新される。
- KTX:Kotlin 拡張(Kotlin での記法を便利にするヘルパ)。Core と Core.Ktx は基本的に同一系列で使う設計思想。
- LiveData:ライフサイクル対応のデータホルダ。Fragment / Preference / Navigation 等と密接に連動。
このため、LiveData の世代ズレは UI コンポーネント一帯に波及しやすく、依存グラフの中枢をピン留めするのが現実的な最適解です。
ケーススタディ:段階的アップグレードの実践
レガシー MAUI (あるいは Xamarin.Android)からの移行途中で、AndroidX の世代が複数混在することがあります。次の手順で安全に更新します。
- まず LiveData と Fragment.Ktx・Preference を同系列に揃える。
- 次に Navigation・Activity・AppCompat など周辺を追随させる。
- それでも破綻が出る場合、MAUI バージョンの更新を検討(Breaking Changes を個別に吸収)。
品質担保:アップデート後の確認観点
- アプリ起動時間(Cold Start)に変化はないか。
- 課金フロー(購入・復元・エラー処理)の UI/UX が維持されているか。
- バックスタックやライフサイクルイベント(
OnResume等)で LiveData の通知順序が変わっていないか。 - ProGuard / R8 の設定で LiveData 関連クラスが誤って縮小されていないか。
まとめ:今日すぐやるべきこと
- LiveData.Core / Core.Ktx を 2.9.2.1 に明示追加
- bin/obj と Java Build Cache をクリア
- Plugin.InAppBilling を 9.1.0 に更新し、テスト
- Directory.Packages.props を導入し、AndroidX の世代を集中管理
この 4 点を押さえれば、「LiveData.Core.Ktx のバージョン競合」による更新停止は解消し、将来のアップデートでも同種の衝突を未然に防げます。依存グラフを “中枢でピン留めする” という設計パターンを、チームの標準として取り入れておくと長期的に効きます。
FAQ
Q. 2.9.2.1 以外の 2.9.2.x でもよい?
A. 同系列であれば概ね機能しますが、Ktx と Core を完全一致させることが重要です。チームで特定パッチに固定し、全環境で同じ結果を得る運用を推奨します。
Q. MAUI を上げれば本件は不要?
A. 多くのケースで競合は消えますが、他のパッケージが更に新しい系列を要求して再びズレることもあります。中央管理による固定は引き続き有効です。
Q. LiveData を 2.9 系へ上げる副作用は?
A. Fragment/Preference/Navigation/Activity 系の組み合わせにより、ごく稀に警告が増えることがあります。まずは本記事の最小差分(LiveData のみ)で検証し、問題があれば隣接パッケージを同系列に寄せていきます。
Q. 競合の根拠をどう見つける?
A. dotnet list package --include-transitive でパスを追跡し、どの経路がどのバージョンを要求しているかをテキスト保存。差分レビューできるように PR に添付すると再発時の解析が速くなります。
手順スニペット集(そのまま貼って使える)
CLI まとめ
# 1) LiveData を明示固定
dotnet add ProjectAndroid package Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx --version 2.9.2.1
dotnet add ProjectAndroid package Xamarin.AndroidX.Lifecycle.LiveData.Core --version 2.9.2.1
# 2) クリーン & リストア
dotnet nuget locals all --clear
dotnet clean
dotnet restore
# 3) 目的パッケージ更新
dotnet add ProjectAndroid package Plugin.InAppBilling --version 9.1.0
# 4) ビルド
dotnet build -c Release
Central Package Management(導入直後の props 例)
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData.Core" Version="2.9.2.1" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData.Core.Ktx" Version="2.9.2.1" />
</ItemGroup>
</Project>
.csproj(中央管理併用時の最小記述例)
<ItemGroup>
<PackageReference Include="Plugin.InAppBilling" />
<PackageReference Include="Microsoft.Maui.Controls" />
</ItemGroup>
最後に
依存関係の問題は「どのパッケージを入れるか」ではなく、“どの世代を全体として採用するか”の判断が要です。今回のケースは LiveData を 2.9.2.1 に揃えるだけで解けますが、学べる原則は再利用可能です。AndroidX のコアを中枢に、トップレベルの明示と中央管理で堅牢な依存グラフを保ちましょう。

コメント