Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile: Prepare demo app POC with hardcoded data #960

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

MDikkii
Copy link
Contributor

@MDikkii MDikkii commented Nov 30, 2024

Added dataSource concept and provided both demo and remote data sources where needed.
Added demoManager to make it possible to flip the demo flag in the repositories and use proper data source in both demo and real app scenarios.

Summary by CodeRabbit

  • New Features

    • Introduced a demo mode management system with DemoManager.
    • Added demo-related buttons and localization support for demo features in the UI.
  • Enhancements

    • Updated authentication and organization handling to include organization data upon successful user authentication.
    • Streamlined data fetching processes in repositories to support both demo and remote data sources.
  • Bug Fixes

    • Improved state management for user authentication and organization data.
  • Documentation

    • Updated localization files to support new demo features.

Copy link

vercel bot commented Nov 30, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
public ⬜️ Ignored (Inspect) Visit Preview Jan 2, 2025 1:35pm

Copy link

coderabbitai bot commented Nov 30, 2024

📝 Walkthrough

Walkthrough

The changes in this pull request introduce several new classes and modify existing ones to enhance the application's handling of demo functionalities and organization data. Key updates include the addition of DemoManager for managing demo mode, modifications to AuthCubit and AuthState to include organization data, and the implementation of various data source classes for user, payment, survey, and organization management. The repositories have been refactored to utilize these data sources, allowing for a seamless switch between demo and remote modes.

Changes

File Change Summary
recipients_app/lib/core/cubits/auth/auth_cubit.dart Modified AuthCubit to handle organization data; updated method signatures for emitted states to include organization.
recipients_app/lib/core/cubits/auth/auth_state.dart Added organization property to AuthState; updated constructor, props, and copyWith method.
recipients_app/lib/data/datasource/demo/demo_user.dart Introduced DemoUser class implementing User interface with placeholder methods.
recipients_app/lib/data/datasource/demo/no_op_document_reference.dart Added NoOpDocumentReference class as a placeholder for document references.
recipients_app/lib/data/datasource/demo/organization_demo_data_source.dart Created OrganizationDemoDataSource class to return demo organization data.
recipients_app/lib/data/datasource/demo/payment_demo_data_source.dart Added PaymentDemoDataSource class to manage demo payment data with methods for fetching, confirming, and contesting payments.
recipients_app/lib/data/datasource/demo/survey_demo_data_source.dart Introduced SurveyDemoDataSource class for managing demo surveys.
recipients_app/lib/data/datasource/demo/user_demo_data_source.dart Created UserDemoDataSource class for mock user authentication and management.
recipients_app/lib/data/datasource/organization_data_source.dart Added abstract OrganizationDataSource class defining fetchOrganization method.
recipients_app/lib/data/datasource/payment_data_source.dart Introduced abstract PaymentDataSource class with methods for payment operations.
recipients_app/lib/data/datasource/remote/organization_remote_data_source.dart Created OrganizationRemoteDataSource class to fetch organization data from Firestore.
recipients_app/lib/data/datasource/remote/payment_remote_data_source.dart Added PaymentRemoteDataSource class for managing payment data in Firestore.
recipients_app/lib/data/datasource/remote/survey_remote_data_source.dart Introduced SurveyRemoteDataSource class to fetch surveys from Firestore.
recipients_app/lib/data/datasource/remote/user_remote_data_source.dart Created UserRemoteDataSource class for user management using Firebase services.
recipients_app/lib/data/models/recipient.dart Removed payments field from Recipient class and updated related methods.
recipients_app/lib/data/repositories/organization_repository.dart Refactored OrganizationRepository to use demo and remote data sources based on DemoManager.
recipients_app/lib/data/repositories/payment_repository.dart Updated PaymentRepository to utilize demo and remote data sources for payment operations.
recipients_app/lib/data/repositories/survey_repository.dart Refactored SurveyRepository to switch between demo and remote data sources.
recipients_app/lib/data/repositories/user_repository.dart Modified UserRepository to manage user data through demo and remote data sources.
recipients_app/lib/demo_manager.dart Introduced DemoManager class to manage demo mode status.
recipients_app/lib/l10n/app_en.arb Added new localization entry for demo functionality.
recipients_app/lib/l10n/app_kri.arb Added new localization entry for demo functionality.
recipients_app/lib/main.dart Integrated DemoManager into application initialization.
recipients_app/lib/my_app.dart Updated MyApp to include DemoManager in repository providers.
recipients_app/lib/view/widgets/welcome/phone_input_page.dart Modified PhoneInputPage to include demo functionality with a new button.
recipients_app/lib/view/pages/terms_and_conditions_page.dart Updated button label based on demo mode status.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@MDikkii MDikkii changed the title Prepare demo app POC with hardcoded data feature(recipients-app): Prepare demo app POC with hardcoded data Nov 30, 2024
@MDikkii MDikkii changed the base branch from mdikkii/update-android-sdk to main December 1, 2024 14:58
@ssandino ssandino changed the title feature(recipients-app): Prepare demo app POC with hardcoded data Mobile: Prepare demo app POC with hardcoded data Dec 6, 2024
@ssandino ssandino added the mobile Issues concerning Mobile App label Dec 6, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 21

🧹 Outside diff range and nitpick comments (17)
recipients_app/lib/data/repositories/payment_repository.dart (1)

9-10: Remove unnecessary 'late' keywords for initialized variables

