Sprint 2 - EQUIPO-11-CONSTR-APLIC-MOVILES/Team-11-Wiki GitHub Wiki
- Including material design components
- Prototype of main screen, liked screen, account screen and filter screen
- Prototype navigation across screens
- Prototype integration of styles and order
- Detail caring
- Description of the logo, images and icons and their rationale.
- Inclusion of the search screen in the prototype to comply with the class' requirements.
- Enhancement of the value proposal.
- Project creation
- Dependencies in the project
- Configuration of DaggerHilt, including the creation of AppModule object and RestaUApplication class
- Inclusion of font files and the fontfamily making on Type.kt
- Color Palette inclusion
- Shapes inclusions
- Disabling Dynamic Coloring
- Project structure (Data, Domain, Presentation, Utils)
- Presentation submodules (common, navgraph)
- Domain submodules (model, repository, usecases)
- Data submodules (local, remote, repository)
- Removed the original SplashScreen modifying themes and android manifest.
- Fixed the use of colors in nav bar.
- Added all the routes to the nav graph.
- Added System Ui controller dependency for coloring the system bars in the navigator screen.
- Designed SplashScreen according to figma
- Removed SplashScreen route
- Created files for Search, Random, Home, Liked and Map Screens.
- Implemented the splash screen modifying NavigatorScreen.kt and NavigatorViewModel.kt to change the splash screen after a given time.
- Added the RestaU logo to the project.
- Implemented the NavigatorEvent to communicate the NavigatorScreen with it's ViewModel.
- Moved navigator screen, components and view model to it's own package
- Creation of the navgraph, navbar, navigator screen, inclusion of nav bar icons, basic route definition. Modification of main activity with the navigator screen
- Creation of RecentsRepository.kt with the implementation of preferences data store with a set of strings.
- Creation and implementation of the RestaurantsRepository which has one function for getting firestore's whole list of restaurants.
- Creation and implementation of the RecentsRepository with methods for retrieving and saving preferences, in this case, recent restaurants searched.
- Creation and implementation of the following: GetRestaurants.kt use case, SaveRecents.kt, GetRecents.kt
- RestaurantUseCases and adding it to dependency injection.
- RecentsUseCases and adding it to dependency injection.
- Dependency injection of Recents and Restaurant repositories.
- Creation of the domain model for restaurant
- Fixing the schedule attribute of a restaurant from Map<String, List> to Map<String, Map<String, Double>> to allow simpler queries to firebase.
- Creation of the GetOpenRestaurants function in the restaurants repository and including it within it's interface.
- Making of the GetOpenRestaurants.kt use case and it's inclusion within the DI app module, RestaurantUseCases.kt.
- Fixing a padding issue within the navigator screen.
- Inclusion of multiple icons such as kid_star, map_filled, filled_lie.
- Creation of the HomeViewModel.kt
- Implementation of the RestaurantCard.kt file.
- Creation of the TopBarAction class decorated for managing top bar variations.
- Development of the App's top bar.
- Implementation of the HomeScreen.kt but with static values.
- Creation of the DateTimeUseCases.kt which include GetCurrentDay and GetCurrentTime which are utilities to get the current day of the week and the time in HHmm format.
- Inclusion of the new use cases in the DI App Module.
- Creation of HomeEvent.kt to manage communication among the UI and the ViewModel.
- Creation of the HomeState with the values displayed in the HomeScreen.
- Implementation of state in the viewModel, the onEvent function to receive events from the UI and the getRestaurants methods to retrieve restaurants based on the filter.
- Progress on the HomeScreen including the state and viewModel.
- Fixing the lack of an image field in the model.
- Including GetIsNewRestaurantArray in the DI App module.
- Reducing the top bar height in DynamicTopBar.
- Creation of the GetIsNewRestaurantArray to retrieve a list of boolean values indicating if a restaurant was opened within the past 30 days.
- Changing the restaurant list lateral margins in HomeScreen.kt.
- Inclusion of separation among cards of restaurants in HomeScreen.kt.
- Inclusion of database data in the lazyColumn of HomeScreen.kt.
- Including a circular progress indicator for the restaurant list in HomeScreen.kt.
- Including the isNew attribute in HomeState.kt.
- Emptying the restaurant list on filter pressing and using setting isNew state with the GetIsNewRestaurantArray useCase.
- Simplifying navigation when pressing bottom bar items in NavigatorScreen.kt.
- Inclusion of changing the selected bottom bar item when pressing the back button.
- Elipsis on titles of RestaurantCard.kt
- Ordering retrieved data by averageRating on RestaurantsRepositoryImpl.
- Inclusion of the GetIsNewRestaurantArray useCase in the RestaurantUseCases.kt.
- Making of the itemsMap to compare the screen route to an index in Route.kt.
- Removal of DateTimeUseCases.kt, GetCurrentDay.kt and GetCurrentTime.kt and inclusion of their functionality to the GetOpenRestaurants.kt use case.
- Removal of test logs.
- Inclusion of the app icon image.
- Inclusion of the nothingOpen attribute in the HomeState.kt.
- Refactoring getRestaurants function on the HomeViewModel.kt.
- Inclusion of the nothingOpen functionality to show a message when no restaurants are open in HomeViewModel.kt
- Simplification of the onEvent function in HomeViewModel.kt.
- Refactoring and ordering the Circular progress indicator in a composable called LoadingCircle in HomeScreen.kt
- Implementation of the NoRestaurantOpen composable function to show the message and icon for when no restaurants are open and implementing it's functionality using the nothingOpen attribute in HomeScreen.kt.
- Clock icon added with clock.xml.
- Inclusion of the google play location services library and the accompanist permissions in the build.gradle.kts file.
- Inclusion of the location permissions in the android manifest.
- Implementation of the LocationRepositoryImpl.kt
- Creation of the LocationRepository interface.
- Creation of the GetLoacation use case.
- Inclusion of the LocationRepository and the GetLocation use case in the DI app module.
- Creation of the PermissionGranted and Revoked in the MapEvent.kt file.
- Implementation of permissions, map, camera location, restaurants pins and cards, radius circle with static size.
- Creation and implementation of the MapState
- Creation and implementation of the mapViewModel for getting restaurants, permissions and managing the circle's position.
- Adding the radius circle color in the Color.kt file.
- Implementation of the has location permission extension.
- Creation of the StarRating.kt to show the rating as stars.
- Inclusion of icons such as star.xml, no_permissions.xml,half_star.xml and empty_star.xml.
- Creation of the SliderCard.kt which includes the radius picking slicer.
- Creation of the NoPermissionsSign.kt to indicate the denial of the location permission.
- Implementation of the map, variable radius, restaurant pins and restaurant pin cards. This is reflected in MapViewModel.kt, MapState.kt, MapScreen.kt, CardMarker.kt and MapEvent.kt.
- Reduction of location query time in LocationRepositoryImpl.kt.
- Creation of the LoadingCircle.kt component to indicate loading state.
- Creation and implementation of image download functions in the ImageRepositoryImpl.kt, ImageRepository.kt, ImageDownloadUseCases.kt, DownloadSingleImage.kt and DownloadImages.kt.
- Removal of repeated LoadingCircle function in HomeScreen.kt.
- Implementation of GetRestaurantsInRadius.kt to get boolean list of restaurants in the current radius.
- Implementation of related dependencies in the App Module of DI.
- Inclusion of the accompanist permissions library to control permissions easier.
- Inclusion of the GetRestaurantsInRadius use case in the RestaurantUseCases.kt, modification of this in App Module for DI. Fix this issue in MapViewModel.kt.
- Creation of the LocationUseCases.kt to maintain a more consistent structure. Implementation and fixes of this in the App Module of DI and the MapViewModel.kt.
- Creating main.py in the analytics engine
- Addition of the GetRestaurantsLiked in the RestaurantUseCases and the implementation in the DI app module.
- Implementation of the GetRestaurantsLiked use case.
- Implementation of the GetUserObject use case.
- Creation of the SendLike event.
- Implementation of likes in the HomeScreen and the HomeViewModel.
- Creation of the SendLike use case.
- Addition of the documentId attribute to the User model.
- Creation of the UsersRepository and it's implementation.
- Creation of the UserUseCases.
- Inclusion specialpin.png for the highlighting of new restaurants that the user might like.
- Implementation of the HasLikedCategoriesArray to find restaurants that the user would possibly like.
- Implementation of the HasLikedCategoriesArray in the DI AppModule and the RestaurantUseCases.
- Optimization of images retrieval.
- Implementation of the highlighting of new restaurants that the user might like in the MapViewModel, HomeScreen, MapState, MapEvent.
- Improvement of the CardMarker shape.
- Inclusion of tag for turning off automatic screen tracking.
- Inclusion of the analytics object in the AppModule for DI.
- Inclusion of the Firebase Analytics dependency.
- Creation of the ScreenOpened and ScreenClosed HomeEvent.
- Calling of the new events in the HomeScreen for screen time tracking.
- Inclusion of the timer methods in the HomeViewModel.
- Creation of the AnalyticsUseCases file.
- Creation of the ScreenTimeEventsRepository and the ScreenTimeEventsRepositoryImpl.
- Creation of the SendScreenTimeEvent use case.
- Improvement of the SearchScreen top bar and inclusion of the dynamic user photo.
- Inclusion of new repository and use cases in the AppModule for DI.
- Inclusion of lifecycle dependencies in the build.gradle.kts and libs.versions.toml.
- Implementation of the screen time measuring in the SearchScreen, SearchViewModel and SearchEvent.
- Implementation of screen time measuring in the MapScreen, MapViewModel and MapEvent.
- Simplification of the time measuring in the HomeScreen and HomeViewModel.
- Creation of the LikedEvent with the ScreenOpened and ScreenClosed events.
- Inclusion of the lifecycle effects in the liked screen for screen time measuring.
- Creation of the LikedViewModel for retrieving the current user and implementing the methods for measuring screen time.
- Inclusion of the "/screentimes/clean" endpoint to clean the locally saved values of average screen time usage.
- Inclusion of the "/screentimes" endpoint to get the average user weekly time in each screen.
- Creation of the WeekScreenTime and LastVisit tables in the local database for fast access to information within the same week.
- Configuration of basic aspects of the analytics repository
- Removal of the RestaurantsLazyList function from HomeScreen and put in RestaurantsLazyList.kt file under the common folder for reuse.
- Creation of the SendLike event in LikedEvent.kt
- Creation of the LikedState.kt
- Implementation of the liked restaurants list in LikedScreen.kt
- Implementation of the liked restaurants list functionalities in LikedViewModel.kt.
- Fixing the LikedViewModel problem when pressing back.
- Fixed repetitive stack entries bug in navigator screen.
- Removed screen rotation possibility in AndroidManifest.xml.
- Added the average rating in StarRating.kt and size modifications to fit it in CardMarker.kt.
- Fixed excess padding in SearchScreen.kt.
- Removed bus top markers in MapScreen.kt using map_style.json
- Creation of the ScreenLaunched event on HomeEvent.kt and LikedEvent.kt.
- Reaction to the ScreenLaunched event in the LikedViewModel.kt and HomeViewModel.kt.
- Inclusion of LaunchedEffect to launch the ScreenLaunched event and (re)retrieve data and maintain consistency of data.
- Inclusion of the business questions to implement in the wiki.
- Part of the analytics diagram and rationale corresponding to the "What are the screens where the users in average spend the least time weekly?" business question.
- Making of the domain model and it's rationale.
- Diagram of the firebase business collections and rationale
- Contribution to the kotlin component diagram, architecture diagram and their rationale.
- Diagram and rationale for the facade pattern
- Participation on the ethics video
- List of implemented features
- Editing the ethics video
- Added shades to the RestaurantCard and SliderCard.
- Removed unnecessary variable in HomeViewModel.
- Participation in the deployment diagram.
- Prototype design of restaurant detail and schedule screen
- Prototype navigation across screens
- Description of color palette
- Rewritting the value proposal enhancement
- Creating base flutter application
- Development of deployment diagram and rationale
- Development of own business question in pipeline diagram and API
- Development of Flutter app components diagram
- Development of Flutter Log In screen and functionality
- Development of Flutter Registration screen and functionality
- Participation in ethics video
- Description of singleton design pattern and rationale
- Prototype design of restaurant menu and detail.
- Description of navigational patterns.
- Check the value proposal enhancement.
- Rationale of the componentes diagram for Flutter.
- Development of own business question in pipeline diagram and API
- Development of Flutter Home scscreen and functionality
- Development of Flutter Search screen and functionality
- Development of Flutter Liked screen and functionality
- Participation in ethics video
- Description of repository design pattern and rationale
- Prototype of map screen
- Logo Design
- Selection of fonts
- Review of team's repository with small corrections
- Screen files created
- Navigation bar implemented
- Map Screen implemented
- Firestore service implemented
- Inclusion of the "/navigation-paths" endpoint to get the most common navigation paths used by the users
- Participation on the ethics video
- Editing the ethics video
- Diagram and rationale for the adapter pattern
- Prototype of the review screen and write review screen
- Prototype navigation across screens
- Add to the prototype the color palette and font styling
- Adding the UI prototype to the wiki
- Add an explanation of half of the Navigational Patterns.
- Enhancement of the Navigational Patterns
- Help in the development of components diagram.
- Development in Kotlin of the search screen with all the functions.
- Development in Kotlin of the voice recognition feature.
- Development of the for you (recommendations) screen in the HomeScreen.
- Participation in ethics video.
- Description of singleton design pattern in the Kotlin app and rationale.
- Design of own business question (Which of the app's features are the most used?) in pipeline diagram and API.
- Implementation of all the data collection functions for feature interactions across the app to address the BQ.
- Implementation of an endpoint in FastAPI to retrieve and process data for feature interactions.
- Logo Design
- Development of splash screen, sign up and sign in screens mockups
- Minor design fixes
- Selection of fonts
- Definition of fonts on the wiki
- Added preferences screen to the mockup
- Redesign of detail screen , detail open, reviews and write reviews screens
- Populated Restaurant database
- Implemented Sign In Screen and User Authentication
- Implemented Sign Up Screen and User Registration
- Implemented Preferences Screen and its register on Firebase
- Description of Repository design pattern in the Kotlin app and rationale
- Participation on the ethics video
- Contribution to the kotlin component diagram
- Analytics for type 2 BQ
- Contribution to App Report 2
Type 1: What features are causing the app to crash unexpectedly?
This is a type 1 question because it concerns the monitoring of the app's performance. In this case it relates to bugs causing the app to crash, that should be revised and fixed. The data for type of question is collected internally.
Type 2: Is there a new restaurant nearby that matches the user's 'liked' cuisine types?
This is a type 2 question because it enhances the user's dining experience by leveraging their personal preferences to discover new options. By identifying new restaurants that align with the user's 'liked' cuisine types, the app provides personalized recommendations that cater to their tastes. This functionality not only introduces variety but also encourages exploration of new dining experiences, making it easier for the user to find appealing options. The system uses the user's location and historical data to deliver relevant and timely suggestions (via a notification), thereby improving their overall interaction with the app.
Type 3: What are the screens where the users in average spend the least time weekly?
This is a type 3 question because it is helping the business decide if a screen with a set of functionalities is not being used nor accepted by the users. If users aren't really using a screen, the business should think about upgrading it, fixing it or removing it. The datasource for this question would be internal by using the time each user spends in the different screens weekly and doing an average.
Type 3: What are the most common navigation paths users take within the app?
This is a type 3 because it focuses on understanding user behavior within the app to optimize features and user experience. By analyzing the navigation paths, the business can identify which features are most frequently accessed, which paths are less intuitive or problematic, and where users might drop off. This insight helps in making informed decisions about feature placement, streamlining the user journey, and potentially removing or updating features that aren't being used as intended. The ultimate goal is to enhance the app’s usability and efficiency, ensuring it meets user needs effectively.
Type 4: What are the most searched cuisines or types of food by the users?
This is a type 4 question due to the following considerations: the main purpose of this question involves making a profit since we can sell this data to a third-party organization who can get advantage of that. Next to this, the main source of this data comes from the interaction of the user with the app given that each search by an user is a new piece of data added to the collection.
As mentioned before, this kind of data could be object of selling to third parties as it could help them to create value for the organization, for example, a restaurant chain manager is looking to settle a new business near de campus and wants to target the most preferred gastronomical options by people, this kind of knowledge could be derived from the sold data. Besides this, the answer to this question could help us to put or locate some adds or publicity in our app since the trends extracted from the data could attract potential sponsorships looking for more visibility. Finally, before answering this question it is necessary to find potential stakeholders that could share an interest for the data and eventually benefit for it.
Type 5: Which of the app's features are the most used?
This is a type 2 questions because it concerns the direct user experience of using the different features of the app. By identifying the most popular features we could highlight them, improving the user experience, making it easier to browse the app. It is also a type 3 question because it regards the features with the purpose of understanding which features should be removed or updated to better increase the user's experience. These hybrid question tries to analize the effectivity of each implemented feature in terms of amount of usage and user satisfaction with it.
The following diagram represents a structured process for collecting, analyzing, and presenting app data, primarily focused on answering the bussines questions detailed in the last section. Tha artifact is composed by the following parts:
- Data Source: This layer gathers unstructured data from user interactions within the app. It focuses on key questions like which screens users spend the least time on, the most popular features, navigation paths, and the root causes of app crashes. This raw data forms the foundation for deeper analysis of user behavior, app performance, and user preferences.
- Ingestion: The data ingestion process detects and logs user activity, such as lifecycle events, button presses, and navigation within the app, using Kotlin or Dart. This stage measures which app features are being used, identifies errors leading to crashes, and monitors user searches and location data when they interact with restaurant features.
- Integration: Collected data is mapped to Firebase collections by aligning app interactions with structured fields. This is then sent as JSON-formatted data to an API for further processing, ensuring consistent and structured storage of the app’s interaction data.
- Storage: Firebase Firestore (NoSQL) stores various data types, including screen-time events, feature interactions, restaurant searches, user preferences, and navigation paths. Firebase Crashlytics also logs app crashes to track and analyze stability issues, helping diagnose and prevent unexpected crashes.
- Data Computation: Using Python, Pandas, and FastAPI, the data is processed to extract insights, such as trends in restaurant searches, the frequency of crashes, and the most common navigation paths. This computation provides valuable information to refine app performance and enhance the user experience.
- Presentation: Power BI is used to present the insights through various visualizations, such as bar graphs for user time spent on screens, pie charts for feature usage, Sankey diagrams for navigation paths, and notifications for relevant restaurant matches. These visual aids allow the team to quickly interpret the data and act on the insights, improving app performance and personalization for users.
This pipeline offers a detailed understanding of how users interact with RestaU app, identifies pain points like crashes, and delivers tailored insights to improve both user experience and app stability.
To answer thisquestion we first need to rely on Firebase Crashlytics to automatically collect crash reports. These reports include details such as stack traces, device information, and app version, which help identify the root cause of crashes. By integrating Firebase Crashlytics custom logging (e.g., using setCustomKey
), we can tag specific features or screens, linking crashes directly to user actions or app features. This allows us to track which feature was in use when the app crashed and gather more specific insights into the areas of the app causing issues.
After gathering the data, we process it by grouping crashes by feature, calculating the number of affected users and crash frequency. This can be done by exporting data from Crashlytics, converting it into a structured format like a pandas dataframe, and performing statistical analysis to identify patterns. Finally, we visualize the results, such as in a dashboard, showing the features most prone to causing crashes, helping prioritize fixes based on impact and frequency. Now we can summarize the process in the following steps:
- Use Firebase Crashlytics to collect crash logs automatically.
- Integrate custom logs using
setCustomKey
to tag features or screens during user interactions. - Export and process crash data by grouping crashes by feature and calculating frequency and user impact.
- Use a dashboard or reporting tool to visualize and analyze crash trends by feature.
- Use the analysis to prioritize fixes for the most impactful or frequent crash-causing features.
This feature improves the user's dining experience by using their personal preferences to suggest new restaurants. The app looks for nearby restaurants that match the types of cuisine the user likes, giving personalized recommendations. This helps users try new places while staying within their preferred tastes. The app uses the user's location and past data to send timely notifications, making it easier to find appealing options. To address this, the following steps are taken:
- When the user opens the app, their location and ID are sent to the analytics API.
- The pipeline retrieves the user's preferred cuisines from the database.
- It gathers data on nearby restaurants and their cuisine types.
- Convert all entries into Python dictionaries
- It checks for restaurants within 1 km that match the user's preferred cuisines.
- If a match is found, the app gets the info to show the user a notification.
This simple process helps users discover new restaurants that fit their tastes, making the app more useful and personalized.
For this question, the first layer was the app and the user's interactions with it. Here, raw data was present as the actions taken by the user of entering or leaving a screen. For ingesting this data and take it to processing, we detected lifecycle events in each screen where we acted upon on Resume
and Pause
events to react when a user entered or left the screen. With these events, we were able to find the time when the screen was opened and when it was closed and be able to find the time between. For the integration step, we made objects that had the fields determined to be stored in the firestore database (date, screen, time in seconds, user id)
to transport the data among the app's layers. Then, the object was passed to the firestore API which turned it into their JSON like format. The previous action also transfered the object to firestore (screen_time_events collection
) which is our document-NoSQL storage layer. For the data computation layer we used a python analytics backend using FASTAPI and SQLAlchemy, and did the following steps.
- Gathering the
screen_time_events
collection for average calculations. - Convert all the entries into python dictionaries.
- Make a pandas dataframe out of the dictionaries.
- Get the week and year of the event out of the date field and joining them in the following format
"YYYY-W"
. - Removing all columns except
screen
,user_id
,week
andtime_seconds
. - Group by
screen
,user_id
andweek
and add all thetime_seconds
values. - Group by
screen
anduser_id
and find the mean of all thetime_seconds
. - Group by
screen
and find the mean of all thetime_seconds
. - Save the screen-time_seconds pairs in a local relational database for faster access in future queries.
- Expose data in an endpoint of the analytics backend.
Finally, for the presentation layer the information was presented in a PowerBI dashboard with a bar graph portraying screens vs average weekly user time.
To address this question, the first layer involves capturing user interactions within the app, where raw data is collected based on actions taken by the user, such as clicking buttons or interacting with key components of the features. To ingest and collect this data, we detect user interactions with buttons or main components of each feature to measure their usage, for example, tracking the number of times the voice recognition feature is used by counting interactions with the corresponding button.
During the integration step, we created objects with fields designated for storage in the Firestore database (e.g., name_feature_interaction, user_id) to facilitate data transfer across the app's layers. These objects were then passed to the Firestore API, which transformed them into its JSON-like format. This process also stored the object in Firestore (specifically in the features_interactions collection), which functions as our document-based NoSQL storage layer.
For the data processing layer, we utilized a Python analytics backend built with FASTAPI and SQLAlchemy, following these steps:
- Retrieve the
features_interactions
collection. - Convert all entries into Python dictionaries.
- Create a Pandas DataFrame from the dictionaries.
- Ensure the DataFrame contains only the
name_feature_interaction
anduser_id
columns. - Group by the
name_feature_interaction
field. - Count the interactions for each feature.
- Add a new column reflecting the total interaction count for each feature.
- Save the interaction counts by feature name in a local relational database for faster future queries.
- Expose the data via an endpoint in the analytics backend.
Finally, for the presentation layer, the information was displayed in a PowerBI dashboard with a pie chart showing the usage of each feature in the app.
To answer this question, we collected user navigation paths by tracking the sequence of screen transitions during a user session. Each path consists of the screens visited by the user in order. The raw data was stored in the navigation_paths
collection within Firestore, where each path was saved as a string format like "Home > Search > Product"
.
For processing, these strings were retrieved by our API and converted into structured lists of screen names. To efficiently analyze the paths, these lists were used to build a tree data structure. In this tree, each node represents a screen, and each branch indicates a transition to the next screen in the path. Each node holds two important values: the number of users who have reached that screen along a particular path and how many users ended their navigation on that screen.
The tree allows us to store and track all possible paths in a consolidated structure. From this tree, we performed the following steps:
- Tree Construction: Each user's navigation path was parsed and added as a branch in the tree, incrementing counts at each screen transition.
- Path Collection: Paths of at least 3 screens were collected from the tree, along with the number of users who followed each path.
- Top Paths Extraction: The collected paths were sorted by the number of users who followed them, and we extracted the top N most common paths.
- Data Transformation for Visualization: The top paths were then converted into source-target pairs for each transition, along with the count of users making that transition.
For visualizing the results, we used PowerBI, where the data was displayed using a Sankey diagram. The diagram represented the most common paths, showing screen transitions as flows between nodes (screens) and the number of users following each transition.
To respond to this business question effectively the first thing that needs to be done is collect the necessary information from the app. For this purpose, each time a restaurant is searched and selected in the search screen of the app, the data concerning the type of restaurant needs to be stored. This occurs in the 'restaurant_search_types' collection in the app's Firestore, where each search group of types is stored in each Database entry.
This information is gathered by out API, which converts it into a map with keys for each restaurant Type, and values that count the amount of times that restaurant type was searched in total.
With this map it will be easy to evaluate the most searched for restaurants, as it will be possible to compare directly the amount of times each type was searched for using the RestaU App.
The resulting converted information will be displayed using a bar graph in Power BI, where each bar will represent a type of restaurant, and the y-axis will show the amount of times it was searched for.
Here we can see the domain model with the principal concepts that were needed for the sprint. Here we got the main concept being a restaurant with it's name, a name to identify easily a location, an opening date, latitude, longitude, average rating and an image url. A restaurant has a schedule which consists of 7 days with a opening and closing hour. A restaurant has a set of categories which it belongs to and a user can like a set of categories. A user has an email, name and a profile picture url. A user has a set of liked restaurants. With this understanding of the context and the elements of it. The non relational document schema was made.
In this schema we used the same concepts from the domain model but used the NoSQL ability of having special data types such as lists and maps to portray the information that consisted of multiple or complex relations. This can be seen in the schedules, which is now a map with an entry for every day, consisting of a map with the starting hour and the closing hour. Additionally, in restaurants, their categories are now portrayed as an array of strings, this can also be seen in the user's liked categories. Finally, in users, their liked restaurants are a list of restaurant ids.
The deployment diagram above describes the different environments that make up the RestaU app, and the way they interact with each other. As we follow a client-server architecture pattern, a user device will run the RestaU App, having its View, ViewModel and Model. The app also connects to the Maps library through the View, and the Firebase library through the model. The RestaU app uses external components from the user device, such as the preferences component, that stores locally some of the user's preferences, and the location, used primarily by the map funcionality. The user device contacts other external services that help the app function, like the Google Maps API, through the HTTPS protocol. Additionally, it interacts with Firebase and three of its services: the firebase authentication, the firebase storage, and the firebase database.
Another independent user device is used to run the analytics dashboard, through the execution environment of Power BI. This device connects to the analytics pipeline, in the form of a E2-micro Ubuntu cloud server, that has a Fast API environment with the analytics engine and a PostgreSQL environment with the analytics database. This cloud server also connects to Firebase, but only to the Firebase Database service, in order to extract the necessary information for the analytics.
In the previous diagram we can see the different components developed in the Kotlin app, here they are portrayed at a low level showing the interactions among the 5 principal developed elements (views [blue], view models [yellow], use cases [pink], use cases groups (grouped by similar concepts and purpose) [light yellow] , repositories [brown] and data sources [beige]). Each of these components (except for views), exposes methods to be used by the upper layers. Repositories contain reference to API or DAO to access data sources and provide the main functionalities for data access for use cases. Use cases handle and encapsulate business logic and actions repeated by multiple view models and have references to the repositories to modify data. View models are in charge of managing view logic and maintain the UI's state (information managed by the visual elements of the app), also, they call the use cases business logic and with it perform changes on the model, this is done through references to these classes and result on state updates. Finally, views bind themselves to view models through references to them and in an observer pattern-like way receive notifications of changes to update the views. Also, views use the view models’ methods to communicate user events to make changes on the application data (state and model). To manage all the references and dependencies, we used the Dagger Hilt library to manage dependency injection.
As it might be already obvious, the architectural pattern used is MVVM (Model, View, View Model) and we used the Google's "Modern App Architecture". Both the pattern and the architecture allowed to make the app scalable, which we noticed during development because, while including new features, previous elements were not affected, and the app grew quickly. With both, we were also able to define (at different scopes) boundaries and specific concerns for each part of the app which favored modularity.
The modern app architecture proposes 3 layers UI layer, Domain layer and Model layer, compatible with MVVM and seen in the previous diagram. The UI layer is in charge of displaying data to the user and responding to changes in the context (user, network and others). This layer contains 2 of the MVVM components, view which represents the visual elements and the user interface of the app and hears the view model for state changes. The view model which acts as the state holder which holds state, modifies it, shares it with the view and manages view logic. The next 2 layers would be equivalent to the MVVM’s model layer. The modern app architecture shows the domain layer which consists of invokable business logic classes which have methods that could be used in multiple view models and make the project's components more reusable. In this layer we find our model and classes called use cases, each use case is responsible of doing a single action/operation. The last layer is the data layer which is where the app creates and modifies data. This layer is made up of repositories which connect with our data sources (firestore, location services, preferences and others).
In general, this architecture was used to separate concerns and facilitate development, debugging, modularity and modifiability which benefitted the work pace throughout the sprint. Additionally, this architecture benefits the unidirectional data flow pattern which can be seen in the shown diagrams, where data flows from data sources to the UI and events go in the opposite direction. Unidirectional data flow maintains data consistency and facilitates debugging. The architecture also helped with maintaining single sources of truth which defined owners of data, and these exposed inmutable data and methods to modify it which protected data and centralized the access. This was done principally with view models and data sources along with repositories where VMs were the owners of the state and data sources the owners of data.
More Information and Source (Android Developers)
In the following diagram we can asses the the MVVM (Model-View-ViewModel) architecture followed by the Flutter implementation that organizes the application into three main layers: View, ViewModel, and Model. The View layer includes user interface screens such as RegisterScreen
, LogInScreen
, and MapScreen
, which interact with their respective ViewModels. The ViewModel layer manages the business logic, processing data from the Model and updating the View. Each ViewModel, such as AuthViewModel
or SearchViewModel
, connects to repositories like AuthRepository
or RestaurantRepository
to handle authentication, restaurant data, and navigation.
The Model layer is responsible for data management and interacts with external sources like Firebase and local sensors. This separation of concerns allows for better maintainability, scalability, and testability of the app. The ViewModel serves as an intermediary, ensuring data flows seamlessly from the Model to the View while keeping the business logic and UI independent.
In the next diagram we illustrate again the MVVM (Model-View-ViewModel) architecture, showing the interaction between the UI Layer, ViewModel Layer, and Data Layer. The UI Layer contains the user interface elements (View) which display the UI state and capture user events. These events are passed to the State holders (ViewModel), which handle business logic and manage the state of the UI.
The Data Layer (Model) consists of repositories and data sources. Repositories are responsible for invoking the appropriate data sources (such as a database or API) based on the ViewModel's requests. The data sources then return the necessary data, which is passed back through the repositories to the ViewModel and, ultimately, the UI layer. This separation of concerns ensures clean, maintainable code and a clear flow of data throughout the application.
class GetUserObject(
private val usersRepository: UsersRepository,
private val authRepository: AuthRepository
) {
suspend operator fun invoke(): User {
val user = authRepository.getCurrentUser()
val userObject = usersRepository.getUser(user?.email ?: "")
return userObject
}
}
In the app we used the facade pattern to provide a class with a method that hides an operation that involved the combination of 2 repositories (each with multiple operations) to make a specific operation with specific repository methods. It is a facade because it avoided having to use the repositories independently, having to make 2 separated use cases to use in the view model, and consequently, having to manage these 2 use cases objects and the related operation in the view model. These previously mentioned process would have involved business logic in the view model which would have violated the proposed architecture. Also, it would have made the view model more coupled with the use cases and repositories. Now, with the facade pattern, only the necessary operation is shown. In this case, the GetUserObject
use case would be the facade and the repositories the complex data subsystem classes. The invoke()
is calling the getCurrentUser()
method of the AuthRepository
and the getUser()
method of the UsersRepository
to combine their result in a single one useful for the view models that use the GetUserObject
use case. This favors the separation of concerns, modularity, debugging and maintainability.
The use of the Singleton pattern for RestaurantsRepository
, as shown in the diagram, is justified to ensure that the entire application accesses a single, centralized instance of this repository. This is crucial to avoid the creation of multiple instances that could lead to inconsistencies in handling restaurant data, such as duplicated database requests or unnecessary memory usage. By having a single RestaurantsRepository
, all operations related to fetching restaurant data, such as the GetRestaurants and GetOpenRestaurants use cases, rely on the same data source, optimizing resources and making the code easier to maintain.
Here we create the unique instance (singleton) of RestaurantsRepository
@Provides
@Singleton
fun provideRestaurantsRepository(db: FirebaseFirestore): RestaurantsRepository {
return RestaurantsRepositoryImpl(db)
}
Also through dependency injection using Dagger Hilt we can use the same instance of the RestaurantsRepository
in different classes:
class GetRestaurants(
private val restaurantsRepository: RestaurantsRepository
) {
class GetOpenRestaurants(
private val restaurantsRepository: RestaurantsRepository
) {
The use of the Singleton pattern in the project is evident through the implementation of the RestaurantsRepository
, where a single instance of this repository is created and shared across the entire application. This pattern ensures that multiple components, such as use cases and ViewModels, all rely on the same instance, preventing the duplication of objects and centralizing access to restaurant-related data. The Singleton guarantees consistency in the data flow and optimizes resource usage, as it avoids unnecessary instantiations of the repository, which could lead to performance issues or memory overhead. Additionally, by managing the repository's instance centrally, the project becomes more maintainable, as any changes to the repository's behavior are propagated across all components that depend on it without the need for redundant modifications. This pattern is reinforced by the use of dependency injection (DI), ensuring the repository is provided only once throughout the application's lifecycle.
The project adopts the Repository Pattern to create a clear separation between the data access layer and the business logic. In this case, the RestaurantsRepository
interface defines the contract for retrieving restaurant data through methods like getAllRestaurants()
and getOpenRestaurants(day: String, time: Int)
. This ensures that the business logic can interact with the repository without needing to know the underlying details of how the data is stored or fetched. The concrete implementation, RestaurantsRepositoryImpl
, handles the specifics of interacting with FirebaseFirestore
, abstracting away the complexities of querying the database and maintaining a consistent data interface throughout the application.
This pattern brings several benefits to the project. By abstracting the data access logic into a repository, the system is more modular and flexible, for example if the data source needs to be changed (for instance, moving from FirebaseFirestore
to another database), only the implementation needs to be updated, leaving the business logic untouched. It also improves maintainability by centralizing the data access logic in one layer, reducing duplication and improving the ease of managing future data-related changes.
The Repository Pattern is a design pattern used to abstract the data access layer of an application, allowing it to separate business logic from the code that accesses data from various sources (such as databases, web services, or APIs). This pattern is composed of three key layers:
- Bussines logic layer. This place is hold by the HomeScreen, that doesn't care where the restaurant data is coming from. It calls the
RestaurantRepository
to get the list of restaurants.
class _HomeScreenState extends State<HomeScreen> {
final FirestoreService firestoreService = FirestoreService();
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home screen"),
),
body: FutureBuilder<List<Map<String, dynamic>>?>(
future: firestoreService.getAllRestaurants(),
...
}
}
- Repository layer. The
RestaurantRepository
manages interactions between the business logic and the data source (FirestoreService
). It contains the functiongetAllRestaurants
that fetchs the restaurants in theFirestoreService
.
class RestaurantRepository{
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Fetch all restaurants
Future<List<Map<String, dynamic>>> getAllRestaurants() async {
try {
QuerySnapshot querySnapshot = await _db.collection('restaurants').get();
return querySnapshot.docs.map((doc) => doc.data() as Map<String, dynamic>).toList();
} catch (e) {
print(e.toString());
return [];
}
}
}
- Data access layer: The
FirestoreService
directly handles Firestore queries and returns raw data. It’s entirely encapsulated, meaning the rest of the app doesn't know it's using Firestore.
The Adapter pattern is used in the project to allow the Restaurant
model to be transformed into a Marker
that can be displayed on Google Maps within the Flutter app. This transformation is necessary because Google Maps requires a specific format (i.e., the Marker
object), while the Restaurant
class contains the data that we want to represent on the map. Instead of changing the Restaurant
model or creating redundant logic elsewhere, the adapter provides a clean, isolated solution.
In this case, the RestaurantMarkerAdapter
acts as the adapter, taking in a Restaurant
object and converting it into a Google Maps Marker
. It encapsulates the logic necessary to manage the mapping of restaurant data to markers, including defining how the marker icon should appear and what happens when the marker is tapped.
Here's the code where the adapter is applied:
class RestaurantMarkerAdapter {
final Restaurant restaurant;
final BuildContext context;
RestaurantMarkerAdapter({required this.restaurant, required this.context});
Marker toMarker() {
return Marker(
markerId: MarkerId(restaurant.name),
position: LatLng(restaurant.latitude, restaurant.longitude),
icon: _selectIcon(restaurant),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: CustomInfoWindow(restaurant: restaurant),
);
},
);
},
);
}
BitmapDescriptor _selectIcon(Restaurant restaurant) {
// Logic to select the icon based on the restaurant
return BitmapDescriptor.defaultMarker;
}
}
By using the Adapter pattern, the RestaurantMarkerAdapter
isolates the transformation logic, making the code easier to maintain and scale. Any changes to how the Marker is generated or displayed can be made within this adapter class, without affecting other parts of the system. This promotes separation of concerns and modularity.
The singleton pattern is used for the UserViewModel
, which also makes UserRepository
a singleton. This pattern is appropriate in this instance because it garantees the properties regarding the user can only be accessed using the NavigationScreen
. With this, only one instance of a User
can exist, therefore their related services are restrained to a single point and can be accessed easily. As the information regarding a User
can be sensitive, it is better the information can only be accessed through a singleton.
Here is the code of the NavigationScreen
, that creates a unique instance of UserViewModel
:
class NavigatorScreenState extends State<NavigatorScreen> {
UserViewModel activeUser = UserViewModel();
LogInViewmodel livm = LogInViewmodel();
// ...
}
By limiting access to the user instance to only the NavigationScreen, all other screens that require user data are forced to reference the same instance, reinforcing data integrity and secure access throughout the application.
In this sprint we implemented a functionality that uses the phone's microphone to listen the users voice to fill the search field with their words when searching for restaurants in the search screen.
In the map screen we implemented a feature that responds to the "Is there a new restaurant nearby that matches the user's 'liked' cuisine types?" business question. The map shows with a special icon the location of a restaurant that is within a radius of the user if it is new, if the user hasn't liked it yet and if any of the restaurant's categories is within the user's liked categories.
For the context aware functionality, we made a functionality that depending on the location of the user and the established search radius, it will automatically adapt and show the restaurants near the user. This will improve the user's experience by narrowing down the restaurant search space by showing only the restaurants the user is willing to reach/transport to. This will allow the user to find a restaurant within distances he/she feels comfortable with.
For the smart feature we made a section in the home screen that recommends the user restaurants he/she might like and improve their experience through the highlighting of restaurants that match their registered likings in the app. He/She will see this information by just pressing the "For you" button in the home screen.
For this functionality, we made a login and register screen for the users to enter or create an account for registering liked categories, give restaurants "likes" and get recommendations based on their likings.
For functionalities that use external services, we made multiple o them. One of them uses the google maps code API and the google play map services to show the user the locations of restaurants near them, in addition to their surroundings and points of interests that will help them understand where they are and find a restaurant they desire. Also, we used a firebase database for persisting the user's, information, restaurant's information, analytics events and information needed for the functioning of the application. This service is used to track the users likes, liked categories, list the restaurants, filter them and find their location. Finally, we used firebase storage to store image files for the images that belong to restaurants and the images that are assigned to represent the users. With this storage we have a permanent non-changing access to images that will attract users to a restaurant and help them recognize food services they are looking for.
In this sprint we also implemented a list of currently opened restaurants that uses the phone's time and compares this value with the schedules of the restaurants. This allows the user to know what is opened at a moment and avoid uncomfortable situations such as getting to a restaurant and finding out it is closed. We also implemented a list of all restaurants for the user to get a broader scope of all possibilities. To support the functionality mentioned in part a. we implemented the screen that allows the user to find restaurants using keywords, see the restaurants he recently searched for and erase their history. Another implemented feature was a list of liked restaurants for the user to only see what he has been interested on or has enjoyed in the past.