実際に躓いたポイント全部まとめ
個人開発で「自分だけの単語帳アプリ」を Flutter で作り、iOS / Android 両対応で公開しました。
Google スプレッドシートをデータソースとして使うという構成で、思ったより多くの壁にぶつかったので、同じところで詰まる人が減るように記録しておきます。
作ったもの
Google スプレッドシートで管理した単語データをスマホアプリから参照・学習できる単語帳アプリです。
- PC のスプレッドシートで単語を追加・編集
- アプリからインポートしてインクリメンタルサーチ・フラッシュカード学習
- 手動登録にも対応
バックエンドサーバーを持たず、Google スプレッドシートをデータストアとして使うことで、個人開発の規模感に合ったシンプルな構成にしました。
1. Google スプレッドシート連携の実装
必要な準備
Google スプレッドシートをアプリから読み書きするには、Google Cloud Console での設定が必要です。
- console.cloud.google.com でプロジェクトを作成
- Google Sheets API を有効化
- OAuth 2.0 クライアント ID を作成
ここで重要なのが「OAuth クライアント ID はプラットフォームごとに別々に作る必要がある」という点です。最初これを知らずに Android 用のものを iOS でも使おうとして、ログインエラーに悩まされました。
Android の設定
Android では「ウェブアプリケーション」タイプのクライアント ID を使います(Credential Manager の仕様です)。
作成したクライアント ID を android/app/src/main/res/values/sign_in_config.xml に設定します。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="default_web_client_id">
YOUR_WEB_CLIENT_ID.apps.googleusercontent.com
</string>
</resources>
Flutter 側では --dart-define でビルド時に値を注入する方法もあります。
// lib/app/app_config.dart
class AppConfig {
static const googleServerClientId = String.fromEnvironment(
'GOOGLE_SERVER_CLIENT_ID',
defaultValue: 'YOUR_DEFAULT_CLIENT_ID.apps.googleusercontent.com',
);
}
iOS の設定(ここが一番躓く)
Android で動いたからといって iOS でそのまま動くと思ったら大間違いでした。iOS には iOS 専用のクライアント ID が別途必要です。
Google Cloud Console で「iOS」タイプのクライアント ID を作成すると、設定用の .plist ファイルがダウンロードできます。中身はこんな感じです。
<key>CLIENT_ID</key>
<string>XXXXXX-YYYYYYYY.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.XXXXXX-YYYYYYYY</string>
<key>BUNDLE_ID</key>
<string>jp.your.app</string>
この値を ios/Runner/Info.plist に追加します。
<key>GIDClientID</key>
<string>XXXXXX-YYYYYYYY.apps.googleusercontent.com</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.XXXXXX-YYYYYYYY</string>
</array>
</dict>
</array>
CFBundleURLSchemes に設定する値は「リバースクライアント ID」と呼ばれる、クライアント ID を逆にした文字列です。
これを忘れると Google からのコールバックが受け取れずサインインが完了しません。
よくあるエラー
iOS でサインインボタンを押したときにこのエラーが出た場合:
PlatformException(google_sign_in, No active configuration.
Make sure GIDClientID is set in Info.plist., NSInvalidArgumentException, null)
→ Info.plist に GIDClientID が設定されていません。上記の手順で追加してください。
2. Android APK の配布
ビルド
flutter build apk --release
これだけです。build/app/outputs/flutter-apk/app-release.apk が生成されます。
iOS と違い、証明書まわりの複雑な設定が不要なのが Android の楽なところです。
配布方法
| 方法 | 特徴 |
|---|---|
| Google Play Store | 公式配布。審査があるが信頼性が高い |
| APK 直接配布 | 審査なし・即日配布可。「提供元不明のアプリ」として警告が出る |
| Firebase App Distribution | テスター向け配布に最適。無料で使える |
躓きポイント
Bundle ID がデフォルトのまま
Flutter でプロジェクトを新規作成すると Bundle ID が com.example.アプリ名 になっています。これを公開前に必ず変更してください。android/app/build.gradle の applicationId と ios/Runner.xcodeproj/project.pbxproj の PRODUCT_BUNDLE_IDENTIFIER の両方を変更します。
特に iOS 側は Google Cloud Console でクライアント ID を作成したときの Bundle ID と完全一致している必要があります。不一致だとサインインが失敗します。
Keystore のバックアップを忘れずに
Google Play でアプリを公開したあと、Keystore ファイルを紛失するとアップデートが一切できなくなります。必ず安全な場所にバックアップしてください。
3. App Store への公開
Android に比べると、iOS は準備が多いです。
順番に進めれば詰まらないので、手順通りにやることが大切です。
前提
- Apple Developer Program(年額 $99 / 約15,000円)への登録が必要
- Xcode が動く Mac が必要
手順の全体像
① Developer Portal で Bundle ID を登録
↓
② App Store Connect でアプリを登録
↓
③ Xcode で署名設定
↓
④ flutter build ipa でビルド
↓
⑤ Xcode Organizer でアップロード
↓
⑥ TestFlight でテスト → 審査提出
① Developer Portal で Bundle ID を登録
App Store Connect でアプリを登録しようとすると、Bundle ID のドロップダウンに何も出てこないことがあります。
これは先に Bundle ID を Apple Developer Portal に登録する必要があるためです。
- developer.apple.com/account/resources/identifiers/list を開く
- 右上の「+」をクリック
- 「App IDs」→「App」を選択
- Bundle ID:Explicit を選択 →
jp.your.appを入力 - 「Register」で完了
② App Store Connect でアプリを登録
appstoreconnect.apple.com → 「マイ App」→「+」→「新規 App」
Bundle ID のドロップダウンを開くと、先ほど登録した ID が選択肢に現れます。
③ Xcode で署名設定
Xcode でプロジェクトを開くときは必ず .xcworkspace を開いてください。.xcodeproj を開くと CocoaPods の依存関係が読み込まれません。
open ios/Runner.xcworkspace
左のファイルツリー最上部にある「Runner」(青いアイコン)をクリック → ターゲット「Runner」を選択 → 「Signing & Capabilities」タブ
- Automatically manage signing にチェック
- Team で自分の Apple Developer アカウントを選択
- Bundle Identifier が正しいことを確認
④ IPA ファイルをビルド
flutter build ipa --release
⑤ Xcode Organizer でアップロード
Xcode メニュー → Product → Archive
アーカイブが完了すると Organizer ウィンドウが開きます。
「Distribute App」→「App Store Connect」→「Upload」で App Store Connect にアップロードされます。
⑥ TestFlight → 審査提出
アップロード後、App Store Connect でビルドの処理が完了するまで10〜30分かかります。処理完了後は TestFlight で実機テストができます。問題なければ「審査へ提出」。
4. 開発中に遭遇したその他の問題
Android ANR(アプリが応答しません)が頻発
jsonEncode / jsonDecode を Flutter のメインスレッド(UI スレッド)で実行していたことが原因でした。
単語データが増えるにつれて処理時間が長くなり、Android の ANR 閾値(約5秒)を超えてしまっていました。
解決策は compute() を使ってバックグラウンド Isolate で処理することです。
import 'package:flutter/foundation.dart' show compute;
// トップレベル関数として定義(compute() に渡す関数はトップレベル必須)
List<Entry> _decodeEntries(String raw) {
final list = jsonDecode(raw) as List<dynamic>;
return list.map((e) => Entry.fromJson(e as Map<String, dynamic>)).toList();
}
// 呼び出し側
final entries = await compute(_decodeEntries, rawJsonString);
また、複数シートのインポート時に1シートごとにディスク書き込みしていたのも原因の一つでした。全シートの処理が終わったあと、1回だけ書き込むように変更して解決しました。
iOS シミュレータでペーストができない
シミュレータの Edit メニューに「Automatically Sync Pasteboard」という項目があり、チェックを入れればMacのクリップボードと共有されるはずなのですが、特定の Xcode バージョンでは機能しないバグがあります。
「Get Pasteboard」もグレーアウトしていて使えないことがあり、原因の特定に時間を取られました。結論:シミュレータのクリップボード問題はシミュレータのバグなので、実機で確認するのが正解です。
テキストフィールドの長押し → ネイティブペーストは動くことが多いので、開発中はそれで代替できます。
RenderFlex Overflow が繰り返し発生する
Column の中に高さが可変のウィジェットを並べると、画面サイズや他のウィジェットの高さが変わるたびにオーバーフローエラーが出ました。
根本的な原因は「兄弟ウィジェットの高さを手で推定して最大高さを計算していた」ことです。
ウィジェットを追加するたびに推定値がずれてオーバーフローする、という悪循環でした。
解決策は LayoutBuilder を使って実際の残り高さを取得し、オーバーレイパネルを Stack + Positioned で描画することです。
Expanded(
child: LayoutBuilder(
builder: (context, constraints) => Stack(
children: [
Positioned.fill(child: メインコンテンツ),
if (パネルを表示したい)
Positioned(
top: 0, left: 0, right: 0,
child: ConstrainedBox(
// LayoutBuilder から取得した実際の高さを使う
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
child: オーバーレイパネル,
),
),
],
),
),
)
こうすることで、兄弟ウィジェットの高さを推定する必要がなくなり、構造的にオーバーフローが発生しなくなります。
5. Google Search Console でのドメイン所有権確認
Google Cloud Console でドメイン名を含む設定(OAuth の承認済みドメインなど)を行う際、そのドメインの所有権を Google に証明する必要があります。
Search Console でのドメイン確認が済んでいないと Google Cloud Console 側でエラーが表示されることがあります。
Search Console でドメインを追加する
まず Google Search Console 側で操作します。
- search.google.com/search-console を開く
- 「プロパティを追加」→ 「ドメイン」 を選択(「URL プレフィックス」ではないので注意)
- ドメイン名(例:
hoge.jp)を入力して「続行」 - 表示された TXT レコードの値(
google-site-verification=XXXXXXXXXX)をコピーしておく
お名前.com で DNS TXT レコードを追加する
DNS レコードの追加は取得元のレジストラで行います。お名前.com の場合は以下の手順です。
-
お名前.com Navi にログイン
-
「ドメイン」→「ドメイン機能一覧」→「DNS 関連機能の設定」
-
対象ドメインを選択 →「次へ」
-
「DNS レコード設定を利用する」→「設定する」
-
以下の内容を入力して「追加」
項目 値 ホスト名 (空白) TYPE TXT TTL 3600 VALUE google-site-verification=XXXXXXXXXX(Search Console でコピーした値) -
「確認画面へ」→「設定する」で保存
DNS の反映には最大 48 時間かかる場合があります(通常は数分〜1 時間程度)。
反映後に Search Console の「確認」ボタンを押すと所有権が証明され、Google Cloud Console のエラーも解消されます。
まとめ
| 問題 | 原因 | 解決策 |
|---|---|---|
| iOS Google サインイン失敗 | iOS 専用クライアント ID が未設定 | Info.plist に GIDClientID を追加 |
| Bundle ID が App Store Connect に出ない | Developer Portal への登録が先 | Identifiers で App ID を登録 |
| Android ANR | JSON 処理をメインスレッドで実行 | compute() でバックグラウンド処理 |
| Overflow が繰り返し出る | 高さを手で推定していた | LayoutBuilder で実測値を使う |
| シミュレータでペーストできない | Xcode のバグ | 実機でテストする |
| Google Cloud Console のドメインエラー | Search Console での所有権確認が未完了 | DNS TXT レコードを追加して確認 |
Google スプレッドシート連携自体は便利で、PC でデータ管理・スマホで参照という使い方がシームレスにできます。ただし OAuth の設定はプラットフォームごとに異なるため、Android で動いたからといって iOS でそのまま動くとは限りません。
App Store の公開は手順が多いですが、一度やると流れがわかります。
Bundle ID の事前登録さえ忘れなければ、あとは Xcode の自動署名に任せるだけです。
この記事は実際の個人開発プロジェクト の開発過程をもとに書きました。