The variables remoteDataSource and demoDataSource are initialized at declaration, so the late keyword is unnecessary.

Apply this diff to remove the late keywords:

-late PaymentDataSource remoteDataSource = PaymentRemoteDataSource(firestore: firestore);
-late PaymentDataSource demoDataSource = PaymentDemoDataSource();
+PaymentDataSource remoteDataSource = PaymentRemoteDataSource(firestore: firestore);
+PaymentDataSource demoDataSource = PaymentDemoDataSource();
recipients_app/lib/data/repositories/user_repository.dart (2)

12-13: Remove unnecessary 'late' keywords for initialized variables

The variables remoteDataSource and demoDataSource are initialized at declaration, so the late keyword is unnecessary.

Apply this diff to remove the late keywords:

-late UserDataSource remoteDataSource = UserRemoteDataSource(firestore: firestore, firebaseAuth: firebaseAuth);
-late UserDataSource demoDataSource = UserDemoDataSource();
+UserDataSource remoteDataSource = UserRemoteDataSource(firestore: firestore, firebaseAuth: firebaseAuth);
+UserDataSource demoDataSource = UserDemoDataSource();

42-45: Set 'onCancel' handler outside of the listener

Assigning authStateController.onCancel inside the isDemoEnabledStream listener reassigns it every time the demo mode changes. Move it outside to set it once.

Apply this diff to move the onCancel assignment:

+authStateController.onCancel = () {
+  authStateSubscription?.cancel();
+};

demoManager.isDemoEnabledStream.listen((isDemoMode) {
  authStateSubscription?.cancel();

  authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
    authStateController.add(authState);
  });
-  authStateController.onCancel = () {
-    authStateSubscription?.cancel();
-  };
});
recipients_app/lib/data/datasource/organization_data_source.dart (1)

4-8: Add documentation to the interface and method.

Add dartdoc comments to describe:

  • The purpose of this data source
  • The expected behavior of fetchOrganization
  • The meaning of a null return value
+/// Defines the contract for accessing organization data.
 abstract class OrganizationDataSource {
+  /// Fetches an organization by its Firestore reference.
+  /// Returns null if the organization doesn't exist.
   Future<Organization?> fetchOrganization(
     DocumentReference organizationRef,
   );
 }
recipients_app/lib/data/datasource/demo/organization_demo_data_source.dart (1)

8-10: Consider using more realistic demo data

The hardcoded organization data could be more representative of real-world scenarios.

-    return const Organization(name: "Demo organization", contactName: "Demo manager", contactNumber: "+232 123456789");
+    return const Organization(
+      name: "Sierra Leone Social Income",
+      contactName: "John Smith",
+      contactNumber: "+232 76 123456"
+    );
recipients_app/lib/data/datasource/remote/organization_remote_data_source.dart (1)

5-5: Remove unused constant

The surveyCollection constant is not used in this file.

-const String surveyCollection = "surveys";
recipients_app/lib/data/repositories/survey_repository.dart (1)

10-11: Consider eager initialization of data sources

Late initialization of data sources could lead to runtime errors if accessed before initialization. Consider moving initialization to the constructor.

-  late SurveyDataSource remoteDataSource = SurveyRemoteDataSource(firestore: firestore);
-  late SurveyDataSource demoDataSource = SurveyDemoDataSource();
+  final SurveyDataSource remoteDataSource;
+  final SurveyDataSource demoDataSource;
+
+  SurveyRepository({
+    required this.firestore,
+    required this.demoManager,
+  }) : remoteDataSource = SurveyRemoteDataSource(firestore: firestore),
+       demoDataSource = SurveyDemoDataSource();
recipients_app/lib/data/datasource/demo/survey_demo_data_source.dart (1)

13-16: Document demo survey ID format

Add documentation explaining the demo survey ID format and how it relates to production IDs. This helps maintain consistency when adding new demo surveys.

+ /// Demo survey IDs follow the format: <survey_type>
+ /// Production format: UUID

Also applies to: 18-21, 23-28, 30-35

recipients_app/lib/data/datasource/demo/no_op_document_reference.dart (3)

3-4: Add class documentation

Add documentation explaining the purpose and usage of this no-op implementation.

+ /// A no-op implementation of DocumentReference for demo/testing scenarios.
+ /// All operations throw UnimplementedError.

8-11: Improve error messages

Replace generic UnimplementedError with descriptive messages indicating this is a demo implementation.

- throw UnimplementedError();
+ throw UnimplementedError('Operation not supported in demo mode');

Also applies to: 14-17, 24-27, 42-45, 55-58


20-21: Fix TODO comment formatting

Remove TODO comments from getter implementations as they're redundant with the thrown error.

- // TODO: implement firestore
  FirebaseFirestore get firestore => throw UnimplementedError();

Also applies to: 30-31, 34-35, 38-39

recipients_app/lib/data/datasource/demo/user_demo_data_source.dart (2)

14-15: Consider parameterizing phone numbers

Hardcoded phone numbers should be moved to a configuration file for easier maintenance and testing.


42-51: Implement auth flow demo

The TODO comments indicate missing authentication flow implementation. This could affect testing of auth-dependent features.

Would you like help implementing a basic demo auth flow?

Also applies to: 58-61

recipients_app/lib/data/datasource/demo/payment_demo_data_source.dart (2)

18-19: Consider deterministic demo data

Random payment counts could make testing unpredictable. Consider using fixed counts or making them configurable.


31-33: Parameterize payment constants

Currency and amount values should be configurable rather than hardcoded.

Consider moving these to a configuration object:

+class PaymentDemoConfig {
+  final String currency;
+  final double amount;
+  
+  const PaymentDemoConfig({
+    this.currency = "SLE",
+    this.amount = 700,
+  });
+}

Also applies to: 48-50

recipients_app/lib/data/datasource/demo/demo_user.dart (1)

3-149: Consider implementing essential methods

Most methods throw UnimplementedError which could cause crashes. Consider implementing essential methods with demo values.

Examples:

- bool get isAnonymous => throw UnimplementedError();
+ bool get isAnonymous => false;

- String? get phoneNumber => throw UnimplementedError();
+ String? get phoneNumber => "+1234567890";

- List<UserInfo> get providerData => throw UnimplementedError();
+ List<UserInfo> get providerData => [];
recipients_app/lib/my_app.dart (1)

Line range hint 45-69: Consider consolidating repository creation

Multiple repositories are created with similar dependencies.

Consider using a factory or builder pattern to reduce repetition:

class RepositoryFactory {
  static T create<T>(BuildContext context, {
    required FirebaseFirestore firestore,
    required DemoManager demoManager,
  }) {
    switch (T) {
      case UserRepository: return UserRepository(
        firestore: firestore,
        demoManager: demoManager,
      ) as T;
      case PaymentRepository: return PaymentRepository(
        firestore: firestore,
        demoManager: demoManager,
      ) as T;
      // ... other repositories
    }
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b967f4e and 88e4263.

📒 Files selected for processing (27)
  • recipients_app/lib/core/cubits/auth/auth_cubit.dart (2 hunks)
  • recipients_app/lib/core/cubits/auth/auth_state.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/demo_user.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/no_op_document_reference.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/organization_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/payment_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/survey_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/user_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/organization_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/payment_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/organization_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/payment_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/survey_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/user_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/survey_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/user_data_source.dart (1 hunks)
  • recipients_app/lib/data/models/recipient.dart (0 hunks)
  • recipients_app/lib/data/repositories/organization_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/payment_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/survey_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/user_repository.dart (1 hunks)
  • recipients_app/lib/demo_manager.dart (1 hunks)
  • recipients_app/lib/l10n/app_en.arb (1 hunks)
  • recipients_app/lib/l10n/app_kri.arb (1 hunks)
  • recipients_app/lib/main.dart (3 hunks)
  • recipients_app/lib/my_app.dart (4 hunks)
  • recipients_app/lib/view/widgets/welcome/phone_input_page.dart (4 hunks)
💤 Files with no reviewable changes (1)
  • recipients_app/lib/data/models/recipient.dart
🔇 Additional comments (8)
recipients_app/lib/l10n/app_kri.arb (1)

179-180: Verify translation accuracy for "Demo"

The English word "Demo" is used in both English and Krio versions. Consider if this needs localization to Krio or if it's an intentionally preserved English term.

✅ Verification successful

"Demo" appears to be an acceptable English term in Krio context

Looking at the codebase:

  • "Demo" is used consistently in both English and Krio versions
  • Several other English terms are preserved in the Krio translations where they represent technical or widely adopted concepts (e.g., "Surveys", "Overview", "In Progress")
  • This follows a common localization pattern where certain technical or universally recognized terms are intentionally kept in English
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if "Demo" is consistently used across other Krio translations
rg -i "demo" recipients_app/lib/l10n/app_kri.arb

Length of output: 70


Script:

#!/bin/bash
# Check how "Demo" is translated in other language files
fd -e arb -E 'app_kri.arb' -E 'app_en.arb' . recipients_app/lib/l10n/ --exec rg -i "demo" {}

Length of output: 90


Script:

#!/bin/bash
# Check English source file for context
rg -i "demo" recipients_app/lib/l10n/app_en.arb

Length of output: 69


Script:

#!/bin/bash
# Check for other English words in Krio translations to understand pattern
rg -A 1 -B 1 '"[^"]+": "[A-Za-z]+"' recipients_app/lib/l10n/app_kri.arb

Length of output: 2035

recipients_app/lib/l10n/app_en.arb (1)

279-280: LGTM: Consistent formatting and clear messaging

The new entries maintain consistent JSON formatting and use clear, concise text.

recipients_app/lib/data/datasource/survey_data_source.dart (1)

1-7: Interface definition is correct

The abstract class SurveyDataSource properly defines the required method fetchSurveys.

recipients_app/lib/data/repositories/survey_repository.dart (1)

21-26: LGTM: Clean implementation of data source switching

The implementation of _activeDataSource getter and its usage in fetchSurveys follows good practices for abstracting data source selection.

recipients_app/lib/core/cubits/auth/auth_state.dart (1)

29-43: LGTM: Complete state implementation

The state implementation properly includes the new organization field in both props and copyWith method, maintaining immutability and equality comparison.

recipients_app/lib/data/repositories/organization_repository.dart (1)

20-25: LGTM: Clean data source implementation

The implementation of data source switching and organization fetching is clean and follows the established pattern.

recipients_app/lib/main.dart (1)

2-2: LGTM: Clean integration of DemoManager

The DemoManager is properly initialized and integrated into the application setup flow.

Also applies to: 35-35, 57-57

recipients_app/lib/view/widgets/welcome/phone_input_page.dart (1)

115-116: LGTM: Good use of theme system

Using Theme.of(context) ensures consistent styling across the app.

Comment on lines +22 to +23
await firestore.collection(recipientCollection).doc(recipientId).collection(paymentCollection).get();

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Define 'recipientCollection' constant

The variable recipientCollection is used but not defined in this file, which will cause a runtime error.

Apply this diff to define recipientCollection:

+const String recipientCollection = "recipients";

Also applies to: 51-55, 71-74

return null;
}

final userSnapshot = matchingUsers.docs.firstOrNull;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use correct method to retrieve first document

The method firstOrNull is not defined for List<DocumentSnapshot>. Use a null-aware operator instead.

Apply this diff to fix the issue:

-final userSnapshot = matchingUsers.docs.firstOrNull;
+final userSnapshot = matchingUsers.docs.isNotEmpty ? matchingUsers.docs.first : null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final userSnapshot = matchingUsers.docs.firstOrNull;
final userSnapshot = matchingUsers.docs.isNotEmpty ? matchingUsers.docs.first : null;

Comment on lines +74 to +76
codeAutoRetrievalTimeout: (e) {
log("auto-retrieval timeout");
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct parameter name in 'codeAutoRetrievalTimeout' callback

The codeAutoRetrievalTimeout callback should accept a String verificationId, not e.

Apply this diff to correct the parameter:

-codeAutoRetrievalTimeout: (e) {
+codeAutoRetrievalTimeout: (verificationId) {
   log("auto-retrieval timeout");
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
codeAutoRetrievalTimeout: (e) {
log("auto-retrieval timeout");
},
codeAutoRetrievalTimeout: (verificationId) {
log("auto-retrieval timeout");
},

Comment on lines +29 to +35
final phoneNumber = firebaseUser.phoneNumber ?? "";

final matchingUsers = await firestore
.collection(recipientCollection)
.where(
"mobile_money_phone.phone",
isEqualTo: int.parse(phoneNumber.substring(1)),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle empty 'phoneNumber' to prevent exceptions

If firebaseUser.phoneNumber is null or empty, calling substring(1) will throw a RangeError. Ensure the phone number is valid before processing.

Apply this diff to handle invalid phoneNumber:

final phoneNumber = firebaseUser.phoneNumber ?? "";

+if (phoneNumber.length <= 1) {
+  return null;
+}

final matchingUsers = await firestore
    .collection(recipientCollection)
    .where(
      "mobile_money_phone.phone",
      isEqualTo: int.parse(phoneNumber.substring(1)),
    )
    .get();

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +4 to +19
abstract class PaymentDataSource {
Future<List<SocialIncomePayment>> fetchPayments({
required String recipientId,
});

Future<void> confirmPayment({
required Recipient recipient,
required SocialIncomePayment payment,
});

Future<void> contestPayment({
required Recipient recipient,
required SocialIncomePayment payment,
required String contestReason,
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add documentation and error handling specifications.

The interface needs:

  1. Documentation for the class and methods
  2. Error handling specifications for invalid inputs or failed operations
+/// Defines the contract for managing payment operations.
 abstract class PaymentDataSource {
+  /// Fetches payments for a given recipient.
+  /// Throws [ArgumentError] if recipientId is empty.
   Future<List<SocialIncomePayment>> fetchPayments({
     required String recipientId,
   });

+  /// Confirms a payment for a recipient.
+  /// Throws [StateError] if payment is already confirmed.
   Future<void> confirmPayment({
     required Recipient recipient,
     required SocialIncomePayment payment,
   });

+  /// Contests a payment with a given reason.
+  /// Throws [StateError] if payment is already contested.
+  /// Throws [ArgumentError] if contestReason is empty.
   Future<void> contestPayment({
     required Recipient recipient,
     required SocialIncomePayment payment,
     required String contestReason,
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
abstract class PaymentDataSource {
Future<List<SocialIncomePayment>> fetchPayments({
required String recipientId,
});
Future<void> confirmPayment({
required Recipient recipient,
required SocialIncomePayment payment,
});
Future<void> contestPayment({
required Recipient recipient,
required SocialIncomePayment payment,
required String contestReason,
});
}
/// Defines the contract for managing payment operations.
abstract class PaymentDataSource {
/// Fetches payments for a given recipient.
/// Throws [ArgumentError] if recipientId is empty.
Future<List<SocialIncomePayment>> fetchPayments({
required String recipientId,
});
/// Confirms a payment for a recipient.
/// Throws [StateError] if payment is already confirmed.
Future<void> confirmPayment({
required Recipient recipient,
required SocialIncomePayment payment,
});
/// Contests a payment with a given reason.
/// Throws [StateError] if payment is already contested.
/// Throws [ArgumentError] if contestReason is empty.
Future<void> contestPayment({
required Recipient recipient,
required SocialIncomePayment payment,
required String contestReason,
});
}

Comment on lines +26 to +30
Organization? organization;

if (recipient?.organizationRef != null) {
organization = await organizationRepository.fetchOrganization(recipient!.organizationRef!);
}
Copy link

@coderabbitai coderabbitai bot Dec 6, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicated organization fetching logic

The organization fetching logic is duplicated between authStateChanges listener and init method.

Extract into a private method:

+ private Future<Organization?> _fetchOrganization(Recipient? recipient) async {
+   if (recipient?.organizationRef != null) {
+     return await organizationRepository.fetchOrganization(recipient!.organizationRef!);
+   }
+   return null;
+ }

Then use it in both places:

- Organization? organization;
- if (recipient?.organizationRef != null) {
-   organization = await organizationRepository.fetchOrganization(recipient!.organizationRef!);
- }
+ final organization = await _fetchOrganization(recipient);

Also applies to: 67-68

Copy link
Contributor

Choose a reason for hiding this comment

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

I also do not get why we need the organization fetch logic now here, too?

Copy link

Choose a reason for hiding this comment

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

@KarinBerg, the organization fetch logic is included in both the authStateChanges listener and the init method to ensure that the AuthState always contains the most up-to-date organization information. This way, whether the user's authentication state changes or the app initializes, the associated organization data is fetched and emitted with the AuthState.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm talking to AI :) But I got the point and agree with the suggestion to refatore the code. :)

Copy link

Choose a reason for hiding this comment

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

@KarinBerg, glad to hear that you agree with the refactoring suggestion!

(^.^)🎉


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

Comment on lines +107 to +108
@override
String get uid => throw UnimplementedError();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement required uid property

The uid property is critical for user identification and should return a consistent demo value instead of throwing.

- String get uid => throw UnimplementedError();
+ String get uid => "demo-user-id";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
String get uid => throw UnimplementedError();
@override
String get uid => "demo-user-id";

@@ -19,12 +20,14 @@ class MyApp extends StatelessWidget {
final FirebaseAuth firebaseAuth;
final FirebaseFirestore firestore;
final FirebaseMessaging messaging;
final DemoManager demoManager;
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

DemoManager initialization is problematic

Multiple instances of DemoManager are being created despite singleton pattern:

  • Main initialization: main.dart:final demoManager = DemoManager()
  • Widget initialization: phone_input_page.dart:demoManager = DemoManager()

This breaks the singleton pattern and can lead to inconsistent demo state across the app.

🔗 Analysis chain

Verify DemoManager initialization

DemoManager is injected into multiple repositories but its initialization state isn't verified.

Also applies to: 30-30, 47-47, 56-56, 62-62, 68-68


Let me check the complete implementation and usage of DemoManager to verify its initialization pattern and potential issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check DemoManager initialization
rg -A 5 "class DemoManager" 
rg "DemoManager\(" 

Length of output: 607


Script:

#!/bin/bash
# Check DemoManager singleton implementation and initialization
ast-grep --pattern 'class DemoManager {
  $$$
}'

# Check where DemoManager is used
rg -A 2 "DemoManager" 

Length of output: 2548

@@ -1,4 +1,5 @@
import "package:app/core/cubits/signup/signup_cubit.dart";
import "package:app/demo_manager.dart";
Copy link

@coderabbitai coderabbitai bot Dec 6, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use dependency injection for DemoManager

Creating a new instance of DemoManager could lead to inconsistent demo state. Consider injecting it through the constructor or using a dependency injection framework.

class _PhoneInputPageState extends State<PhoneInputPage> {
  late final RoundedLoadingButtonController btnController;
  late final TextEditingController phoneNumberController;
  late PhoneNumber number;
-  late DemoManager demoManager;
+  final DemoManager demoManager;

+  _PhoneInputPageState({required this.demoManager});

  @override
  void initState() {
    super.initState();
    btnController = RoundedLoadingButtonController();
    phoneNumberController = TextEditingController();
    number = PhoneNumber(isoCode: "SL");
-    demoManager = DemoManager();
  }

Also applies to: 22-22, 30-30

Copy link

Choose a reason for hiding this comment

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

@KarinBerg, to fix this issue, you can inject the existing instance of DemoManager instead of creating a new one. Here's how you can modify your code:

  1. Update the PhoneInputPage constructor to accept DemoManager:

    class PhoneInputPage extends StatefulWidget {
      final DemoManager demoManager;
    
      const PhoneInputPage({super.key, required this.demoManager});
    
      @override
      State<PhoneInputPage> createState() => _PhoneInputPageState();
    }
  2. Modify _PhoneInputPageState to use the DemoManager from the widget:

    class _PhoneInputPageState extends State<PhoneInputPage> {
      late final RoundedLoadingButtonController btnController;
      late final TextEditingController phoneNumberController;
      late PhoneNumber number;
    
      @override
      void initState() {
        super.initState();
        btnController = RoundedLoadingButtonController();
        phoneNumberController = TextEditingController();
        number = PhoneNumber(isoCode: "SL");
        // Remove the line where you create a new instance of DemoManager
        // demoManager = DemoManager();
      }
    
      @override
      Widget build(BuildContext context) {
        // Access DemoManager via widget.demoManager
        // ...
      }
    }
  3. Pass the existing DemoManager instance when creating PhoneInputPage:

    // Wherever you instantiate PhoneInputPage
    PhoneInputPage(demoManager: demoManager);

Alternatively, if you're using a state management solution like Provider, you can provide DemoManager higher up in the widget tree and access it using:

final demoManager = Provider.of<DemoManager>(context);

This ensures that you're using the same instance of DemoManager throughout your application and avoids inconsistent states.

Copy link
Contributor

Choose a reason for hiding this comment

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

AI is not right about the second instance. But I agree with the AI that injecting the DemoManage instance would be cleaner and more bullet-proofed.

Copy link

Choose a reason for hiding this comment

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

@KarinBerg, understood. I'm glad you agree that injecting the DemoManager instance would be cleaner and more bullet-proofed.


✏️ Learnings added
Learnt from: KarinBerg
PR: socialincome-san/public#960
File: recipients_app/lib/view/widgets/welcome/phone_input_page.dart:2-2
Timestamp: 2024-12-07T14:27:34.302Z
Learning: In `PhoneInputPage`, creating a new instance of `DemoManager` does not cause inconsistent demo state in this application.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Demo manager is singleton. I'm not sure we really want and can provide it properly. I hate to say that but I don't "feel" the DI in our project at all still 😄

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +51 to +62
Align(
alignment: Alignment.topRight,
child: SafeArea(
child: ButtonSmall(
onPressed: () {
demoManager.isDemoEnabled = true;
},
label: localizations.demoCta,
buttonType: ButtonSmallType.outlined,
),
),
),
Copy link

@coderabbitai coderabbitai bot Dec 6, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance demo mode toggle UX

The demo button lacks visual feedback and the ability to disable demo mode once enabled.

 Align(
   alignment: Alignment.topRight,
   child: SafeArea(
     child: ButtonSmall(
       onPressed: () {
-        demoManager.isDemoEnabled = true;
+        setState(() {
+          demoManager.isDemoEnabled = !demoManager.isDemoEnabled;
+        });
       },
-      label: localizations.demoCta,
+      label: demoManager.isDemoEnabled 
+        ? localizations.demoDisable 
+        : localizations.demoCta,
       buttonType: ButtonSmallType.outlined,
     ),
   ),
 ),

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

Choose a reason for hiding this comment

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

Because the user sees this page exactly once, we do not need this improvement from my point of view.

Copy link

Choose a reason for hiding this comment

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

@KarinBerg, understood. Thank you for the clarification.


✏️ Learnings added
Learnt from: KarinBerg
PR: socialincome-san/public#960
File: recipients_app/lib/view/widgets/welcome/phone_input_page.dart:51-62
Timestamp: 2024-12-07T14:29:17.686Z
Learning: In `phone_input_page.dart`, enhancements to the demo button UX are unnecessary because the user sees this page only once.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


@override
CollectionReference<Map<String, dynamic>> collection(String collectionPath) {
// TODO: implement collection
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO comments should be deleted if they are not needed.

Copy link
Contributor

@KarinBerg KarinBerg left a comment

Choose a reason for hiding this comment

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

See comments

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
recipients_app/lib/data/repositories/organization_repository.dart (1)

9-10: 🛠️ Refactor suggestion

Consider eager initialization in constructor

Late initialization of data sources could lead to initialization errors.

-  late OrganizationDataSource remoteDataSource = OrganizationRemoteDataSource(firestore: firestore);
-  late OrganizationDataSource demoDataSource = OrganizationDemoDataSource();
+  final OrganizationDataSource remoteDataSource;
+  final OrganizationDataSource demoDataSource;
+
+  OrganizationRepository({
+    required this.firestore,
+    required this.demoManager,
+  }) : 
+    remoteDataSource = OrganizationRemoteDataSource(firestore: firestore),
+    demoDataSource = OrganizationDemoDataSource();
recipients_app/lib/core/cubits/auth/auth_cubit.dart (1)

26-30: 🛠️ Refactor suggestion

Extract duplicated organization fetch logic

Organization fetching logic is duplicated between authStateChanges listener and init method.

Extract into a private method as suggested in previous review.

Also applies to: 67-68

recipients_app/lib/data/datasource/remote/user_remote_data_source.dart (3)

29-36: ⚠️ Potential issue

Add phone number validation.

The phone number parsing could throw exceptions with invalid input.

Apply this diff:

 final phoneNumber = firebaseUser.phoneNumber ?? "";
+if (phoneNumber.length <= 1) {
+  return null;
+}

43-43: ⚠️ Potential issue

Fix List access.

The firstOrNull method usage was previously flagged as incorrect.

Apply this diff:

-final userSnapshot = matchingUsers.docs.firstOrNull;
+final userSnapshot = matchingUsers.docs.isNotEmpty ? matchingUsers.docs.first : null;

74-76: ⚠️ Potential issue

Use correct parameter name in timeout callback.

The parameter should be named 'verificationId' for clarity.

Apply this diff:

-codeAutoRetrievalTimeout: (e) {
+codeAutoRetrievalTimeout: (verificationId) {
   log("auto-retrieval timeout");
 },
🧹 Nitpick comments (6)
recipients_app/lib/data/repositories/payment_repository.dart (3)

9-10: Consider initializing data sources in constructor

Late initialization could lead to runtime errors if accessed before initialization. Consider moving these initializations to the constructor.

 class PaymentRepository {
-  late PaymentDataSource remoteDataSource = PaymentRemoteDataSource(firestore: firestore);
-  late PaymentDataSource demoDataSource = PaymentDemoDataSource();
+  final PaymentDataSource remoteDataSource;
+  final PaymentDataSource demoDataSource;

20-20: Consider adding error handling for data source selection

While the selection logic is clean, consider adding error handling for cases where data sources might not be properly initialized.

-  PaymentDataSource get _activeDataSource => demoManager.isDemoEnabled ? demoDataSource : remoteDataSource;
+  PaymentDataSource get _activeDataSource {
+    try {
+      return demoManager.isDemoEnabled ? demoDataSource : remoteDataSource;
+    } catch (e) {
+      throw StateError('Data source not properly initialized: $e');
+    }
+  }

22-48: Add error handling and logging to repository methods

While the delegation to data sources is clean, consider adding error handling and logging for better debugging and error recovery.

Example for fetchPayments:

   Future<List<SocialIncomePayment>> fetchPayments({
     required String recipientId,
   }) async {
+    try {
       return _activeDataSource.fetchPayments(recipientId: recipientId);
+    } catch (e) {
+      log.error('Failed to fetch payments for recipient $recipientId', e);
+      throw PaymentRepositoryException('Failed to fetch payments', e);
+    }
   }
recipients_app/lib/data/repositories/user_repository.dart (1)

12-13: Consider moving data source initialization to constructor

Late initialization of data sources could lead to runtime errors if accessed before initialization. Consider moving the initialization to the constructor for better safety.

 class UserRepository {
-  late UserDataSource remoteDataSource = UserRemoteDataSource(firestore: firestore, firebaseAuth: firebaseAuth);
-  late UserDataSource demoDataSource = UserDemoDataSource();
+  final UserDataSource remoteDataSource;
+  final UserDataSource demoDataSource;

   UserRepository({
     required this.firestore,
     required this.firebaseAuth,
-    required this.demoManager,
+    required this.demoManager,
+  }) : remoteDataSource = UserRemoteDataSource(firestore: firestore, firebaseAuth: firebaseAuth),
+       demoDataSource = UserDemoDataSource();
recipients_app/lib/data/datasource/remote/user_remote_data_source.dart (2)

45-49: Remove or properly document commented code.

Either remove the commented code or convert it to a proper documentation comment explaining why this approach was considered and rejected.


70-70: Consider making timeout duration configurable.

The 60-second timeout is hardcoded. Consider making it a configurable parameter.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88e4263 and 99ebeeb.

📒 Files selected for processing (28)
  • recipients_app/lib/core/cubits/auth/auth_cubit.dart (2 hunks)
  • recipients_app/lib/core/cubits/auth/auth_state.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/demo_user.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/no_op_document_reference.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/organization_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/payment_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/survey_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/demo/user_demo_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/organization_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/payment_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/organization_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/payment_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/survey_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/remote/user_remote_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/survey_data_source.dart (1 hunks)
  • recipients_app/lib/data/datasource/user_data_source.dart (1 hunks)
  • recipients_app/lib/data/models/recipient.dart (0 hunks)
  • recipients_app/lib/data/repositories/organization_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/payment_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/survey_repository.dart (1 hunks)
  • recipients_app/lib/data/repositories/user_repository.dart (1 hunks)
  • recipients_app/lib/demo_manager.dart (1 hunks)
  • recipients_app/lib/l10n/app_en.arb (1 hunks)
  • recipients_app/lib/l10n/app_kri.arb (1 hunks)
  • recipients_app/lib/main.dart (3 hunks)
  • recipients_app/lib/my_app.dart (4 hunks)
  • recipients_app/lib/view/pages/terms_and_conditions_page.dart (2 hunks)
  • recipients_app/lib/view/widgets/welcome/phone_input_page.dart (4 hunks)
💤 Files with no reviewable changes (1)
  • recipients_app/lib/data/models/recipient.dart
🚧 Files skipped from review as they are similar to previous changes (21)
  • recipients_app/lib/data/datasource/organization_data_source.dart
  • recipients_app/lib/data/datasource/survey_data_source.dart
  • recipients_app/lib/main.dart
  • recipients_app/lib/l10n/app_kri.arb
  • recipients_app/lib/l10n/app_en.arb
  • recipients_app/lib/my_app.dart
  • recipients_app/lib/data/datasource/demo/organization_demo_data_source.dart
  • recipients_app/lib/data/datasource/demo/survey_demo_data_source.dart
  • recipients_app/lib/core/cubits/auth/auth_state.dart
  • recipients_app/lib/data/datasource/demo/no_op_document_reference.dart
  • recipients_app/lib/view/widgets/welcome/phone_input_page.dart
  • recipients_app/lib/data/datasource/remote/survey_remote_data_source.dart
  • recipients_app/lib/demo_manager.dart
  • recipients_app/lib/data/datasource/demo/demo_user.dart
  • recipients_app/lib/data/datasource/payment_data_source.dart
  • recipients_app/lib/data/datasource/remote/organization_remote_data_source.dart
  • recipients_app/lib/data/repositories/survey_repository.dart
  • recipients_app/lib/data/datasource/remote/payment_remote_data_source.dart
  • recipients_app/lib/data/datasource/user_data_source.dart
  • recipients_app/lib/data/datasource/demo/payment_demo_data_source.dart
  • recipients_app/lib/data/datasource/demo/user_demo_data_source.dart
🔇 Additional comments (12)
recipients_app/lib/data/repositories/organization_repository.dart (2)

20-20: Clean data source selection logic

The active data source selection is clear and maintainable.


24-25: Good delegation to data source

Clean delegation to the active data source implementation.

recipients_app/lib/core/cubits/auth/auth_cubit.dart (1)

36-38: Proper state emission with organization

State emission correctly includes the organization data.

recipients_app/lib/data/repositories/payment_repository.dart (2)

1-5: LGTM! Clean import organization

The imports are well-structured and properly segregated between data sources, models, and external dependencies.


12-18: LGTM! Well-structured constructor

Properties are clearly defined with appropriate required parameters.

recipients_app/lib/view/pages/terms_and_conditions_page.dart (2)

3-3: Import looks good
No issues found with the newly added import statement for DemoManager.


96-96: Conditional label assignment is correct
The ternary condition cleanly switches between the demo and normal account creation texts. This is straightforward and should serve the intended use case effectively.

recipients_app/lib/data/repositories/user_repository.dart (2)

19-25: Well-structured data source switching implementation

The getter provides clean encapsulation of the data source switching logic.


50-78: Clean implementation of repository methods

The methods properly delegate to the active data source and maintain clean separation of concerns. The signOut method correctly handles demo mode cleanup.

recipients_app/lib/data/datasource/remote/user_remote_data_source.dart (3)

1-16: Clean implementation of class structure and dependencies.

The class follows good practices with proper dependency injection and clear interface implementation.


18-22: Well-implemented Firebase Auth integration.

Clean implementation of auth state management methods.


80-84: Clean implementation of auth operations.

Direct and appropriate use of Firebase Auth methods.

Comment on lines +27 to +47
Stream<User?> authStateChanges() {
final StreamController<User?> authStateController = StreamController();
StreamSubscription<User?>? authStateSubscription;

authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});

demoManager.isDemoEnabledStream.listen((isDemoMode) {
authStateSubscription?.cancel();

authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});

authStateController.onCancel = () {
authStateSubscription?.cancel();
};
});

return authStateController.stream;
Copy link

@coderabbitai coderabbitai bot Jan 2, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add proper stream controller disposal

While subscription cleanup is handled well, the stream controller itself should be closed when no longer needed.

 Stream<User?> authStateChanges() {
   final StreamController<User?> authStateController = StreamController();
   StreamSubscription<User?>? authStateSubscription;

   authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
     authStateController.add(authState);
   });

   demoManager.isDemoEnabledStream.listen((isDemoMode) {
     authStateSubscription?.cancel();

     authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
       authStateController.add(authState);
     });

     authStateController.onCancel = () {
       authStateSubscription?.cancel();
+      authStateController.close();
     };
   });

   return authStateController.stream;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Stream<User?> authStateChanges() {
final StreamController<User?> authStateController = StreamController();
StreamSubscription<User?>? authStateSubscription;
authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});
demoManager.isDemoEnabledStream.listen((isDemoMode) {
authStateSubscription?.cancel();
authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});
authStateController.onCancel = () {
authStateSubscription?.cancel();
};
});
return authStateController.stream;
Stream<User?> authStateChanges() {
final StreamController<User?> authStateController = StreamController();
StreamSubscription<User?>? authStateSubscription;
authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});
demoManager.isDemoEnabledStream.listen((isDemoMode) {
authStateSubscription?.cancel();
authStateSubscription = _activeDataSource.authStateChanges().listen((authState) {
authStateController.add(authState);
});
authStateController.onCancel = () {
authStateSubscription?.cancel();
authStateController.close();
};
});
return authStateController.stream;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we really close it? We want to maintain the controller here as it will in the end get the other (switched) authStateSubscribtion.

Copy link

Choose a reason for hiding this comment

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

@MDikkii, you can indeed keep the StreamController open if you want to maintain the same stream across multiple state changes. Once you close a StreamController, it won’t accept new subscriptions or data events, which can be counterproductive if you still need to switch between demo and remote modes on the same stream.

However, it’s generally considered good practice to close the controller when it’s no longer needed (for instance, when the object or widget that owns it is disposed). So if the controller lives for the duration of the entire app session, leaving it open may be acceptable. If it’s tied to a shorter lifecycle, make sure to close it at the appropriate time to avoid potential leaks.

Comment on lines +87 to +91
Future<void> updateRecipient(Recipient recipient) async {
final updatedRecipient = recipient.copyWith(updatedBy: recipient.userId);

return firestore.collection(recipientCollection).doc(recipient.userId).update(updatedRecipient.toJson());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add userId validation before Firestore update.

Validate that recipient.userId is not null before using it in the Firestore update operation.

Apply this diff:

 @override
 Future<void> updateRecipient(Recipient recipient) async {
+  if (recipient.userId == null || recipient.userId.isEmpty) {
+    throw ArgumentError('recipient.userId cannot be null or empty');
+  }
   final updatedRecipient = recipient.copyWith(updatedBy: recipient.userId);
   return firestore.collection(recipientCollection).doc(recipient.userId).update(updatedRecipient.toJson());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<void> updateRecipient(Recipient recipient) async {
final updatedRecipient = recipient.copyWith(updatedBy: recipient.userId);
return firestore.collection(recipientCollection).doc(recipient.userId).update(updatedRecipient.toJson());
}
Future<void> updateRecipient(Recipient recipient) async {
if (recipient.userId == null || recipient.userId.isEmpty) {
throw ArgumentError('recipient.userId cannot be null or empty');
}
final updatedRecipient = recipient.copyWith(updatedBy: recipient.userId);
return firestore.collection(recipientCollection).doc(recipient.userId).update(updatedRecipient.toJson());
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mobile Issues concerning Mobile App
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants