Feature/app-performance-retention-#52#60
Conversation
…rofile loading and editing functionality
… favorite cuisine
…hensive registration
…handling and additional user profile fields
…al and streamline address loading logic
…turn a single address instead of a list
…dress instead of a list
…nd update saveAddress method for user profile integration
…nto service locator
…ecessary alignment in profile screen
…ource, add statusCode and errorType to ServerFailure, and improve user ID retrieval in AddressScreen
…lity and equality checks, update event constructor for named parameters, and add connectivity_plus dependency
…andling, enhance UserProfile validation, and improve error messages in ProfileBloc
…d parameters for improved logging, enhancing clarity and maintainability
…actor logEvent method to accept structured parameters, and update app initialization to include analytics
…me logging, add logUserSession and logLoadingTime methods in HomeScreen and main.dart
…ns, login times, and error tracking using Firebase Analytics and Crashlytics
…ent with logLoadingTime and logUserSessionStart for improved event tracking
…logUserRetention method to track days since last visit
…ved loading time tracking
…analytics logging
…ctivity handling in ProfileBloc
…onnectivity checks and improve profile loading logic
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 Walkthrough📝 Walkthrough📝 WalkthroughWalkthroughThe pull request introduces significant updates across multiple files to enhance user profile management and analytics tracking in the EcoBites application. Key changes include the addition of a Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
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 (
|
There was a problem hiding this comment.
Actionable comments posted: 40
🧹 Outside diff range and nitpick comments (52)
lib/features/address/domain/repositories/address_repository.dart (1)
1-8: Consider adding data validation and access control.Since this repository handles user-specific address data and is part of Firebase integration:
- Consider adding input validation for the userId parameter to prevent unauthorized access
- Document any assumptions about the format of userId (e.g., if it should be a Firebase UID)
- Consider adding repository-level access control checks
lib/features/profile/domain/repositories/user_profile_repository.dart (1)
5-8: LGTM! Well-designed repository interface.The interface follows clean architecture principles with:
- Clear separation of concerns
- Proper error handling using Either type
- Appropriate use of nullable types
- Consistent async operations
Consider documenting the following aspects to improve maintainability:
- Add documentation comments explaining the expected behavior of each method
- Document potential error cases that might result in ServerFailure
- Consider adding method-level annotations for tracking analytics events, given that this PR introduces Firebase Analytics
Example documentation structure:
/// Repository interface for managing user profiles abstract class UserProfileRepository { /// Fetches a user profile by their unique identifier /// /// Parameters: /// - uid: The unique identifier of the user /// /// Returns: /// - Right(UserProfile?): The user profile if found, null if not exists /// - Left(ServerFailure): If there's an error communicating with the server Future<Either<ServerFailure, UserProfile?>> fetchUserProfile(String uid); /// Updates an existing user profile /// /// Parameters: /// - profile: The updated user profile data /// /// Returns: /// - Right(Unit): If the update was successful /// - Left(ServerFailure): If there's an error communicating with the server Future<Either<ServerFailure, Unit>> updateUserProfile(UserProfile profile); }lib/features/profile/domain/usecases/update_user_profile_usecase.dart (1)
4-7: Add class documentation.Consider adding documentation to describe the purpose and usage of this use case. This helps maintain clean architecture principles by clearly stating the use case's role in the domain layer.
+/// Updates a user's profile through the user profile repository. +/// +/// This use case follows clean architecture principles and serves as an intermediary +/// between the presentation layer and the repository layer for profile updates. class UpdateUserProfileUseCase {lib/features/address/presentation/bloc/address_event.dart (1)
Line range hint
1-20: Consider adding documentation for the event classes.While the code structure is clean, adding documentation comments (///) for each event class would improve maintainability by clearly stating the purpose and expected payload of each event.
Example improvement:
abstract class AddressEvent {} +/// Event to save a user's address to the remote data source +/// +/// Requires [address] containing the address details and [userId] for identification class SaveAddress extends AddressEvent { SaveAddress(this.address, {required this.userId}); final Address address; final String userId; }lib/core/error/failures.dart (1)
14-23: Add documentation and consider additional improvements.The
ServerFailureimplementation looks good and provides valuable error details. Consider these enhancements:+/// Represents a failure that occurs during server communication. +/// +/// Use this when the server returns an error response (e.g., 4xx, 5xx). +/// For connectivity issues, use [NetworkFailure] instead. +/// +/// Example: +/// ```dart +/// ServerFailure('Invalid request', statusCode: 400, errorType: 'BAD_REQUEST') +/// ``` class ServerFailure extends Failure { + /// Common error types for consistent error handling + static const String BAD_REQUEST = 'BAD_REQUEST'; + static const String NOT_FOUND = 'NOT_FOUND'; + static const String SERVER_ERROR = 'SERVER_ERROR'; const ServerFailure( super.message, { + /// HTTP status code (e.g., 400, 404, 500) this.statusCode, + /// Type of error for categorization (e.g., BAD_REQUEST, NOT_FOUND) this.errorType, }); final int? statusCode; final String? errorType; + + /// Returns true if this is a client error (4xx status code) + bool get isClientError => statusCode != null && statusCode! >= 400 && statusCode! < 500; + + /// Returns true if this is a server error (5xx status code) + bool get isServerError => statusCode != null && statusCode! >= 500 && statusCode! < 600; }lib/features/profile/domain/usecases/fetch_user_profile_usecase.dart (1)
6-9: Add class and constructor documentation.While the implementation is clean, adding documentation would improve code maintainability. Consider adding:
- Class documentation explaining the purpose and responsibility of this use case
- Constructor documentation describing the repository dependency
+/// Use case responsible for fetching a user's profile information. class FetchUserProfileUseCase { + /// Creates a new instance of [FetchUserProfileUseCase]. + /// + /// Requires a [UserProfileRepository] for accessing user profile data. FetchUserProfileUseCase(this.repository); final UserProfileRepository repository;lib/features/address/domain/usecases/fetch_user_addresses_usecase.dart (1)
12-14: Enhance method documentation.While the implementation is correct, consider improving the documentation to explain:
- Why the address might be null
- The business logic behind single address management
- // Update to return a single Address, not a list. + /// Fetches the user's primary address. + /// Returns null if the user hasn't set an address yet. + /// This aligns with the single-address management approach where each user + /// maintains only one active address at a time.lib/features/auth/domain/repositories/auth_repository.dart (1)
4-4: Consider domain boundary implications.The import of
CuisineTypefrom the food domain creates a dependency between authentication and food domains. While this works for the current use case, consider if this coupling aligns with your domain boundaries. Alternative approaches could include:
- Moving
CuisineTypeto a shared domain model- Using a more generic type (e.g., string enum) in the auth domain
lib/features/profile/presentation/bloc/profile_event.dart (2)
1-12: LGTM! Base class implementation follows best practices.The
ProfileEventclass correctly implementsEquatableand is marked as@immutable, which is essential for bloc event classes.Consider removing the extra blank line at line 5 to maintain consistent spacing.
20-30: Enhance parameter validation and make the class const.While the implementation is generally good, consider these improvements:
- Add null checks for parameters
- Validate updatedProfile
- Make the class const for better performance
Apply this diff to implement the suggestions:
class UpdateProfileEvent extends ProfileEvent { - UpdateProfileEvent({ + const UpdateProfileEvent({ required this.userId, required this.updatedProfile, - }) : assert(userId.isNotEmpty, 'userId cannot be empty'); + }) : assert(userId.isNotEmpty, 'UpdateProfileEvent requires a non-empty userId'), + assert(updatedProfile != null, 'UpdateProfileEvent requires a non-null updatedProfile'); final String userId; final UserProfile updatedProfile;lib/features/auth/presentation/bloc/auth_event.dart (1)
27-32: Consider implementing a builder patternThe class has a large number of required parameters which could make it cumbersome to use and maintain.
Consider implementing a builder pattern or breaking this into smaller, more focused events:
class SignUpBuilder { String? email; String? password; String? name; String? surname; String? citizenId; String? phone; DateTime? birthDate; CuisineType? favoriteCuisine; SignUpBuilder setEmail(String email) { this.email = email; return this; } // ... similar methods for other fields SignUpRequested build() { assert(email != null && password != null /* ... */); return SignUpRequested( email: email!, password: password!, name: name!, surname: surname!, citizenId: citizenId!, phone: phone!, birthDate: birthDate!, favoriteCuisine: favoriteCuisine!, ); } }lib/main.dart (1)
11-11: Consider moving analytics instance to dependency injection.While the global analytics instance works, consider moving it to the dependency injection container (
injection_container.dart) for better testability and maintainability. This would align with the existing DI pattern in the codebase.-final FirebaseAnalytics analytics = FirebaseAnalytics.instance; // In injection_container.dart: +void setupAnalytics() { + sl.registerLazySingleton<FirebaseAnalytics>(() => FirebaseAnalytics.instance); +}lib/features/profile/presentation/bloc/profile_state.dart (4)
5-5: Translate comment to English for consistency.The codebase should maintain consistent use of English in comments.
-// Definimos los diferentes estados que puede tener el perfil +// Define the different states that a profile can have
6-6: Consider using sealed class or freezed package.For better state handling and pattern matching, consider:
- Using Dart 3's sealed class feature:
sealed class ProfileState {}
- Or using the freezed package for immutable state classes with union types support.
This would provide compile-time exhaustiveness checking when handling states.
12-27: Add copyWith method and consider simplifying equality implementation.While the current implementation is functional, consider these improvements:
- Add a copyWith method for easier state updates
- Consider using Dart's collection package for simpler equality implementation
class ProfileLoaded extends ProfileState { ProfileLoaded(this.profile, {this.isUpdated = false}); final UserProfile profile; final bool isUpdated; + ProfileLoaded copyWith({ + UserProfile? profile, + bool? isUpdated, + }) { + return ProfileLoaded( + profile ?? this.profile, + isUpdated: isUpdated ?? this.isUpdated, + ); + } @override - bool operator ==(Object other) => - identical(this, other) || - other is ProfileLoaded && - runtimeType == other.runtimeType && - profile == other.profile && - isUpdated == other.isUpdated; + bool operator ==(Object other) => + identical(this, other) || + (other is ProfileLoaded && + profile == other.profile && + isUpdated == other.isUpdated); @override int get hashCode => profile.hashCode ^ isUpdated.hashCode; }
29-43: Enhance error handling and debugging support.Consider these improvements to the error state:
- Add structured error information instead of just a message
- Include a toString override for better debugging
class ProfileError extends ProfileState { - ProfileError(this.message); + ProfileError({ + required this.message, + this.code, + this.stackTrace, + }); final String message; + final String? code; + final StackTrace? stackTrace; @override bool operator ==(Object other) => identical(this, other) || (other is ProfileError && - runtimeType == other.runtimeType && - message == other.message); + message == other.message && + code == other.code); @override - int get hashCode => message.hashCode; + int get hashCode => Object.hash(message, code); + + @override + String toString() => 'ProfileError(message: $message, code: $code)'; }lib/features/profile/data/repositories/profile_repository_impl.dart (2)
1-8: Consider removing thealways_specify_typesignore directive.Explicit type specifications enhance code clarity and maintainability. Instead of ignoring the lint rule, consider adding explicit types throughout the file.
-// ignore_for_file: always_specify_types
9-13: Add class-level documentation.Consider adding documentation to describe the purpose of this repository implementation and its role in the application architecture.
+/// Implementation of [UserProfileRepository] that manages user profiles in Firebase Firestore. +/// +/// This repository handles the storage and retrieval of user profile data, including +/// favorite cuisines and diet types. class ProfileRepositoryImpl implements UserProfileRepository {lib/features/auth/domain/usecases/sign_up_usecase.dart (2)
29-47: Add documentation and consider splitting into smaller objects.The SignUpParams class has grown to handle many responsibilities. Consider:
- Adding documentation for each parameter, especially format requirements for citizenId and phone
- Splitting into logical groups (e.g., BasicInfo, ContactInfo, Preferences) to improve maintainability
Example refactor:
+/// Represents user's basic information +class UserBasicInfo extends Equatable { + const UserBasicInfo({ + required this.name, + required this.surname, + required this.birthDate, + }); + + final String name; + final String surname; + final DateTime birthDate; + + @override + List<Object?> get props => [name, surname, birthDate]; +} class SignUpParams extends Equatable { const SignUpParams({ required this.email, required this.password, - required this.name, - required this.surname, + required this.basicInfo, required this.citizenId, required this.phone, - required this.birthDate, required this.favoriteCuisine, }); final String email; final String password; - final String name; - final String surname; + final UserBasicInfo basicInfo; final String citizenId; final String phone; - final DateTime birthDate; final CuisineType favoriteCuisine;
Add validation for sensitive user data at presentation layer
After reviewing the codebase, I can see that validation is implemented in the presentation layer (login_screen.dart) but missing for the sign-up process. The repository directly saves the sensitive data without validation. Consider:
- Add form validation in the sign-up screen similar to the login screen's pattern for:
- citizenId (format/length check)
- phone (proper phone number format)
- birthDate (age restrictions)
- name/surname (non-empty, proper character set)
The login screen already demonstrates a good validation pattern using TextFormField's validator property that can be reused.
🔗 Analysis chain
Line range hint
14-24: Consider adding input validation for sensitive data.The use case is handling sensitive personal information (citizenId, phone) but doesn't perform any validation. Consider adding validation rules or moving them to a separate validation use case to ensure data integrity before reaching the repository.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check if there's any validation logic in the repository or related files rg -A 5 "validate|sanitize|check.*citizenId|check.*phone" lib/features/auth/Length of output: 129
Script:
#!/bin/bash # Let's check for any validation-related code in the auth feature rg -l "validate|sanitize|check" lib/features/auth/ # Check the repository implementation to see how the data is handled fd "auth.*repository.*impl" lib/features/auth/ # Look for any validator classes or utilities fd "validator|validation" lib/features/auth/ # Check the SignUpParams class definition ast-grep --pattern 'class SignUpParams { $$$ }'Length of output: 319
Script:
#!/bin/bash # Let's check the repository implementation to see how the sign-up data is handled cat lib/features/auth/data/repositories/auth_repository_impl.dart # Check the login screen for any validation patterns we might want to reuse cat lib/features/auth/presentation/screens/login_screen.dart # Look for any existing validation patterns in the entire codebase rg "String\?.*validate" lib/Length of output: 12292
lib/core/utils/analytics_service.dart (2)
1-3: Consider adding exports and error handling imports.The analytics service functions are implicitly public. Consider:
- Adding an explicit export statement
- Importing error handling utilities for better error management
+export 'analytics_service.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:meta/meta.dart'; +import '../error/failures.dart';
33-40: Enhance login analytics with additional context.Consider adding more context to login time tracking:
- Login method (email, social, etc.)
- Success/failure status
- Input validation
-Future<void> logLoginTime(int milliseconds) async { +Future<void> logLoginTime({ + required int milliseconds, + required String loginMethod, + required bool success, +}) async { + if (milliseconds < 0) { + throw ArgumentError('Login time cannot be negative'); + } await FirebaseAnalytics.instance.logEvent( name: 'login_time', parameters: <String, Object>{ 'milliseconds': milliseconds, + 'login_method': loginMethod, + 'success': success, }, ); }lib/features/profile/data/models/user_profile_model.dart (3)
1-4: Consider adding Equatable and documentation.The class would benefit from:
- Implementing
Equatablefor proper object comparison in Firebase operations- Adding class-level documentation explaining its purpose and usage
+import 'package:equatable/equatable.dart'; import 'package:eco_bites/features/address/data/models/address_model.dart'; import 'package:eco_bites/features/food/domain/entities/cuisine_type.dart'; +/// Represents a user's profile in the EcoBites application. +/// Contains personal information and preferences of the user. -class UserProfile { +class UserProfile extends Equatable {
34-42: Remove redundant comment and add Equatable implementation.The field declarations look good, but:
- Remove the redundant comment on line 42
- Add Equatable props implementation for proper object comparison
final CuisineType favoriteCuisine; final String diet; - final AddressModel? address; // Optional address field + final AddressModel? address; + + @override + List<Object?> get props => [ + name, + surname, + citizenId, + email, + phone, + birthDate, + favoriteCuisine, + diet, + address, + ];
44-56: Add toString method for better debugging.The toMap implementation looks good. Consider adding a toString method for better debugging and logging capabilities.
}; } + + @override + String toString() => 'UserProfile(' + 'name: $name, ' + 'email: $email, ' + 'phone: $phone, ' + 'birthDate: $birthDate, ' + 'favoriteCuisine: $favoriteCuisine, ' + 'address: $address)'; }lib/features/address/data/datasources/address_remote_data_source.dart (3)
19-19: Translate Spanish comments to English for consistency.- // Aquí aseguramos que el UID sea el mismo que el del usuario registrado + // Ensure the UID matches the registered user's UID
49-55: Add specific error codes for different failure scenarios.Consider using specific error codes to differentiate between different types of failures (e.g., 'address-not-found', 'invalid-user', etc.) instead of reusing the original error code.
if (e is FirebaseException) { throw FirebaseException( message: 'Failed to fetch address: ${e.message}', plugin: 'cloud_firestore', - code: e.code, + code: e.code == 'permission-denied' ? 'address-permission-denied' : + e.code == 'not-found' ? 'address-not-found' : + 'address-fetch-error', ); }
41-46: Optimize Firestore read with exists() check.For cases where we only need to check address existence, consider using
exists()first to avoid fetching the entire document.- final DocumentSnapshot<Map<String, dynamic>> snapshot = - await _firestore.collection('profiles').doc(userId).get(); - - if (snapshot.exists && snapshot.data()?['address'] != null) { - return AddressModel.fromMap(snapshot.data()!['address']); - } + final docRef = _firestore.collection('profiles').doc(userId); + final exists = await docRef.exists(); + + if (!exists) return null; + + final snapshot = await docRef.get(); + final address = snapshot.data()?['address']; + return address != null ? AddressModel.fromMap(address) : null;lib/features/profile/domain/entities/user_profile.dart (1)
40-48: Improve field documentation and type safety.Consider these improvements:
- Add documentation for each field
- Convert dietType to an enum for type safety
Add field documentation and create an enum:
/// Enumeration of supported diet types enum DietType { unknown, vegan, vegetarian, pescatarian, other } class UserProfile extends Equatable { // ... constructor ... /// Unique identifier of the user final String userId; /// User's first name final String name; /// User's last name final String surname; /// Government-issued identification number final String citizenId; /// User's email address final String email; /// Contact phone number final String phone; /// User's date of birth final DateTime birthDate; /// Preferred cuisine type final CuisineType favoriteCuisine; /// Dietary preference/restriction final DietType dietType;lib/app.dart (2)
59-61: LGTM: ProfileBloc provider is properly integratedThe ProfileBloc provider is correctly added to the MultiBlocProvider list and follows the established pattern of using the service locator.
Consider documenting the order of BlocProviders if there are dependencies between them, as the order can matter when blocks depend on each other.
64-66: LGTM: Firebase Analytics observer is properly configuredThe FirebaseAnalyticsObserver is correctly integrated for automatic screen tracking. Consider adding custom analytics events for important user interactions beyond just navigation.
Example tracking points to consider:
- User engagement metrics
- Feature usage statistics
- Error occurrences
- Performance metrics
lib/features/auth/presentation/bloc/auth_bloc.dart (1)
71-76: Document the requirements for new signup parameters.Please add documentation for the new parameters describing:
- Required formats (e.g., phone number format, citizenId format)
- Validation rules
- Whether fields are optional or required
Example documentation:
/// SignUpParams( /// email: Valid email address /// password: Min 8 chars with 1 uppercase, 1 number /// name: Required, 2-50 chars /// surname: Required, 2-50 chars /// citizenId: Required, national ID format XX-XXXX-XXXXX /// phone: Required, format +XX-XXX-XXX-XXXX /// birthDate: Required, must be 18+ years old /// favoriteCuisine: Optional /// )lib/injection_container.dart (1)
Line range hint
181-185: Consider adding Firebase services initialization verification.While the Firebase service registrations are correct, consider adding initialization checks:
- Verify Firebase initialization before registering services
- Consider wrapping Firebase instances in initialization-aware factories
Example improvement:
// Add this before registering Firebase services Future<void> ensureFirebaseInitialized() async { if (!Firebase.apps.isNotEmpty) { await Firebase.initializeApp(); } }lib/features/auth/presentation/screens/register_screen.dart (1)
132-149: Enhance form field components with validation and accessibility.The helper methods could be improved with:
- Support for validation
- Error text display
- Better accessibility labels
Suggested improvements for _buildTextField:
Widget _buildTextField(String label, TextEditingController controller, - {bool obscureText = false,}) { + {bool obscureText = false, + String? Function(String?)? validator, + String? errorText, + String? semanticsLabel,}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: TextField( controller: controller, obscureText: obscureText, + semanticsLabel: semanticsLabel ?? label, decoration: InputDecoration( labelText: label, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), ), filled: true, fillColor: Colors.white, + errorText: errorText, ), ), ); }Also applies to: 180-206
lib/features/address/presentation/screens/address_screen.dart (1)
235-237: Enhance loading indicator UX in the confirm button.While the loading indicator works, the button might change size when switching between text and loader, causing layout shifts.
Consider this improvement:
- child: isLoading - ? const CircularProgressIndicator() - : const Text('Confirm Address'), + child: SizedBox( + height: 20, // Match your text height + child: isLoading + ? const CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation<Color>(Colors.white), + ) + : const Text('Confirm Address'), + ),lib/features/home/presentation/screens/home_screen.dart (1)
Line range hint
1-300: Consider splitting HomeScreen into smaller components.The HomeScreen has grown to handle multiple responsibilities (location tracking, address management, analytics, UI). Consider:
- Extract location tracking into a separate service
- Create dedicated widgets for address management and search
- Move analytics logic to a service layer
This will improve:
- Testability
- Maintainability
- Code reusability
- Separation of concerns
Would you like help creating a detailed refactoring plan?
lib/features/address/data/repositories/address_repository_impl.dart (1)
29-29: Consider extracting the error message 'No internet connection' to a constant.The error message
'No internet connection'is repeated in multiple places. Extracting it to a constant will improve maintainability and make future changes easier.Apply this diff:
+const String _noInternetConnectionMessage = 'No internet connection'; ... - return const Left<Failure, void>(NetworkFailure('No internet connection')); + return const Left<Failure, void>(NetworkFailure(_noInternetConnectionMessage)); ... - return const Left<Failure, Address?>(NetworkFailure('No internet connection')); + return const Left<Failure, Address?>(NetworkFailure(_noInternetConnectionMessage));Also applies to: 46-46
lib/features/address/presentation/bloc/address_bloc.dart (2)
51-56: Use specific exception types in error handlingIn the
catchblock, consider catching specific exceptions or failures rather than a genericException. This will improve error handling and allow for more precise messaging to the user.
49-56: Refactor duplicate error handling logicThe error handling logic in
_onSaveAddressand_onLoadAddressis similar. Consider refactoring this code into a separate method to reduce duplication and improve maintainability.Example refactored method:
void _handleFailure(Failure failure, Emitter<AddressState> emit, String action) { if (failure is ServerFailure) { emit(AddressError('Failed to $action address: Server Error')); } else if (failure is NetworkFailure) { emit(AddressError('Failed to $action address: Network Error')); } else { emit(AddressError('Failed to $action address: ${failure.toString()}')); } }Then update your error handling in both methods:
// In _onSaveAddress: } catch (failure) { _handleFailure(failure as Failure, emit, 'save'); } // In _onLoadAddress: result.fold( (failure) { _handleFailure(failure, emit, 'load'); }, // ... );Also applies to: 75-83
lib/features/profile/presentation/bloc/profile_bloc.dart (2)
41-46: Use a distinct state instead of emitting an error when caching due to no internet.When there is no internet connection and the updated profile is cached for later synchronization, the bloc currently emits a
ProfileErrorstate with a message indicating that the data will be saved when online. Emitting an error state in this expected scenario may not be appropriate. Consider creating a new state, such asProfileCached, to represent this situation more accurately and inform the UI accordingly.This change enhances state management clarity and allows the UI to provide appropriate feedback to the user.
55-57: Enhance exception handling for more informative error messages.In the catch blocks, the emitted
ProfileErrorstates have generic messages. Providing more detailed error information can aid in debugging and improve user experience. Consider including exception details or specific error codes when appropriate.For example, you could adjust the catch blocks to include exception information:
} catch (e) { emit(ProfileError('Failed to update profile: ${e.toString()}')); }Also applies to: 73-75, 100-101
lib/features/auth/data/repositories/auth_repository_impl.dart (1)
63-63: Ensure code comments are in English for consistencyComments on lines 63 and 70 are in Spanish. For consistency across the codebase and to aid team collaboration, please use English for all code comments.
Consider translating the comments:
-// Crear el usuario en Firebase Authentication +// Create the user in Firebase Authentication ... -// Guardar datos adicionales en Firestore +// Save additional user data in FirestoreAlso applies to: 70-70
lib/features/profile/presentation/screens/profile_screen.dart (5)
1-1: Avoid suppressing lint rules; address 'use_build_context_synchronously' and 'unrelated_type_equality_checks'Suppressing lint rules at the file level can hide potential issues and reduce code quality. It's better to resolve the underlying warnings to ensure code correctness and maintainability.
Consider fixing the code to comply with the lint rules and remove the
// ignore_for_filedirective.
74-87: Handle potential state changes appropriately inBlocConsumerIn the
listenerofBlocConsumer, consider handling other potential states, such asProfileLoading, to provide feedback to the user during long operations.You might add a loading indicator or disable inputs while the profile is being updated.
107-107: Correct greeting text: change '¡Hi' to 'Hi'There appears to be an unintended inverted exclamation mark before 'Hi'. Changing it to 'Hi' will correct the greeting.
Apply this diff:
- '¡Hi, ${_nameController.text}!', + 'Hi, ${_nameController.text}!',
138-138: Handle potential parsing exceptions when parsing birth dateParsing the birth date string could throw a
FormatExceptionif the text is not in the expected format. Consider adding error handling to prevent potential crashes.Wrap the parsing in a try-catch block:
DateTime? birthDate; try { birthDate = DateFormat('MM/dd/yyyy').parse(_birthDateController.text); } catch (e) { // Handle the error or assign a default value birthDate = DateTime.now(); // Example default value }And update the
updatedProfileaccordingly.
45-45: Check for accurate type comparisons in conditional statementsThe lint rule
unrelated_type_equality_checkssuggests there may be a type comparison issue. Ensure thatinternetConnectionBloc.stateis being compared to a compatible type.Verify that
DisconnectedInternetis a subtype of the state and adjust the comparison if necessary.lib/features/profile/domain/repositories/profile_repository.dart (5)
24-27: Consider rethrowing original exceptions for better debuggingCatching exceptions and throwing new generic
Exceptions can obscure the original stack trace and make debugging more difficult. Consider rethrowing the original exceptions to preserve the stack trace and exception details.Apply this diff to rethrow the original exceptions:
} on FirebaseException catch (e) { - throw Exception('Failed to fetch profile: $e'); + rethrow; } catch (e) { - throw Exception('Error processing profile data: $e'); + rethrow; }
31-31: Use consistent language in code commentsThe comment on line 31 is in Spanish. To maintain consistency across the codebase, it's recommended to use English for all code comments unless the project standard specifies otherwise.
Apply this diff to translate the comment:
- // Solo actualizamos los campos `favoriteCuisine` y `dietType` + // Only updating the fields `favoriteCuisine` and `dietType`
40-45: Simplify exception handling and preserve stack traceBy catching all exceptions and throwing new ones, the original stack trace is lost, which can hinder debugging. Consider rethrowing the exception to maintain the original stack trace.
Apply this diff:
} catch (e) { - if (e is FirebaseException) { - throw Exception('Failed to save profile: ${e.message}'); - } - throw Exception('Unexpected error while saving profile: $e'); + rethrow; }
67-73: Remove unnecessaryasyncandawaitin synchronous codeThe mapping over
snapshot.docsdoesn't perform any asynchronous operations, so marking the closure asasyncand usingFuture.waitis unnecessary. Simplify the code by removing theasyncmodifier andFuture.wait.Apply this diff to simplify the code:
- return await Future.wait( - snapshot.docs.map((QueryDocumentSnapshot<Map<String, dynamic>> doc) async { - final Map<String, dynamic> data = doc.data(); - return AddressModel.fromMap(data); - }), - ); + return snapshot.docs.map((doc) { + return AddressModel.fromMap(doc.data()); + }).toList();
83-86: Specify concrete types for the update data mapUsing
<Object, Object?>is too general. For clarity and type safety, use<String, dynamic>when updating Firestore documents.Apply this diff:
- .update(<Object, Object?>{ + .update(<String, dynamic>{ 'favoriteCuisine': profile.favoriteCuisine.name, 'dietType': profile.dietType, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (2)
pubspec.lockis excluded by!**/*.lockwidgetbook/pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (34)
lib/app.dart(2 hunks)lib/core/error/failures.dart(1 hunks)lib/core/utils/analytics_service.dart(1 hunks)lib/features/address/data/datasources/address_remote_data_source.dart(3 hunks)lib/features/address/data/repositories/address_repository_impl.dart(1 hunks)lib/features/address/domain/repositories/address_repository.dart(1 hunks)lib/features/address/domain/usecases/fetch_user_addresses_usecase.dart(1 hunks)lib/features/address/presentation/bloc/address_bloc.dart(3 hunks)lib/features/address/presentation/bloc/address_event.dart(1 hunks)lib/features/address/presentation/screens/address_screen.dart(4 hunks)lib/features/auth/data/repositories/auth_repository_impl.dart(3 hunks)lib/features/auth/domain/repositories/auth_repository.dart(2 hunks)lib/features/auth/domain/usecases/sign_up_usecase.dart(3 hunks)lib/features/auth/presentation/bloc/auth_bloc.dart(3 hunks)lib/features/auth/presentation/bloc/auth_event.dart(1 hunks)lib/features/auth/presentation/screens/login_screen.dart(2 hunks)lib/features/auth/presentation/screens/register_screen.dart(5 hunks)lib/features/home/presentation/screens/home_screen.dart(3 hunks)lib/features/profile/data/models/user_profile_model.dart(1 hunks)lib/features/profile/data/repositories/profile_repository_impl.dart(1 hunks)lib/features/profile/domain/entities/user_profile.dart(1 hunks)lib/features/profile/domain/repositories/profile_repository.dart(1 hunks)lib/features/profile/domain/repositories/user_profile_repository.dart(1 hunks)lib/features/profile/domain/usecases/fetch_user_profile_usecase.dart(1 hunks)lib/features/profile/domain/usecases/update_user_profile_usecase.dart(1 hunks)lib/features/profile/presentation/bloc/profile_bloc.dart(1 hunks)lib/features/profile/presentation/bloc/profile_event.dart(1 hunks)lib/features/profile/presentation/bloc/profile_state.dart(1 hunks)lib/features/profile/presentation/screens/profile_screen.dart(1 hunks)lib/features/splash/presentation/screens/splash_screen.dart(3 hunks)lib/injection_container.dart(5 hunks)lib/main.dart(2 hunks)pubspec.yaml(2 hunks)widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift(1 hunks)
🔇 Additional comments (40)
lib/features/address/domain/repositories/address_repository.dart (1)
1-7: LGTM! Clean architecture principles well applied.
The repository interface follows clean architecture principles with proper separation of concerns and dependency rules. The use of Either type for error handling is a solid choice for functional error handling.
lib/features/profile/domain/repositories/user_profile_repository.dart (1)
1-3: LGTM! Well-organized imports.
The imports are clean, necessary, and well-structured, properly separating the external package (dartz) from internal imports.
lib/features/profile/domain/usecases/update_user_profile_usecase.dart (1)
1-2: LGTM! Clean and well-scoped imports.
The imports are specific to the feature module and contain only what's needed.
lib/features/address/presentation/bloc/address_event.dart (1)
6-6: LGTM! The changes align with the new address management system.
The addition of the required userId parameter is a good improvement that:
- Makes the user identification explicit and type-safe
- Supports the transition to single address per user
- Integrates well with Firebase Authentication
Let's verify that all call sites have been updated with the new parameter:
Also applies to: 8-8
✅ Verification successful
All call sites have been updated correctly with the new userId parameter
The verification shows that there is only one call site in lib/features/address/presentation/screens/address_screen.dart line 219, and it correctly uses the new constructor signature with both the address and userId parameters:
context.read<AddressBloc>().add(SaveAddress(address, userId: userId));🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Find all instances where SaveAddress is instantiated to ensure they include userId
# Search for SaveAddress constructor calls
ast-grep --pattern 'SaveAddress($$$)'
# Also search for any remaining references to old constructor
rg 'SaveAddress\([^,]+\)'
Length of output: 228
lib/features/profile/domain/usecases/fetch_user_profile_usecase.dart (2)
1-4: LGTM! Clean and well-organized imports.
The imports are properly organized and include all necessary dependencies for the use case implementation.
11-17: Verify analytics integration requirement.
The PR objectives mention Firebase Analytics integration, but this use case doesn't implement any analytics tracking. Consider tracking profile fetch events for better user behavior insights.
Let's verify if analytics tracking is implemented elsewhere:
Consider injecting an analytics service to track:
- Profile fetch success/failure rates
- Profile fetch latency
- User engagement patterns
lib/features/address/domain/usecases/fetch_user_addresses_usecase.dart (3)
7-8: LGTM! Class renaming and structure are well-implemented.
The transition from plural to singular naming aligns with the PR objective of managing a single address, while maintaining clean architecture principles.
7-22: Verify analytics integration for address operations.
Since this PR includes Firebase Analytics integration, we should ensure address-related events are properly tracked:
#!/bin/bash
# Description: Verify analytics integration for address operations
# Check for analytics event logging in address-related files
rg -g "lib/features/address/**" "logEvent|AnalyticsService|FirebaseAnalytics" -A 3
# Check if the analytics service has address-related event constants defined
rg -g "lib/core/utils/analytics_service.dart" "address" -B 1 -A 118-22: Verify userId handling across the codebase.
The implementation looks good, but let's verify the consistency of userId handling:
✅ Verification successful
userId handling is properly implemented across the address feature
The verification shows consistent userId handling throughout the address feature:
- Data source properly validates userId and uses it for Firestore operations
- Repository implementation correctly passes userId between layers
- Presentation layer safely retrieves userId from preferences and passes it to use cases
- All components using FetchUserAddressParams properly provide the required userId parameter
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify consistent userId handling in address-related files
# Check for any direct Firebase Auth userId access that might bypass this use case
rg -g "lib/features/address/**" "currentUser.*uid" -A 3
# Verify all calls to this use case properly pass the userId
ast-grep --pattern 'FetchUserAddressParams.*userId'
Length of output: 148
Script:
#!/bin/bash
# Let's try a more comprehensive verification approach
# Check for userId usage in address-related files
rg -g "lib/features/address/**" "userId" -A 2
# Find all instances where FetchUserAddress is used
rg -g "lib/**" "FetchUserAddress" -A 2
# Look for the implementation of the use case to verify userId handling
fd "fetch_user_address" --exec cat {}
Length of output: 11503
lib/features/auth/presentation/bloc/auth_event.dart (1)
2-2: LGTM: Import statement is correctly placed
The import for CuisineType is properly added and necessary for the new favoriteCuisine field.
lib/main.dart (3)
4-6: LGTM: Firebase service imports are properly configured.
The necessary Firebase service imports are correctly added and follow Dart's import conventions.
29-29: LGTM: Crashlytics error handling is properly configured.
The global Flutter error handler is correctly set up to report errors to Firebase Crashlytics.
31-31: Verify Firebase services initialization order.
While the Firebase core is initialized, it's important to ensure that Analytics and Crashlytics are properly initialized before use.
✅ Verification successful
Let me check the initialization order more thoroughly by looking at the main.dart file content.
Firebase services initialization order is correct
The initialization order in main.dart is properly implemented:
- Firebase Core is initialized first with
Firebase.initializeApp() - Firebase Performance is enabled after Core initialization
- Firebase Crashlytics error handler is set up before app launch
- Firebase Analytics instance is available globally and used after Core initialization
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for proper Firebase services initialization
# Expected: Find initialization of Firebase services before their usage
# Search for Firebase initialization patterns
echo "Checking Firebase services initialization patterns..."
rg -n "Firebase.*\.instance" --type dart
# Search for await patterns on Firebase initialization
echo "\nChecking for async initialization..."
rg -n "await.*Firebase" --type dart
Length of output: 3424
Script:
#!/bin/bash
# Get the content of main.dart to verify initialization order
cat lib/main.dart
Length of output: 1097
lib/features/profile/presentation/bloc/profile_state.dart (1)
8-10: LGTM!
The initial and loading states are correctly implemented as simple classes.
widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift (2)
21-25: LGTM: Plugin registrations are correctly implemented.
The new plugins are properly registered with the Flutter plugin registry:
- ConnectivityPlusPlugin for network state management
- FLTFirebaseAnalyticsPlugin for analytics tracking
- FLTFirebaseCrashlyticsPlugin for error reporting
Let's verify these plugins are being used in the codebase:
#!/bin/bash
# Description: Check usage of the newly added plugins
echo "Checking Firebase Analytics usage..."
rg "FirebaseAnalytics|logEvent|analytics\." -t dart
echo -e "\nChecking Firebase Crashlytics usage..."
rg "FirebaseCrashlytics|recordError|crashlytics\." -t dart
echo -e "\nChecking Connectivity Plus usage..."
rg "ConnectivityResult|connectivity\." -t dart9-13: LGTM: Required plugin imports are properly added.
The new imports align with the PR objectives for Firebase Analytics integration and error tracking. The addition of connectivity_plus will help with network state management.
Let's verify the plugin dependencies are properly declared in pubspec.yaml:
✅ Verification successful
Let me verify the pubspec.yaml directly since the previous search didn't show the dependencies:
All required plugins are properly declared in pubspec.yaml and actively used in the codebase
The verification confirms that:
connectivity_plus: ^6.1.0is declared and used for network state managementfirebase_analytics: ^11.3.3is declared and integrated across multiple filesfirebase_crashlytics: ^4.1.3is declared and implemented for error tracking- The imports in GeneratedPluginRegistrant.swift are consistent with both the dependencies and their usage in the codebase
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that the imported plugins are declared in pubspec.yaml
rg -A 1 "connectivity_plus:|firebase_analytics:|firebase_crashlytics:" pubspec.yaml
Length of output: 3088
Script:
#!/bin/bash
# Check pubspec.yaml for the required dependencies
cat pubspec.yaml
Length of output: 4009
lib/features/profile/data/repositories/profile_repository_impl.dart (1)
16-17: Verify Firebase collection name consistency.
Ensure that the 'profiles' collection name is consistent with other parts of the codebase and follows the established naming conventions.
✅ Verification successful
Collection name 'profiles' is consistently used across the codebase
The verification confirms that the 'profiles' collection name is used consistently across all Firestore operations in the codebase, specifically in:
- Profile management operations (get, set, update)
- Address-related operations
- Authentication flow
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for all Firestore collection references to ensure consistency
rg "collection\(['\"]profiles['\"]"
Length of output: 1285
lib/core/utils/analytics_service.dart (1)
1-58: Verify Firebase configuration and initialization.
Ensure that Firebase is properly initialized before using these analytics services.
✅ Verification successful
Let me check the Firebase initialization code to ensure it's properly set up.
Firebase is properly configured and initialized
Firebase is correctly set up with:
- Proper initialization in
main.dartusing platform-specific configurations - Configuration files present (
google-services.json) - Required Firebase dependencies in
pubspec.yaml - Platform-specific Firebase options configured in
firebase_options.dart
The analytics services in analytics_service.dart are safe to use.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check Firebase initialization in the app
rg -l "Firebase.initializeApp" --type dart
# Check if Firebase configuration files are present
fd "google-services.json|GoogleService-Info.plist"
# Look for Firebase-related configuration in pubspec.yaml
rg "firebase" pubspec.yaml
Length of output: 338
Script:
#!/bin/bash
# Check Firebase initialization implementation
rg -A 10 "Firebase.initializeApp" lib/main.dart
# Check firebase_options.dart content
cat lib/firebase_options.dart
Length of output: 2702
lib/features/address/data/datasources/address_remote_data_source.dart (2)
6-6: LGTM! Interface change aligns with single address management.
The change from returning a list to a nullable single address model correctly implements the new address management approach.
21-26: Verify the collection structure change impact.
The change from users/{userId}/addresses subcollection to profiles/{userId} document with an address field is significant. Ensure data migration is handled appropriately.
Let's verify the collection structure and existing data:
lib/features/profile/domain/entities/user_profile.dart (2)
50-62: LGTM! Clean and correct implementation.
The toMap method properly handles all field serializations, including the DateTime to Timestamp conversion.
64-76: LGTM! Correct Equatable implementation.
All fields are properly included in the props list, ensuring correct value comparison.
lib/features/splash/presentation/screens/splash_screen.dart (1)
3-3: LGTM: Analytics service import added correctly.
The import aligns with the PR's objective of integrating Firebase Analytics.
lib/app.dart (2)
9-9: LGTM: Import statements are correctly added
The new imports for ProfileBloc and FirebaseAnalytics are properly placed and necessary for the new functionality.
Also applies to: 12-12
18-18: LGTM: Constructor properly integrates FirebaseAnalytics
The analytics integration is properly implemented with required parameter and final field. However, let's verify that all instantiations of EcoBitesApp have been updated with the new required parameter.
Also applies to: 21-21
✅ Verification successful
All EcoBitesApp instantiations properly include the analytics parameter
Verification confirms that the only instantiation of EcoBitesApp (in main.dart) correctly includes both required parameters - appLaunchTime and analytics. No further changes needed.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for EcoBitesApp instantiations to ensure they include the analytics parameter
rg "EcoBitesApp\(" --type dart
Length of output: 222
pubspec.yaml (1)
55-55: LGTM: Meta package addition.
The addition of the meta package with version ^1.15.0 is appropriate and commonly required by Firebase packages.
lib/features/auth/presentation/screens/login_screen.dart (1)
1-1: LGTM: Analytics import properly integrated.
The analytics service import is correctly placed and aligns with the PR's objective of integrating Firebase Analytics.
lib/injection_container.dart (3)
27-31: LGTM! Well-organized profile feature imports.
The new imports follow clean architecture patterns and align with the PR objectives for profile management.
Line range hint 104-126: Verify the impact of switching to single address management.
The change from fetchUserAddressesUseCase to fetchUserAddressUseCase reflects a shift from managing multiple addresses to a single address. This architectural change needs careful verification:
#!/bin/bash
# Check for any remaining plural references to addresses
echo "Checking for plural 'addresses' references in the codebase..."
rg -l "addresses" "lib/features/address"
# Check for potential UI/bloc references to address lists
echo "Checking for array/list handling of addresses..."
ast-grep --pattern 'List<Address' 83-99: Consider adding error handling for profile operations.
While the dependency registration follows clean architecture patterns correctly, consider adding error handling:
- Register failure handlers for profile operations
- Ensure the repository implementation handles Firestore errors appropriately
Let's verify if error handling is implemented in the repository:
lib/features/auth/presentation/screens/register_screen.dart (1)
18-25: LGTM: Controllers are properly managed.
The TextEditingController instances are correctly initialized and properly disposed of, following Flutter best practices for resource management.
Also applies to: 29-35
lib/features/home/presentation/screens/home_screen.dart (1)
76-77: Verify loading time measurement implementation.
The loading time measurement needs verification:
- The
logLoadingTimefunction is not defined in the visible code - Consider error handling for the measurement logic
- Loading time measurement could be affected by other post-frame callbacks
✅ Verification successful
Let me gather more information about the logLoadingTime implementation.
Loading time measurement implementation is properly handled
The verification shows that:
logLoadingTimeis well-implemented inanalytics_service.dartusing Firebase Analytics- The timing measurement is correctly isolated as the first PostFrameCallback in
initState, ensuring accurate measurement - Error handling is inherently managed by Firebase Analytics implementation
While there are multiple PostFrameCallback usages in home_screen.dart, they are properly sequenced with the timing measurement being the first callback, ensuring accurate load time capture.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for logLoadingTime implementation
echo "Searching for logLoadingTime implementation..."
rg "logLoadingTime" --type dart
# Search for other PostFrameCallback usage that might affect timing
echo "Checking other PostFrameCallback usage..."
ast-grep --pattern 'WidgetsBinding.instance.addPostFrameCallback'
Length of output: 3216
Script:
#!/bin/bash
# Check the implementation of logLoadingTime in analytics_service.dart
echo "Checking logLoadingTime implementation..."
cat lib/core/utils/analytics_service.dart
# Check if there are any error handling patterns in home_screen.dart
echo "Checking error handling patterns in home_screen.dart..."
cat lib/features/home/presentation/screens/home_screen.dart
Length of output: 14616
lib/features/address/data/repositories/address_repository_impl.dart (2)
20-32: saveAddress method implementation looks good.
The saveAddress method correctly checks for network connectivity, handles exceptions, and returns the appropriate Either type.
34-47: fetchUserAddress method implementation is correct.
The fetchUserAddress method properly handles the retrieval of the user's address, including the case when no address is found, and handles exceptions appropriately.
lib/features/address/presentation/bloc/address_bloc.dart (4)
15-15: Ensure constructor parameter matches renamed use case
The constructor parameter fetchUserAddressUseCase should match the updated use case name. Confirm that this change aligns with the renamed class.
27-28: Handle potential null user when fetching user ID
While retrieving the current user from Firebase Authentication, ensure that the possibility of currentUser being null is adequately handled. The current implementation gracefully handles this by returning null for the userId.
85-89: Handle null address appropriately
When the fetched address is null, emitting AddressInitial is acceptable. Ensure that this state is correctly handled in the UI to inform the user that no address is available.
41-43: 🛠️ Refactor suggestion
Use specific failure type instead of generic exception
Throwing a generic Exception when the user is not authenticated can make error handling less precise. Consider using a specific AuthenticationFailure or emitting an appropriate error state to enhance error granularity.
Update the code as follows:
if (userId == null) {
- throw Exception('User not authenticated');
+ emit(AddressError('User not authenticated'));
+ return;
}Likely invalid or redundant comment.
lib/features/profile/presentation/screens/profile_screen.dart (2)
89-102: Ensure the UI updates if the profile changes during the session
Currently, the controllers are initialized only once. If the user's profile is updated elsewhere and a new ProfileLoaded state is emitted, the UI may not reflect the updated data.
Consider removing the _isInitialized check to update the controllers whenever a new ProfileLoaded state is received.
39-39: Ensure logUserRetention is properly implemented and imported
The logUserRetention function is called, but it's not clear if it's correctly implemented or imported. Ensure that this function is defined in analytics_service.dart and that it operates as expected.
Run the following script to verify that logUserRetention is defined:
✅ Verification successful
logUserRetention is properly implemented and imported
The function is correctly implemented in analytics_service.dart and properly imported in the profile screen:
- Defined in
core/utils/analytics_service.dartas an async function that logs to Firebase Analytics - Correctly imported in profile screen via
import 'package:eco_bites/core/utils/analytics_service.dart' - Implementation matches the usage, accepting userId and days parameters
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence and implementation of `logUserRetention` function.
# Test: Search for the definition of `logUserRetention`. Expect: The function definition is found.
rg -A 5 'void\s+logUserRetention\('
Length of output: 37
Script:
#!/bin/bash
# Let's try different search patterns and locations to find the function
# Search for any occurrence of logUserRetention
rg "logUserRetention" -A 3
# Search specifically in analytics related files
fd "analytics.*\.dart$" --exec rg "logUserRetention" {} -A 3
# Search for the import statement in the profile screen
rg "import.*analytics" "lib/features/profile/presentation/screens/profile_screen.dart"
Length of output: 1106
| abstract class AddressRepository { | ||
| Future<Either<Failure, void>> saveAddress(String userId, Address address); | ||
| Future<Either<Failure, List<Address>>> fetchUserAddresses(String userId); | ||
| Future<Either<Failure, Address?>> fetchUserAddress(String userId); |
There was a problem hiding this comment.
💡 Codebase verification
Inconsistency found in address handling implementation
There's a mismatch between the single address architecture and the actual implementation:
- The
AddressRepositorycorrectly uses single address withAddress? - However,
ProfileRepositorystill implements multiple addresses:- Uses a subcollection 'addresses'
- Has
getUserAddresses()returningList<AddressModel> - Has
saveUserAddress()that adds to addresses collection
This needs to be aligned with the single address approach by:
- Removing the addresses subcollection approach
- Updating ProfileRepository to handle single address
- Ensuring consistency with AddressRepository implementation
🔗 Analysis chain
Verify the impact of switching from multiple addresses to single address.
The change from List<Address> to Address? aligns with the PR objective of managing a single address. The nullable return type appropriately handles cases where a user has no address.
Let's verify this architectural change is consistently applied across the codebase:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that we're not mixing single/multiple address handling
# Test 1: Check for any remaining references to multiple addresses
rg -i "addresses" --type dart
# Test 2: Check for proper null handling in address-related files
rg "Address\?" --type dart
# Test 3: Look for any address-related UI files that might need updates
fd -e dart -e yaml . | rg -i "address.*screen|address.*page|address.*view"
Length of output: 2170
Script:
#!/bin/bash
# Let's check the implementation of fetch_user_addresses_usecase.dart since it appears in imports
cat lib/features/address/domain/usecases/fetch_user_addresses_usecase.dart
# Also check the address_bloc.dart to see how addresses are handled
cat lib/features/address/presentation/bloc/address_bloc.dart
# Check the profile repository implementation for addresses collection usage
cat lib/features/profile/domain/repositories/profile_repository.dart
Length of output: 7571
| Future<void> call(UserProfile userProfile) async { | ||
| await repository.updateUserProfile(userProfile); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add error handling and input validation.
The current implementation lacks error handling and input validation. Given the PR's focus on Firebase integration and error handling improvements, consider:
- Adding error handling using the new
ServerFailureclass - Validating the
UserProfileinput - Integrating with FirebaseCrashlytics for error reporting
- Future<void> call(UserProfile userProfile) async {
- await repository.updateUserProfile(userProfile);
+ Future<Either<Failure, void>> call(UserProfile userProfile) async {
+ try {
+ // Validate input
+ if (userProfile.userId.isEmpty) {
+ return Left(ValidationFailure('User ID cannot be empty'));
+ }
+
+ await repository.updateUserProfile(userProfile);
+ return const Right(null);
+ } catch (e, stackTrace) {
+ // Log to Firebase Crashlytics
+ await FirebaseCrashlytics.instance.recordError(
+ e,
+ stackTrace,
+ reason: 'Error updating user profile',
+ );
+ return Left(ServerFailure('Failed to update profile'));
+ }
}Don't forget to add these imports:
import 'package:dartz/dartz.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:eco_bites/core/error/failures.dart';| Future<UserProfile?> call(String userId) async { | ||
| final Either<ServerFailure, UserProfile?> result = await repository.fetchUserProfile(userId); | ||
| return result.fold( | ||
| (ServerFailure failure) => null, | ||
| (UserProfile? userProfile) => userProfile, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider improving error handling and adding input validation.
The current implementation has several areas for improvement:
- Silently returning null on failure loses valuable error context
- Missing input validation for userId
- No analytics tracking (mentioned in PR objectives)
Consider this improved implementation:
+ /// Fetches a user profile for the given user ID.
+ ///
+ /// Returns null if the profile doesn't exist or if an error occurs.
+ /// Logs analytics events for successful and failed attempts.
Future<UserProfile?> call(String userId) async {
+ if (userId.isEmpty) {
+ throw ArgumentError('userId cannot be empty');
+ }
+
final Either<ServerFailure, UserProfile?> result = await repository.fetchUserProfile(userId);
return result.fold(
- (ServerFailure failure) => null,
+ (ServerFailure failure) {
+ // Log failure to analytics
+ return null;
+ },
(UserProfile? userProfile) => userProfile,
);
}Committable suggestion skipped: line range outside the PR's diff.
| required String name, | ||
| required String surname, | ||
| required String citizenId, | ||
| required String phone, | ||
| required DateTime birthDate, | ||
| required CuisineType favoriteCuisine, |
There was a problem hiding this comment.
💡 Codebase verification
Validation and data security measures needed for sensitive information
Based on the code analysis, there are several security and validation concerns that need to be addressed:
- No validation is implemented for any of the fields - the registration form uses basic
TextFieldinstead ofTextFormFieldwith validators - Sensitive data (
citizenId,phone) is being sent to the server without any encryption or secure handling - Birth date validation only prevents future dates but lacks minimum age verification
- No format validation for phone numbers and citizen IDs
Required changes:
- Replace
TextFieldwithTextFormFieldand implement proper validators - Add encryption for sensitive data before transmission
- Implement proper format validation for phone and citizen ID
- Add minimum age validation for birth date
- Consider moving sensitive data handling to a secure storage solution
🔗 Analysis chain
Verify data handling and validation requirements.
The expanded user profile collection raises several important considerations:
- The
citizenIdparameter contains sensitive PII data. Ensure proper encryption and compliance with data protection regulations. - Consider adding validation for:
- Phone number format
- Birth date (e.g., minimum age requirements)
- Citizen ID format
Would you like me to help implement the validation logic for these fields?
Let me gather more information about validation and data handling in the codebase.
Let me check the user entity and look for any form validation in the signup screen.
Let me check the user utilities and request utilities for any validation or data handling.
Let me check the register screen implementation for any existing validation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if there's encryption handling for sensitive data
rg -A 5 "citizenId|phone" lib/features/auth/data/
# Look for validation implementations
ast-grep --pattern 'validate$_($$$)' lib/features/auth/
Length of output: 1381
Script:
#!/bin/bash
# Check for validation utilities or helpers
rg -g '*.dart' -B 2 -A 2 'validate|Validator|encryption|encrypt' lib/
# Look for models/entities that might contain validation logic
fd -e dart . lib/features/auth/domain/entities
fd -e dart . lib/features/auth/domain/value_objects
# Check for any existing test cases that might reveal validation patterns
fd -e dart . lib/features/auth/test
Length of output: 952
Script:
#!/bin/bash
# Check user entity implementation
cat lib/features/auth/domain/entities/user.dart
# Look for signup form validation
rg -g '*.dart' -B 3 -A 3 'signup|register' lib/features/auth/presentation/
# Check for any form validators or utilities
fd -e dart . lib/core/
Length of output: 1870
Script:
#!/bin/bash
# Check user and request utilities
cat lib/core/utils/user_util.dart
cat lib/core/utils/request_util.dart
# Look for any validators in core
rg -g '*.dart' 'validator|validate|encryption|secure' lib/core/
# Check signup screen implementation
fd -g '*signup*.dart' . lib/features/auth/presentation/
fd -g '*register*.dart' . lib/features/auth/presentation/
Length of output: 1286
Script:
#!/bin/bash
# Check register screen implementation
cat lib/features/auth/presentation/screens/register_screen.dart
# Look for any validation patterns in the entire auth feature
rg -g '*.dart' 'TextFormField|validator:|String\?' lib/features/auth/
Length of output: 9701
| class LoadProfileEvent extends ProfileEvent { | ||
|
|
||
| const LoadProfileEvent(this.userId); | ||
| final String userId; | ||
| } |
There was a problem hiding this comment.
Add props override to ensure proper equality comparison.
The LoadProfileEvent class should override the props getter to include userId in equality comparisons. Without this, two events with different userId values would be considered equal.
Apply this diff to fix the issue:
class LoadProfileEvent extends ProfileEvent {
const LoadProfileEvent(this.userId);
final String userId;
+
+ @override
+ List<Object> get props => <Object>[userId];
}📝 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.
| class LoadProfileEvent extends ProfileEvent { | |
| const LoadProfileEvent(this.userId); | |
| final String userId; | |
| } | |
| class LoadProfileEvent extends ProfileEvent { | |
| const LoadProfileEvent(this.userId); | |
| final String userId; | |
| @override | |
| List<Object> get props => <Object>[userId]; | |
| } |
| Future<void> _checkAndSaveCachedProfile() async { | ||
| final SharedPreferences prefs = await SharedPreferences.getInstance(); | ||
| final String? cachedProfileString = prefs.getString('cachedProfile'); | ||
| if (cachedProfileString != null) { | ||
| final Map<String, dynamic> cachedProfileMap = jsonDecode(cachedProfileString); | ||
| final UserProfile cachedProfile = UserProfile.fromMap(cachedProfileMap); | ||
| try { | ||
| await updateUserProfileUseCase(cachedProfile); | ||
| await prefs.remove('cachedProfile'); | ||
| final UserProfile? updatedProfile = await fetchUserProfileUseCase(cachedProfile.userId); | ||
| if (updatedProfile != null) { | ||
| emit(ProfileLoaded(updatedProfile, isUpdated: true)); | ||
| } | ||
| } catch (e) { | ||
| emit(ProfileError('Failed to update cached profile')); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Ensure _checkAndSaveCachedProfile() is invoked when internet connectivity is restored.
The _checkAndSaveCachedProfile() method is designed to synchronize cached profile data when the device regains internet connectivity. However, it appears that this method is not currently being called. To ensure that cached profiles are properly updated, consider subscribing to the InternetConnectionBloc and invoking _checkAndSaveCachedProfile() when the connection state changes to connected.
You can add a listener in the constructor to handle connectivity changes:
// Add this in the constructor after initializing super(...)
_internetConnectionBloc.stream.listen((state) {
if (state is ConnectedInternet) {
_checkAndSaveCachedProfile();
}
});| required String name, | ||
| required String surname, | ||
| required String citizenId, | ||
| required String phone, | ||
| required DateTime birthDate, | ||
| required CuisineType favoriteCuisine, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider adding input validation for new sign-up parameters
The new parameters added to the signUp method (name, surname, citizenId, phone, birthDate, and favoriteCuisine) should be validated to ensure data integrity and prevent incorrect or malicious data from being stored in Firestore.
Consider adding validation checks before storing the data. For example:
if (name.isEmpty ||
surname.isEmpty ||
!isValidCitizenId(citizenId) ||
!isValidPhoneNumber(phone) ||
birthDate.isAfter(DateTime.now())) {
return const Left<Failure, User>(ValidationFailure('Invalid user input'));
}| await firestore.collection('profiles').doc(user.id).set(<String, dynamic>{ | ||
| 'name': name, | ||
| 'surname': surname, | ||
| 'citizenId': citizenId, | ||
| 'phone': phone, | ||
| 'birthDate': birthDate.toIso8601String(), | ||
| 'email': email, | ||
| 'favoriteCuisine': favoriteCuisine.name, | ||
| }); |
There was a problem hiding this comment.
Ensure secure handling of sensitive user data
Storing sensitive information like citizenId, phone, and birthDate requires careful handling to comply with data protection regulations and to protect user privacy. Ensure that this data is stored securely, with appropriate encryption and access controls.
Consider reviewing your data storage policies and implement encryption or anonymization where necessary.
| } catch (e) { | ||
| return const Left<Failure, User>(ServerFailure('Failed to save user profile')); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid catching broad exceptions
Using a general catch (e) statement can obscure specific exceptions and make debugging more difficult. It's better to catch specific exceptions related to Firestore operations to handle different failure scenarios appropriately.
Replace with specific exception handling:
} on AuthException catch (e) {
return Left<Failure, User>(AuthFailure(e.message));
} on FirebaseException catch (e) {
return const Left<Failure, User>(ServerFailure('Failed to save user profile'));
}| final SharedPreferences prefs = await SharedPreferences.getInstance(); | ||
| await prefs.setString('cachedProfile', updatedProfile.toMap().toString()); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use JSON encoding when saving profile data to SharedPreferences
When caching the profile data, serializing it using JSON ensures proper encoding and decoding, especially if the profile data contains nested structures or special characters.
Apply this diff to serialize and store the profile data as JSON:
+import 'dart:convert';
...
await prefs.setString('cachedProfile', jsonEncode(updatedProfile.toMap()));Committable suggestion skipped: line range outside the PR's diff.
This pull request includes significant updates to the
EcoBitesapp, focusing on integrating Firebase Analytics, improving error handling, and modifying the address management system to use a single address instead of a list. Here are the most important changes:Firebase Integration:
FirebaseAnalyticsandFirebaseCrashlyticsintegration to log various events and errors (lib/app.dart,lib/core/utils/analytics_service.dart). [1] [2]Error Handling:
ServerFailureclass to handle server-related errors more effectively (lib/core/error/failures.dart).UI and State Management:
EcoBitesAppwidget to includeFirebaseAnalyticsand added aProfileBlocprovider (lib/app.dart).AddressBlocto handle new error types and to use the current user's ID from Firebase Authentication (lib/features/address/presentation/bloc/address_bloc.dart). [1] [2]Miscellaneous:
FirebaseFirestoreto theAuthRepositoryImplto support future enhancements (lib/features/auth/data/repositories/auth_repository_impl.dart). [1] [2]Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation
Chores