Skip to content

初回起動時のユーザードキュメント書き込みを最適化#1768

Draft
bannzai wants to merge 3 commits into
mainfrom
optimize-user-doc-write
Draft

初回起動時のユーザードキュメント書き込みを最適化#1768
bannzai wants to merge 3 commits into
mainfrom
optimize-user-doc-write

Conversation

@bannzai

@bannzai bannzai commented Jan 22, 2026

Copy link
Copy Markdown
Owner

概要

初回起動時のFirestoreへのユーザードキュメント書き込みを2回から1回に削減します。

現状の問題

初回起動時(2回書き込み)

  1. FetchOrCreateUser._create()書き込み1: {anonymousUserID, userIDWhenCreateUser}
  2. SaveUserLaunchInfo._saveStats()書き込み2: {lastLoginAt, stats, timezone, packageInfo, ...}

通常起動時(1回書き込み)

  1. SaveUserLaunchInfo._saveStats()書き込み1のみ

解決方法

_create() で起動情報も一緒に保存し、UserSetup で新規作成時は saveUserLaunchInfo をスキップするようにしました。

変更内容

1. lib/provider/user.dart

  • FetchOrCreateUser.call() の返り値を (User, {bool isNewlyCreated}) に変更
    • 既存ユーザー取得時: (user, isNewlyCreated: false)
    • 新規作成時: (user, isNewlyCreated: true)
  • _create() に起動情報を追加:
    • lastLoginAt, stats, timezone, packageInfo
    • userDocumentIDSets, firebaseCurrentUserIDSets, anonymousUserIDSets
    • isTrial: false

2. lib/features/root/resolver/user_setup.dart

  • isNewlyCreated の状態を追加
  • 新規作成時は saveUserLaunchInfo をスキップ(_create() で既に保存済みのため)

3. テスト

  • build_runner でモックを再生成(返り値の型変更に対応)

効果

  • 初回起動時: Firestore書き込みが2回から1回に削減(50%削減)
  • 通常起動時: 変更なし(1回のまま)
  • コスト削減(Firestoreの書き込み課金)
  • 初回起動時のネットワークラウンドトリップ削減

テスト結果

全テスト(1299件)がパス

- FetchOrCreateUser.call()の返り値を(User, {bool isNewlyCreated})に変更
- _create()に起動情報(stats, timezone, packageInfo等)を追加
- UserSetupで新規作成時はsaveUserLaunchInfoをスキップ

これにより初回起動時のFirestore書き込みが2回から1回に削減される
@coderabbitai

coderabbitai Bot commented Jan 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @bannzai, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

このプルリクエストは、アプリケーションの初回起動時におけるユーザーデータのFirestoreへの書き込みプロセスを最適化することを目的としています。既存の2段階の書き込み処理を1段階に集約することで、初回起動時のユーザーエクスペリエンスを向上させ、ネットワークラウンドトリップとFirestoreの書き込みコストを削減します。この変更は、ユーザーの初期セットアップフローの効率性を高めることに貢献します。

Highlights

  • 初回起動時のFirestore書き込み回数削減: 初回起動時のFirestoreへのユーザーデータ書き込みが2回から1回に削減されました。これにより、初回起動時のパフォーマンスが向上し、Firestoreの課金コストも削減されます。
  • FetchOrCreateUser.call()の返り値変更: FetchOrCreateUser.call() メソッドの返り値が (User, {bool isNewlyCreated}) に変更され、ユーザーが新規作成されたかどうかを示すフラグが追加されました。
  • 新規ユーザー作成時の初期データ書き込み: 新規ユーザーが作成される際 (_create() メソッド内) に、lastLoginAtstatstimezonepackageInfo、各種ユーザーIDセット (userDocumentIDSets, firebaseCurrentUserIDSets, anonymousUserIDSets)、および isTrial: false といった初期起動情報がまとめて書き込まれるようになりました。
  • UserSetupロジックの最適化: UserSetup ウィジェットにおいて、ユーザーが新規作成された場合は saveUserLaunchInfo の呼び出しがスキップされるようになりました。これは、_create() メソッドで既に起動情報が保存されているためです。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

初回起動時のFirestoreへの書き込みを2回から1回に削減する最適化、素晴らしいです。FetchOrCreateUserの返り値を変更し、UserSetupで新規ユーザー作成時のsaveUserLaunchInfoの呼び出しをスキップする対応は、目的を達成するための的確なアプローチだと感じました。
1点、lib/provider/user.dart内の_createメソッドと_saveStatsメソッドでコードの重複が見られます。詳細はコメントをご確認ください。

Comment thread lib/provider/user.dart
Comment on lines 97 to 151
debugPrint('#create $uid');
final sharedPreferences = await SharedPreferences.getInstance();
final anonymousUserID = sharedPreferences.getString(StringKey.lastSignInAnonymousUID);

// Stats
final lastLoginVersion = await PackageInfo.fromPlatform().then((value) => value.version);
String? beginVersion = sharedPreferences.getString(StringKey.beginVersion);
if (beginVersion == null) {
await sharedPreferences.setString(StringKey.beginVersion, lastLoginVersion);
beginVersion = lastLoginVersion;
}

// Timezone
final now = DateTime.now().toLocal();
final timeZoneName = now.timeZoneName;
final timeZoneOffset = now.timeZoneOffset;
final timeZoneDatabaseName = await FlutterTimezone.getLocalTimezone();

// Package
final packageInfo = await PackageInfo.fromPlatform();
final os = Platform.operatingSystem;
final package = Package(latestOS: os, appName: packageInfo.appName, buildNumber: packageInfo.buildNumber, appVersion: packageInfo.version);

// UserIDs(初回なので初期値)
final userDocumentIDSets = [uid];
final anonymousUserIDSets = anonymousUserID != null ? [anonymousUserID] : <String>[];
final firebaseCurrentUserID = firebase_auth.FirebaseAuth.instance.currentUser?.uid;
final firebaseCurrentUserIDSets = firebaseCurrentUserID != null ? [firebaseCurrentUserID] : <String>[];

return databaseConnection.userRawReference().set(
{
// 既存フィールド
if (anonymousUserID != null) UserFirestoreFieldKeys.anonymousUserID: anonymousUserID,
UserFirestoreFieldKeys.userIDWhenCreateUser: uid,
// 起動情報
'lastLoginAt': now,
'stats': {
'lastLoginAt': now,
'beginVersion': beginVersion,
'lastLoginVersion': lastLoginVersion,
},
'timezone': {
'name': timeZoneName,
'databaseName': timeZoneDatabaseName,
'offsetInHours': timeZoneOffset.inHours,
'offsetIsNegative': timeZoneOffset.isNegative,
},
UserFirestoreFieldKeys.packageInfo: package.toJson(),
UserFirestoreFieldKeys.userDocumentIDSets: userDocumentIDSets,
UserFirestoreFieldKeys.firebaseCurrentUserIDSets: firebaseCurrentUserIDSets,
UserFirestoreFieldKeys.anonymousUserIDSets: anonymousUserIDSets,
UserFirestoreFieldKeys.isTrial: false,
},
SetOptions(merge: true),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_createメソッドと_saveStatsメソッドで、起動情報を構築するロジックがほぼ重複しています。
今後のメンテナンス性を考慮すると、この共通ロジックを別のプライベートメソッドとして抽出し、両方のメソッドから呼び出すようにリファクタリングすることをお勧めします。

例えば、以下のような共通メソッドを作成することが考えられます。

Future<Map<String, dynamic>> _buildLaunchInfoData({User? user, String? newUserID}) async {
  // ...共通のロジック...
}

これにより、コードの重複が解消され、将来の仕様変更にも対応しやすくなります。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant