-
-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Skipped Deployment
|
📝 WalkthroughWalkthroughThe 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 Changes
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
73c8b74
to
88e4263
Compare
There was a problem hiding this 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 variablesThe variables
remoteDataSource
anddemoDataSource
are initialized at declaration, so thelate
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 variablesThe variables
remoteDataSource
anddemoDataSource
are initialized at declaration, so thelate
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 listenerAssigning
authStateController.onCancel
inside theisDemoEnabledStream
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 dataThe 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 constantThe
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 sourcesLate 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 formatAdd 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: UUIDAlso applies to: 18-21, 23-28, 30-35
recipients_app/lib/data/datasource/demo/no_op_document_reference.dart (3)
3-4
: Add class documentationAdd 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 messagesReplace 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 formattingRemove 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 numbersHardcoded phone numbers should be moved to a configuration file for easier maintenance and testing.
42-51
: Implement auth flow demoThe 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 dataRandom payment counts could make testing unpredictable. Consider using fixed counts or making them configurable.
31-33
: Parameterize payment constantsCurrency 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 methodsMost 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 creationMultiple 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
📒 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.
await firestore.collection(recipientCollection).doc(recipientId).collection(paymentCollection).get(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
final userSnapshot = matchingUsers.docs.firstOrNull; | |
final userSnapshot = matchingUsers.docs.isNotEmpty ? matchingUsers.docs.first : null; |
codeAutoRetrievalTimeout: (e) { | ||
log("auto-retrieval timeout"); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
codeAutoRetrievalTimeout: (e) { | |
log("auto-retrieval timeout"); | |
}, | |
codeAutoRetrievalTimeout: (verificationId) { | |
log("auto-retrieval timeout"); | |
}, |
final phoneNumber = firebaseUser.phoneNumber ?? ""; | ||
|
||
final matchingUsers = await firestore | ||
.collection(recipientCollection) | ||
.where( | ||
"mobile_money_phone.phone", | ||
isEqualTo: int.parse(phoneNumber.substring(1)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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, | ||
}); | ||
} |
There was a problem hiding this comment.
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:
- Documentation for the class and methods
- 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.
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, | |
}); | |
} |
Organization? organization; | ||
|
||
if (recipient?.organizationRef != null) { | ||
organization = await organizationRepository.fetchOrganization(recipient!.organizationRef!); | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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. :)
There was a problem hiding this comment.
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!
@override | ||
String get uid => throw UnimplementedError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@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; |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
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:
-
Update the
PhoneInputPage
constructor to acceptDemoManager
:class PhoneInputPage extends StatefulWidget { final DemoManager demoManager; const PhoneInputPage({super.key, required this.demoManager}); @override State<PhoneInputPage> createState() => _PhoneInputPageState(); }
-
Modify
_PhoneInputPageState
to use theDemoManager
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 // ... } }
-
Pass the existing
DemoManager
instance when creatingPhoneInputPage
:// 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 😄
There was a problem hiding this comment.
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!
Align( | ||
alignment: Alignment.topRight, | ||
child: SafeArea( | ||
child: ButtonSmall( | ||
onPressed: () { | ||
demoManager.isDemoEnabled = true; | ||
}, | ||
label: localizations.demoCta, | ||
buttonType: ButtonSmallType.outlined, | ||
), | ||
), | ||
), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments
88e4263
to
99ebeeb
Compare
There was a problem hiding this 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 suggestionConsider 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 suggestionExtract duplicated organization fetch logic
Organization fetching logic is duplicated between
authStateChanges
listener andinit
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 issueAdd 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 issueFix 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 issueUse 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 constructorLate 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 selectionWhile 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 methodsWhile 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 constructorLate 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
📒 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 logicThe active data source selection is clear and maintainable.
24-25
: Good delegation to data sourceClean delegation to the active data source implementation.
recipients_app/lib/core/cubits/auth/auth_cubit.dart (1)
36-38
: Proper state emission with organizationState emission correctly includes the organization data.
recipients_app/lib/data/repositories/payment_repository.dart (2)
1-5
: LGTM! Clean import organizationThe imports are well-structured and properly segregated between data sources, models, and external dependencies.
12-18
: LGTM! Well-structured constructorProperties 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 implementationThe getter provides clean encapsulation of the data source switching logic.
50-78
: Clean implementation of repository methodsThe 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.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Future<void> updateRecipient(Recipient recipient) async { | ||
final updatedRecipient = recipient.copyWith(updatedBy: recipient.userId); | ||
|
||
return firestore.collection(recipientCollection).doc(recipient.userId).update(updatedRecipient.toJson()); | ||
} |
There was a problem hiding this comment.
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.
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()); | |
} |
Added
dataSource
concept and provided bothdemo
andremote
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
DemoManager
.Enhancements
Bug Fixes
Documentation