Sprint 3 - mobile-dev-16/S1-G16-planning GitHub Wiki

Implemented Business Questions

Sprint 2

  • What is the average loading time of the app from launch to the main page? (Type 1) ( Raul )
  • Is there a surplus of your favorite cuisine available today near your location? (Type 2) ( Abel )
  • Is the user currently located far from their saved address? (Type 2) ( Raul )

Sprint 3

  • How does the app's performance (loading times, crash rates) correlate with user retention rates? (Type 5) ( Raul )
  • Are there any new partner businesses offering surplus deals near the user's saved address today? (Type 2) ( Mario )
  • Are there new surplus food deals available that match the user’s dietary preferences today? (Type 2) ( Abel )

List of implemented Functionalities

Sprint 2

  • User Authentication (Google and Email Sign-In, Sign-Up).
  • Address Management with GPS and Google Maps API.
  • Calculate loading time of the app from launch to the main page and loading with splash.
  • Add and delete cart items.
  • Scrollable order items.

Sprint 3

  • Profile information registration.
  • Modifiable diet and food type for better user experience
  • "For you" uses user favorite cuisineType
  • Log out option
  • User information in cache
  • Flutter toast to manage Eventual Connectivity
  • Complete cart items management.
  • calculate total order and delete it completely
  • List of available restaurants

Eventual Connectivity Solutions and Scenarios

Our connectivity solution is designed to keep users constantly aware of their internet status, minimizing confusion and enhancing user experience by avoiding common anti-patterns. The solution relies on two main components: InternetConnectionBloc and NetworkInfo.

The InternetConnectionBloc listens for changes in network connectivity using the connectivity_plus library. When there’s a change, it emits specific events: ConnectedEvent when internet is available and DisconnectedEvent when it’s lost. These events trigger state updates in the bloc (ConnectedInternet or DisconnectedInternet), which are then communicated to the user via toast notifications, keeping them informed in real-time.

Meanwhile, the NetworkInfo class constantly checks the device's connectivity status and collaborates with the InternetConnectionBloc to ensure a responsive connection update mechanism. This setup allows widgets within the app to react dynamically to connectivity changes, ensuring users are consistently informed and can make decisions accordingly. For example, in the UI, a SnackBar or toast message notifies users whenever there is a change in connection, as illustrated in the example screenshots. This proactive approach to connectivity management helps maintain user trust and smooth app operation.

Offline Data Access and Updates

  • Scenario: The user is offline and tries to access or update their profile information.
  • Strategy: Implement local caching using SharedPreferences to store user data and updates locally. When the user is offline, any updates to the profile are saved in the local cache. Once the connectivity is restored, the cached updates are synchronized with the server.
  • Behavior: When the user updates their profile offline, the app saves the changes locally and informs the user that the data will be synchronized once the connection is available. Upon reconnection, the app automatically syncs the cached data with the server.

Data Synchronization on Reconnection

  • Scenario: The user was offline and made several updates to their profile. The app needs to synchronize these updates once the connection is restored.
  • Strategy: Implement a mechanism to detect reconnection and trigger the synchronization of cached data with the server. Use a background service or a periodic check to ensure that the data is synchronized as soon as the connection is available.
  • Behavior: When the app detects that the connection is restored, it automatically synchronizes the cached profile updates with the server. The user is notified that their data has been successfully synchronized.

User Authentication and Session Management

  • Scenario: The user is offline and tries to log in or access restricted features.
  • Strategy: Implement offline authentication using cached credentials. Allow the user to access certain features based on the last known authentication state. Once the connection is restored, validate the session with the server.
  • Behavior: When the user tries to log in offline, the app checks the cached credentials and allows access to restricted features if the credentials are valid. Upon reconnection, the app validates the session with the server and updates the authentication state accordingly.

How It Works

  • onnectivity Detection: The InternetConnectionBloc uses the connectivity_plus library to detect changes in internet connectivity. When there is a change, it emits an event (ConnectedEvent or DisconnectedEvent).

  • Event Handling: The InternetConnectionBloc handles these events and updates its state (ConnectedInternet or DisconnectedInternet). It also shows a toast message to notify the user about the change in state.

  • UI Reaction: Widgets that listen to the InternetConnectionBloc can react to these state changes. In the example above, the InternetStatusWidget shows a SnackBar when the internet connection state changes.

InternetConnectionBloc

The InternetConnectionBloc listens for connectivity changes and emits corresponding events

image

NetworkInfo

The NetworkInfo class checks the connectivity status and emits events to the InternetConnectionBloc.

image

Visualization in the Application

When the application detects a change in internet connectivity, a SnackBar is displayed at the bottom of the screen:

  • Internet Connection: A green SnackBar with the message "Connected to the internet" is shown.
  • No Internet Connection: A red SnackBar with the message "No internet connection. Some information may not be up to date." is shown. This provides immediate visual feedback to the user about the internet connection status.

The eventual connectivity solution is implemented and the widgets in the application can listen to these changes and react accordingly, providing a smooth and responsive user experience.

image image

Implemented Local Storage Strategies

How It Works

The application uses local storage strategies to persistently save data on the user's device, allowing it to access this information even after being closed and reopened. SharedPreferences is a Flutter library that provides a simple way to store key-value pairs of primitive data types (such as strings, integers, and booleans) in persistent storage. SharedPreferences works by saving the data in an XML file in the device's internal storage. When a value is saved, it is associated with a unique key, making it easy to retrieve later. For example, to save a user ID, the application uses setString('userId', userId), and to retrieve it, it uses getString('userId'). This technique is useful for storing user settings, session data, and other information that needs to be persistent across application sessions.

Implementation

Snipppet 1

image

The _getUserId function retrieves the unique ID of the currently authenticated user from Firebase Authentication. It first accesses the current user through FirebaseAuth.instance.currentUser. If a user is logged in, it returns their unique identifier (uid). If no user is logged in, it simply returns null. This function is useful for identifying the user in order to personalize the app experience, retrieve user-specific data, or manage user-specific storage operations, ensuring that actions within the app are tied to the correct user.

Snipppet 2

image

This code attempts to fetch the userId stored in SharedPreferences, which is a local storage tool on mobile devices. If the userId is available, it logs the value and checks if it’s not empty before returning it. If an error occurs during retrieval, the code catches and logs the error, returning null to indicate that the user ID could not be obtained. This error-handling process is essential for ensuring the app’s stability and reliability.

Snipppet 3

image

local data source is implemented that uses SharedPreferences to store and retrieve user data in JSON format. The getCachedUser function tries to retrieve the user data from SharedPreferences in JSON format, converting it to a UserModel object if found. The cacheUser function saves the user data in JSON format to SharedPreferences for quick access in the future. This implementation is useful for keeping user data accessible even when the device has no internet connection.

Snipppet 4

image

part of the SplashBloc class, which executes when the app starts. The _onAppStarted function tries to retrieve the userId from SharedPreferences, using this data to check the user’s authentication status on app startup. Based on the retrieved userId, the SplashBloc emits states indicating whether the user is authenticated. This helps decide which screen to display to the user, facilitating initial navigation and the login experience within the app.

Implemented Multi-Threading Strategies

Our application implements multi-threading strategies primarily through the use of asynchronous programming with async and await keywords in Dart. This allows the application to perform non-blocking operations, such as network requests and database queries, without freezing the user interface. Here are some examples:

  1. Fetching Data from Remote Sources:

image

  • In FoodBusinessRemoteDataSourceImpl (e.g., fetchNearbySurplusFoodBusinesses method), asynchronous operations are used to query Firestore and fetch data without blocking the main thread.

In the fetchNearbySurplusFoodBusinesses method of the FoodBusinessRemoteDataSourceImpl class, asynchronous programming is used to query Firestore without blocking the main thread. First, a query is built on the foodBusiness collection in Firestore, applying filters based on cuisine type and category if they are present. Then, a QuerySnapshot of the matching documents is obtained. For each document, another query is made to the offers subcollection to get available and valid offers. If there are available offers, the distance between the user's location and the food business is calculated. If the distance is within the specified range, the business is added to the result list. All of this is done asynchronously to avoid blocking the user interface.

  1. Saving and Retrieving Data Locally:

image

  • In UserLocalDataSourceImpl (e.g., getCachedUser, cacheUser methods), asynchronous operations are used to interact with SharedPreferences to save and retrieve data.

In the UserLocalDataSourceImpl class, the getCachedUser and cacheUser methods use SharedPreferences to persistently store and retrieve user data. The getCachedUser method retrieves a JSON string stored under the StorageKeys.user key, decodes it, and converts it into a UserModel object. If the string is not found, it returns null. The cacheUser method takes a UserModel object, converts it to a JSON string, and stores it in SharedPreferences under the StorageKeys.user key. Both methods are asynchronous, allowing these read and write operations to be performed in the background without blocking the main thread.

  1. Network Connectivity Checks:

image

  • In NetworkInfo (e.g., isConnected method), asynchronous operations are used to check the network connectivity status.

In the NetworkInfo class, the isConnected method checks the network connectivity status asynchronously. It uses the connectivity_plus library to get a list of connectivity results. If there are results and none of them is ConnectivityResult.none, the device is considered to be connected to the Internet. Depending on the connection status, a corresponding event (ConnectedEvent or DisconnectedEvent) is emitted to the InternetConnectionBloc. This approach allows the application to handle network connectivity changes without blocking the main thread.

  1. User Authentication:

image

  • In UserRemoteDataSourceImpl (e.g., signInWithEmailAndPassword, signUpWithEmailAndPassword methods), asynchronous operations are used to interact with Firebase Authentication.

In the UserRemoteDataSourceImpl class, the signInWithEmailAndPassword method uses Firebase Authentication to authenticate a user with their email and password. This method is asynchronous and uses await to wait for Firebase to complete the sign-in operation. If the authentication is successful and a valid UserCredential is obtained, it is converted into a UserModel object and returned. If the authentication fails, an AuthException with an error message is thrown. This approach allows the sign-in operation to be performed in the background, keeping the user interface responsive.

These strategies ensure that the application remains responsive by offloading time-consuming tasks to separate threads, allowing the main thread to handle user interactions smoothly.

Implemented Caching Strategies

image

The UserLocalDataSource class is responsible for caching user data locally using SharedPreferences. It provides methods to store, retrieve, and clear user information and user IDs. The getCachedUser method retrieves a JSON string stored under the StorageKeys.user key, decodes it, and converts it into a UserModel object. If the string is not found, it returns null. The cacheUser method takes a UserModel object, converts it to a JSON string, and stores it in SharedPreferences under the StorageKeys.user key. Similarly, getCachedUserId and cacheUserId methods handle the storage and retrieval of the user ID. The clearUserCache and clearUserId methods remove the stored user data and user ID, respectively. These operations are asynchronous, allowing them to be performed in the background without blocking the main thread, ensuring a responsive user interface. This caching strategy ensures that user data is persistently available across app sessions and can be accessed quickly without requiring a network call.

image

This function is used to check and save the cached profile data when the Internet connection is re-established. First, it retrieves the cached profile data from SharedPreferences under the cachedProfile key. If there is cached data, it converts it to a UserProfile object and calls the updateUserProfileUseCase use case to update the profile on the server. Then, it removes the cached data from SharedPreferences. If the update is successful, it emits a ProfileLoaded state with the updated profile. If not, it emits a ProfileError state with an error message.

image

This function handles the LoadProfileEvent event, which is fired when the user's profile is loaded. First, the function emits a ProfileLoading status to indicate that loading is in progress. Then, it attempts to get the user's ID. If the user's ID cannot be obtained, it emits a ProfileError status with an error message. If the user's ID is obtained, the function calls the fetchUserProfileUseCase use case to retrieve the user's profile from the server. If it is retrieved successfully, it emits a ProfileLoaded status with the user's profile. If not, it emits a ProfileError status with an error message.

Ethics Component

LINK: https://youtu.be/DALXkn0MGZA