Skip to content

This is a course- and program-matching Android application for University of Toronto students.

Notifications You must be signed in to change notification settings

CSC207-2023Y-UofT/course-project-course-match-daddy

Repository files navigation

Course Match Daddy

freestocks-hRVrvH9-dG0-unsplash


License: MIT

Front-End (Android) Development Technologies

Android Studio Java

[Coming Soon] Back-End (Web) Development Technologies

Node.js Express.js Flask MySQL Sequelize ORM MongoDB Apollo-GraphQL React Jest Heroku

[Coming Soon] Data Engineering, Analytics, and Science Technologies

Python Jupyter Notebook Numpy Pandas Matplotlib scikit-learn Spark


Design Document Link: Design Document

GitHub Repository Link: Codebase Repository

GitHub Project Board Link: Project Board

Web Application Link: [Coming Soon]

(Google Slides) Presentation Link: Presentation


Table of Contents


General Information

Description

Our software application’s domain is a course-matching application for students. Specifically, our application aims to match University of Toronto students with courses and programs based on students’ academic histories, preferences, and desired learning outcomes. We hope to streamline students’ course- and program-selection processes to reduce their cognitive load of navigating an overwhelming breadth and depth of the course and program offerings at the University of Toronto.

Wireframes

Application Wireframes Figure 1: Application wireframes.

Screenshots

Screenshots Figure 2: Application screenshots.


Application Features

This application has the following features:

  • Users can create an account.
  • Users can provide data on their academic histories, preferences, and desired learning outcomes.
  • The application can recommend University of Toronto courses and programs to users based on their provided data; the users can indicate whether or not they 'like' the recommendation.
  • The users can save and edit their data and recommendations locally to their smartphones for later viewing.

Team, Roles, and Contributions

The following individuals were involved in the development of this software application:

  • Uyiosa Iyekekpolor
    • Role: Data Specialist and Software Developer
    • Contributions: Uyiosa was responsible for the first user story. Specifically, he handled the development of the application's code related to its sign-up and login features, and the development of the application's algorithm. Additionally, Uyiosa spearheaded most of the views' design and corresponding activity files' implementation along with Dev.
  • Gagandeep Singh Lubana
    • Role: Software Developer
    • Contributions: Gagan was responsible for the second user story. Specifically, he handled the development of the application's code related to collecting survey data from the user.
  • Lavya Vaishno
    • Role: Software Developer
    • Contributions: Lavya was responsible for the third user story. Specifically, he handled the development of the application's code related to displaying recommended courses and programs within the application's carousel.
  • Dev Vora
    • Role: Software Developer
    • Contributions: Dev was responsible for the fourth user story. Specifically, he handled the development of the application's code related to courses and programs. Additionally, Dev spearheaded most of the views' design and corresponding activity files' implementation along with Uyiosa and was a leader within the team.
  • Manav Singh
    • Role: Data Specialist and Software Developer
    • Contributions: Manav was responsible for the fifth user story. Specifically, he handled the development of the application's code related to displaying the user's saved courses and programs within the 'Courses' and 'Programs' views. Manav's web-scraping of courses' and programs' data using a Python script was paramount to the success of this project.
  • Jaspreet Khela
    • Role: Team Lead and Software Developer
    • Contributions: Jaspreet was responsible for the sixth user story. Specifically, he handled the development of the application's code related to updating the user's settings (including their survey data). Additionally, he was the team's 'Chief Solutions Architect' and 'Tech Lead' given that he had the most experience developing deployable software and, in particular, Android applications within the team. His contributions primarily were designing the application in adherence to Clean Architecture and SOLID Design Principles; creating application wireframes; teaching command-line interface Git usage processes, Android application development, and web scraping to his teammates; setting up the GitHub repository, including the application's packaging structure, project board, development milestones, development issues, and documentation seen within the README.md file; leading weekly team meetings, including solving application development issues; and crafting the project presentation.

GitHub Repository Specifications

The GitHub repository is structured as a 'Navigation Drawer Views Activity' project within Android Studio. Within the application's source code, files are organized based on their relation to clear architecture layers, whether or not they are an abstract class, and whether or not they are an interface.


Installation and Usage

The application can be run locally on our computer or an Android smartphone device (after following the installation instructions that are specified below).

To install the application, first, clone the repository to our machine using the following Git commands within our command-line interface:

cd <desired-project-directory-pathway>
git clone https://github.com/CSC207-2023Y-UofT/course-project-course-match-daddy.git
cd 'course-project-course-match-daddy'

Then, open our project folder in Android Studio and run the Android smartphone emulator.


Codebase Architecture Overview

The following outlines the architectural scaffolding of our software application in an effort to design, implement, and maintain a robust and scalable application. We have categorized our classes and interfaces using clean architecture categories, including - from most to least fundamental - by entities, use cases, gateways, controllers, and presenters; the last three categories form a single layer within our software application’s architecture hierarchy. This design choice constitutes packaging by layers. Additionally, SOLID Design Principles were adhered to when designing the content and scope of our codebase’s classes and interfaces. Recall that the SOLID Design Principles are the Single-Responsibility, Open-Closed, Liskov, Interface Segregation, and Dependency Inversion principles.

The Class-Responsibility-Collaboration (CRC) cards shown within our design document outline the high-level abstract classes and interfaces that a category defines. A card includes the class’s/interface’s name, responsible party, responsibilities, collaborating classes/interfaces, (private and public) attributes, and (private and public) methods. The class cards are mostly abstract classes that either are related to or implement specific interfaces. For example, the high-level (abstract) ‘User’ entity class is related to a ‘LoggedInUserInterface’ interface which is implemented within a lower layer’s abstract class (e.g. a class within the ‘Use Case’ layer); this interface enables dependency inversion with less fundamental layers within our software application’s architecture hierarchy in congruence with SOLID Design Principles.

Classes and interfaces were designed to adhere to SOLID Design Principles. Each class and interface has a single responsibility; in other words, each class and interface has high cohesion, which is a goal of effective Object-Oriented Programming (OOP) design. Classes were defined to be abstract so that they, like interfaces, could be extended, not modified, by implementing subclasses, thus adhering to the Open-Closed principle. Moreover, since classes’ and interfaces’ methods were designed and implemented using abstract class instances, substituting an instance of a parent object with a child object is viable, thus adhering to the Liskov Substitution principle. Additionally, interfaces are bijectively related to use cases, thus adhering to the Interface Segregation principle. Lastly, our use of interfaces between classes to achieve dependency inversion enables low coupling between classes, which is also a goal of effective OOP design.

Classes and interfaces were packaged by their associated Clean Architecture layers as described above. This design choice was motivated by the fact that our application is a monorepo with many granular and fungible non-use-case classes and interfaces, not an n-tier or microservices application. Specifically, packaging classes and interfaces by features, components, or whether or not they facilitate API requests would limit their reusability, especially that of more fundamental classes and interfaces.

The following Unified Modeling Language (UML) Diagram diagram depicts the high-level packaging of our application’s classes and interface based on Clean Architecture layers, with classes’ and interfaces’ details (i.e. data attributes, methods, etc.) described within the CRC cards below.

Course Match Application's UML Design Figure 3: Application high-level UML diagram.


Application Database Specifications

We used .csv files for our minimum viable product (MVP) implementation of our user stories. For future development, we plan to implement an SQL relational database to store API-provided and user-generated information.


Application Machine Learning Model Specifications

[Coming Soon]


Application Design Information

The following application design topics and some of their content were inspired by/referenced from a previously-submitted ChatGPT-powered coding exercise in order to facilitate a deeper understanding of the design of our application.

Application Use Cases

Here are use cases for the given user stories:

  • User Story 1: Account Creation
    1. User opens the application.
    2. User enters their email address, username, and/or password.
    3. User clicks on the 'Sign Up' or 'Login' buttons.
    4. System validates the entered information, checks for existing accounts with the same email, and/or creates a new account.
    5. System redirects the user to the survey page.
  • User Story 2: Initial Survey
    1. User answers questions about their academic history, preferences, and desired learning outcomes.
    2. System stores the survey responses in the user's profile.
    3. System uses the survey data to generate personalized course and program recommendations.
  • User Story 3: Course Recommendations Carousel
    1. User sees a 'Recommended Courses' section on their dashboard.
    2. User swipes right on a course card to indicate interest or swipes left to indicate disinterest.
    3. System updates the user's preferences based on their swipes and refines the recommendations accordingly.
    4. System loads new course cards in the carousel based on the updated recommendations.
  • User Story 4: Course Details
    1. User clicks on a course card from the list of recommended courses.
    2. System displays a detailed page for the selected course, including its content, text-based description, and related metadata.
    3. User can read through the course information and click a button to go back to the list of courses.
  • User Story 5: Viewing Saved/Recommended Courses and Programs
    1. User clicks on the 'Courses' or 'Programs' tab in the navigation menu.
    2. System displays a list of recently saved or recommended courses/programs based on the user's interactions and survey responses.
    3. User can scroll through the list, view course/program cards, and click on a card to learn more about a specific course/program.
  • User Story 6: Updating Survey Data
    1. User navigates to the 'Settings' section.
    2. User views their provided survey data and makes necessary updates.
    3. User clicks on the 'Update Preferences' button.
    4. System validates the updated information and adjusts the recommendations based on the new preferences.

These use cases outline how users interact with the application based on the provided user stories below. They cover actions like account creation, survey completion, viewing course recommendations, accessing course details, managing saved/recommended courses, and updating survey data.


Application User Stories

The following user stories breakdown the software usage scenarios specifications into discrete development objectives:

  1. As a user, I want to be able to create an account so that my application-related metadata persists between application usage sessions.
  2. As a user, I want to be able to complete an initial survey that briefly specifies my academic history, preferences, and desired learning outcomes so that I may streamline course and program recommendations for me.
  3. As a user, upon completion of the initial survey, I want to be able to view a carousel of recommended course cards for me that I may swipe right on to indicate interest in the course or swipe left to indicate disinterest in the course so that I can receive relevant course and program recommendations.
  4. As a user, I want to be able to select a course’s card to view its content, text-based description, and related metadata so that I can learn more about it.
  5. As a user, I want to be able to select a ‘Courses’ or ‘Programs’ tab to view recently saved or recommended courses or programs, respectively, so that I may decide on which courses or programs that I would like to enrol in.
  6. As a user, I want to be able to view my provided survey data so that I can update my preferences to get new course and program recommendations.

Application Correctness

General Information

In the context of software development, the correctness of an application refers to whether the application behaves as intended and meets its specified requirements. This concept encompasses various aspects that ensure the application's accuracy, reliability, and adherence to its intended purpose. Correctness is a fundamental quality attribute that developers and testers strive to achieve during the software development lifecycle.

Here are some key aspects of an application's correctness:

  1. Functional Correctness: The application should perform its intended functions accurately and consistently. It should produce the expected outputs and responses for various inputs and scenarios, without errors or unexpected behavior.
  2. Requirements Compliance: The application should meet the requirements outlined in its design specifications, user stories, or project documentation. This involves ensuring that all specified features and functionalities are properly implemented.
  3. Error Handling: The application should handle errors, exceptions, and edge cases gracefully. This involves providing clear error messages and recovering from errors in a way that doesn't disrupt the user experience.
  4. Data Integrity: The application should accurately store, retrieve, and manipulate data. Data integrity involves ensuring that the data remains accurate, consistent, and reliable throughout its lifecycle.
  5. Security: The application should protect sensitive data and maintain security standards. It should prevent unauthorized access, mitigate potential vulnerabilities, and follow best practices for secure coding.
  6. Performance: The application should respond promptly and perform efficiently under expected load conditions. It should not suffer from slow response times, excessive resource utilization, or bottlenecks.
  7. Usability: The application's user interface should be intuitive and easy to use. Users should be able to interact with the application without confusion or frustration.
  8. Interoperability: If the application interacts with other systems or software components, it should do so seamlessly and without conflicts. It should adhere to relevant standards and protocols.
  9. Maintainability: The application's code should be well-organized, modular, and easy to maintain. This involves writing clean, documented code that can be easily understood and modified by other developers.
  10. Testing: Rigorous testing is essential to validate the correctness of an application. This includes unit testing, integration testing, regression testing, and user acceptance testing to identify and address issues before deployment.

Achieving correctness requires a combination of careful design, implementation, testing, and quality assurance practices. Developers often work closely with quality assurance professionals and follow development methodologies that emphasize testing and validation to ensure that the application is correct and reliable for its intended users.

Application-Specific Information

We have put considerable thought and effort into designing our Android course-matching application. We have employed Clean Architecture principles and adhered to SOLID Design Principles, which are key factors in ensuring the correctness, maintainability, and scalability of our application. The correctness of our application in the context of the provided information and user stories can be analyzed as follows:

  1. User Account Creation: The user story involving account creation indicates that users can create accounts to persist their application-related metadata. User data is securely stored, account creation and authentication processes are robust, and privacy considerations are taken into account, all of which contribute to the correctness of this feature.
  2. Initial Survey and Recommendations: The ability for users to complete an initial survey and receive course and program recommendations based on their academic history and preferences is aligned with our application's purpose. Correctness here entails accurately capturing user preferences and providing relevant recommendations based on that data.
  3. Viewing Recommended Courses: The ability to view recommended course cards and interact with them by swiping indicates a user-friendly approach to presenting recommendations. Ensuring that users can interact smoothly with the carousel and that the swiping gestures are responsive and intuitive contributes to the correctness of this feature.
  4. Viewing Course Details: Allowing users to view detailed information about a selected course is crucial for informed decision-making. Ensuring that course details, descriptions, and related metadata are accurately displayed and accessible contributes to the correctness of this feature.
  5. Viewing Saved and Recommended Items: The 'Courses' and 'Programs' tabs allow users to view recently saved or recommended courses and programs. The correctness of this feature involves correctly displaying and organizing the user's selections and recommendations.
  6. Updating Survey Data: Enabling users to update their survey data for new recommendations is valuable. Ensuring that the updated data is accurately captured and used to generate relevant suggestions contributes to the correctness of this feature.

In addition to the user stories, our focus on adhering to SOLID design principles and Clean Architecture layers is likely to enhance the correctness of our application. These principles promote modularity, maintainability, and clear separation of concerns, all of which contribute to the overall correctness and reliability of our codebase.

To ensure the application's correctness further, it's recommended to conduct thorough testing, including unit tests, integration tests, and user acceptance testing. Testing will help identify and address any issues or discrepancies that might affect the accuracy and reliability of our application's functionality.

Overall, our application's design and adherence to software engineering best practices suggest a solid foundation for creating a correct, robust, and scalable course-matching application for University of Toronto students.


Application Testing

General Information

Application testing is a critical phase in the software development lifecycle that involves evaluating a software application to identify defects, errors, and issues. The primary goal of testing is to ensure that the application functions correctly, meets its requirements, and provides a reliable and satisfactory user experience. Testing helps uncover bugs and vulnerabilities before the application is deployed to users, reducing the risk of operational issues and improving overall software quality.

Here are the key aspects of application testing within the context of software development:

  1. Types of Testing:
    • Unit Testing: Involves testing individual components or units of code in isolation to ensure they work as expected. This is typically done by developers using frameworks and tools specific to the programming language.
    • Integration Testing: Focuses on testing interactions between different components or modules to ensure they work together as intended.
    • Functional Testing: Tests the application's functionality against its requirements to verify that it performs its intended tasks correctly.
    • Regression Testing: Re-tests the application after making changes to ensure that new modifications haven't introduced new defects or affected existing functionality.
    • Performance Testing: Evaluate the application's responsiveness, scalability, and resource usage under different load conditions.
    • Security Testing: Identifies vulnerabilities and security weaknesses in the application to ensure that sensitive data and user information are adequately protected.
    • Usability Testing: Involves evaluating the application's user interface and overall user experience to ensure it's intuitive and user-friendly.
    • User Acceptance Testing (UAT): Conducted by end-users to ensure that the application meets their needs and requirements before it's released.
    • Automated Testing: Involves writing scripts or using tools to automate the testing process, making it faster, repeatable, and more efficient.
  2. Testing Process:
    • Test Planning: Defining the scope, objectives, and strategies for testing. This includes identifying test cases, test data, and testing environments.
    • Test Design: Creating detailed test cases based on requirements, user stories, and use cases. Test cases outline the steps to be taken and the expected outcomes for each scenario.
    • Test Execution: Running the test cases on the application and recording the results. This phase involves identifying defects and reporting them to the development team.
    • Defect Tracking: Documenting and managing defects found during testing. Developers address these defects, and the cycle repeats until the defects are resolved.
    • Test Reporting: Summarizing the results of testing, including pass/fail status, defect summaries, and overall assessment of the application's quality.
  3. Test Environments:
    • Development Environment: Where developers write and test code before integrating it into the main codebase.
    • Testing Environment: A dedicated environment where testers perform various types of testing. It closely resembles the production environment but is isolated from it.
    • Staging Environment: A near-production environment where final testing is conducted before deployment to ensure that the application works in a real-world scenario.
  4. Challenges and Benefits:
    • Challenges: Testing can be time-consuming and resource-intensive. It requires careful planning, coverage of diverse scenarios, and coordination among development and testing teams.
    • Benefits: Effective testing improves software quality, reduces the likelihood of bugs reaching production, enhances user satisfaction, and ultimately saves time and money by catching issues early in the development process.

Overall, application testing plays a crucial role in delivering high-quality software that meets user expectations and functions reliably in various scenarios. It's an ongoing process that helps ensure the application's correctness, security, performance, and usability.

Application-Specific Information

In particular, Android Studio has the capacity to perform the following types of tests:

  • Functional Testing:
    • Unit tests were written for the application within the tests folder.
  • Performance Testing:
    • [Coming Soon]
  • Accessibility Testing:
    • [Coming Soon]
  • Compatibility Testing:
    • [Coming Soon]

Screen Shot 2023-08-13 at 8 58 33 PM Figure 4: Application's unit test coverage report. Note that few tests were written for the gateway-, controller-, and presenter-layer classes and subclasses due to time constraints.

Furthermore, some tests which were required to interact with the database were done using Android, so they were not included in the coverage report. However, you will be able to see this information under the androidTest folder (adjacent to test folder).

Implementing unit tests is crucial to ensure the reliability and correctness of our Android application. Unit tests focus on testing individual units of code in isolation, such as methods, functions, or classes. Given the complexity of our application, it's important to have a solid unit testing strategy that covers various aspects of our user stories and architectural design. Each user story-related segment of the codebase was tested as followed:

  1. User Account Creation:
    • Tested the account creation process by mocking the necessary components (e.g., user data storage, authentication) and asserting that the account was created successfully.
    • Verified that the user's application-related metadata persisted between sessions.
  2. Initial Survey and Recommendations:
    • Tested the survey completion by simulating user input and checking if the survey data was correctly captured.
    • Mocked the recommendation algorithm and validated that the recommended courses aligned with the survey input.
  3. Viewing Recommended Courses:
    • Tested the carousel's functionality, including swiping right/left on recommended courses.
    • Ensured that the carousel correctly displayed the recommended course cards.
  4. Viewing Course Details:
    • Tested the selected course's information retrieval and presentation.
    • Verified that the content, description, and metadata of the selected course are accurately displayed.
  5. Viewing Saved and Recommended Items:
    • Test the functionality of the 'Courses' and 'Programs' tabs.
    • Ensure that the recently saved or recommended items are displayed correctly.
  6. Updating Survey Data:
    • Test the process of updating survey data and preferences.
    • Verify that the updated data reflects the user's new preferences and that new recommendations are generated accordingly.

When writing unit tests, we also considered the following guidelines:

  • Isolation: Unit tests should isolate the unit of code being tested. Use mocking frameworks to simulate the behaviour of dependencies and ensure that tests focus solely on the unit being tested.
  • Coverage: Aim for comprehensive test coverage. Test various scenarios, including edge cases, valid inputs, and potential errors.
  • Readability: Write clear and concise test cases with descriptive names that reflect the purpose of the test.
  • Maintainability: As our application evolves, ensure that unit tests remain up-to-date. Regularly refactor tests alongside code changes.
  • Automation: Automate our tests to run as part of our build and deployment pipeline. This ensures that tests are run consistently and automatically.
  • Test-Driven Development (TDD): Consider adopting a TDD approach, where we write tests before writing the actual code. This can help shape our code design and improve test coverage.

Since unit tests are just one aspect of testing, we will also consider future implementations of integration tests, end-to-end tests, and user acceptance testing to ensure comprehensive coverage and the overall reliability of our application.


Application (Memory and Compute) Efficiency

General Information

In the context of software development, application memory and compute efficiency refer to optimizing how an application uses system resources, specifically memory (RAM) and computing power (CPU cycles). Efficiently managing these resources is crucial for creating software that performs well, consumes less energy, and provides a smooth user experience. Efficient software can run faster, use less memory, and require fewer hardware resources, leading to cost savings and improved user satisfaction.

Memory Efficiency: Memory efficiency involves how an application manages and utilizes system memory (RAM) to store data, code, and runtime structures. Efficient memory usage helps prevent memory leaks, reduces the risk of crashes, and improves the application's overall stability and performance.

Key considerations for memory efficiency include:

  1. Memory Allocation: Properly allocate and deallocate memory resources as needed. Unused memory should be released to prevent memory leaks
  2. Data Structures: Choose appropriate data structures that minimize memory usage while still allowing efficient data manipulation.
  3. Caching: Use caching mechanisms to store frequently accessed data in memory, reducing the need to fetch data from slower storage mediums
  4. Buffering: Implement efficient buffering techniques when reading or writing data to and from external sources, such as files or network streams.
  5. Garbage Collection: If using languages with automatic memory management (like Java or C#), ensure that the garbage collector efficiently reclaims memory by collecting unused objects.

Compute Efficiency: Compute efficiency relates to how an application utilizes CPU resources to execute tasks and process data. Efficient use of CPU cycles leads to faster execution times, reduced energy consumption, and better responsiveness.

Key considerations for compute efficiency include:

  1. Algorithm Complexity: Choose algorithms that have optimal time complexity (fast execution) for the specific problem at hand. Avoid algorithms with high time complexity, which can lead to sluggish performance.
  2. Concurrency: Utilize multi-threading or parallelism to take advantage of multi-core CPUs, allowing the application to perform multiple tasks simultaneously.
  3. Task Prioritization: Prioritize tasks to ensure that critical operations receive the necessary computing resources, while less critical tasks yield resources when needed.
  4. Batch Processing: Whenever possible, process data in batches rather than individually. This reduces the overhead of repeated task initiation.
  5. Asynchronous Operations: Use asynchronous programming paradigms to free up the CPU from waiting for I/O operations to complete.
  6. Resource Sharing: Share resources efficiently among different components or users to prevent resource contention and bottlenecks.

Optimizing memory and compute efficiency requires a deep understanding of the application's architecture, programming languages, and underlying hardware. Developers often use profiling tools and performance monitoring to identify bottlenecks and areas for improvement. Striking a balance between memory and compute efficiency is crucial, as over-optimization in one area can negatively impact the other. Efficient software development results in applications that are fast, responsive, and resource-friendly, contributing to a positive user experience and efficient utilization of hardware resources.

Application-Specific Information

Memory and compute efficiency are essential considerations for any software application. The potential memory and compute efficiency aspects of our application are analyzed as follows within the context of our user stories:

  1. Account Creation and Persistence:
    • Memory Efficiency: We use appropriate data structures and libraries to manage user data without excessive memory consumption.
    • Compute Efficiency: For future development, account creation and metadata persistence should be optimized to reduce processing time; we may utilize background threads or asynchronous operations to avoid blocking the main thread.
  2. Initial Survey and Recommendations:
    • Memory Efficiency: Our application efficiently stores and manage survey data to avoid unnecessary memory consumption.
    • Compute Efficiency: For future development, our recommendation algorithms should be optimized for speed and accuracy such as through caching recommendations to avoid redundant computations.
  3. Viewing Recommended Courses:
    • Memory Efficiency: Our application stores and manages course data efficiently, especially when displaying a carousel of recommended courses. For future development, we may employ lazy loading or pagination to help avoid loading excessive data into memory.
    • Compute Efficiency: Our application's UI updates and animations are optimized (by Android Studio) to ensure a seamless user experience.
  4. Viewing Course Details:
    • Memory Efficiency: Our application loads and displays course details on-demand to prevent loading excessive content into memory. Unnecessary resources are disposed of when transitioning between courses.
    • Compute Efficiency: Displaying course details should not cause a significant compute load.
  5. Viewing Saved and Recommended Items:
    • Memory Efficiency: Our application efficiently manages the display of saved and recommended items through the use of HashMap data structures that allow for efficient item retrieval and rendering.
    • Compute Efficiency: Our application's course list rendering and updates are optimized.
  6. Updating Survey Data:
    • Memory Efficiency: Our application updates survey data efficiently.
    • Compute Efficiency: The process of updating survey data and generating new recommendations is not computationally intensive. For future development, we may optimize the recommendation algorithm to balance accuracy and performance.

In addition to these user stories, our application's adherence to SOLID design principles and Clean Architecture layers can contribute to memory and compute efficiency. Separation of concerns, modularization, and low coupling can help we avoid resource-intensive bottlenecks and improve the efficiency of our codebase.

To further enhance memory and compute efficiency, we need to:

  • Implement proper memory management practices, such as releasing resources when they're no longer needed.
  • Optimize image loading and handling to minimize memory consumption and improve loading times.
  • Use appropriate background tasks for data processing, networking, and other resource-intensive operations.
  • Monitor and profile our application to identify and address performance bottlenecks.

By considering both memory and compute efficiency throughout the design and implementation of our Android application, we create a responsive, smooth, and user-friendly experience for our users while ensuring that the application runs efficiently on a variety of devices.


Application Modularity

General Information

Application modularity is a software design principle that promotes breaking down a complex application into smaller, independent, and reusable components called modules. Each module focuses on a specific functionality or feature of the application and can be developed, tested, and maintained independently. Modularity enhances code organization, collaboration among developers, and the overall maintainability and scalability of the software.

Here are the key aspects of application modularity within the context of software development:

  • Benefits of Application Modularity:
    1. Reusability: Modules can be reused in different parts of the application or in other projects, saving time and effort when building new features.
    2. Isolation: Each module operates independently, reducing the chances of one module's errors affecting other parts of the application.
    3. Collaboration: Different developers or teams can work on separate modules simultaneously without interfering with each other's work.
    4. Testing: Modules can be tested individually, making it easier to identify and fix issues. Unit testing can be more focused and thorough.
    5. Maintenance: Updates or bug fixes can be applied to specific modules without affecting the entire application. This simplifies maintenance and reduces the risk of unintended side effects.
    6. Scalability: As the application grows, new modules can be added to accommodate new features or functionalities without significantly impacting existing code.
  • Key Concepts and Practices:
    1. High Cohesion: Modules should have a clear and focused purpose, performing a single, well-defined function. This promotes easier understanding and maintenance of the code.
    2. Low Coupling: Modules should have minimal dependencies on each other. This reduces the risk of changes in one module affecting others.
    3. Interface Design: Well-defined interfaces between modules establish how they communicate and interact. This promotes compatibility and makes it easier to swap out modules when needed.
    4. Encapsulation: Modules should encapsulate their internal details, exposing only the necessary interfaces to interact with them. This prevents unintended direct manipulation of module internals.
    5. Dependency Injection: Instead of modules directly creating instances of other modules they depend on, dependency injection allows modules to receive their dependencies from the outside, enhancing flexibility and testability.
    6. Separation of Concerns: Divide the application's functionality into modules based on different concerns, such as data access, user interface, business logic, and so on.
  • Examples of Module Types:
    1. User Interface Modules: Handle the presentation layer, user interactions, and rendering of the user interface elements.
    2. Business Logic Modules: Implement the core logic of the application, including algorithms, calculations, and data processing.
    3. Data Access Modules: Manage interactions with databases or external data sources.
    4. Utility Modules: Provide common functionalities or helper functions that can be used across the application.
    5. Service Modules: Offer specific services, such as authentication, logging, or messaging, which can be used by other modules.

By adopting a modular approach to software development, developers can create applications that are easier to understand, maintain, and extend. Modular design enhances code quality, promotes collaboration, and contributes to the overall success of a software project.

Application-Specific Information

Our application exhibits modularity in the following ways:

  1. Clean Architecture Layers: Our application's architecture follows the principles of Clean Architecture, which promotes modularity by organizing code into distinct layers: entities, use cases, gateways, controllers, and presenters. Each layer has a clear responsibility, ensuring a separation of concerns and making it easier to maintain and extend the application.
  2. Single Responsibility Principle (SRP): The SOLID principle of SRP is reflected in our design. Each class and interface has a single responsibility, contributing to high cohesion and focused functionality. This modular approach ensures that each component performs a specific task, making code easier to understand and maintain.
  3. Dependency Inversion Principle (DIP): The use of interfaces to achieve dependency inversion enables low coupling between classes. This promotes modularity by allowing classes to depend on abstractions rather than concrete implementations. As a result, changes in one module are less likely to affect others.
  4. Reuse of Components: Our application's granular and fungible classes and interfaces, packaged by Clean Architecture layers, facilitate reuse. The modular structure allows us to easily incorporate existing components into new features, enhancing development efficiency and reducing redundant code.
  5. Separation of Concerns: The division of responsibilities among layers (e.g., use cases, controllers, presenters) demonstrates separation of concerns. Each layer addresses a specific aspect of the application's functionality, contributing to a modular design that can be modified or extended without impacting unrelated components.
  6. Encapsulation and Abstraction: The abstraction provided by interfaces and the encapsulation of functionality within classes contribute to modularity. Abstraction hides implementation details, allowing components to interact without needing to know how each other works internally.
  7. Component Cohesion: Our design promotes high cohesion within individual classes and interfaces. Each entity or use case has a clearly defined purpose, resulting in code that is easier to reason about and less prone to unexpected side effects.
  8. Maintainability and Scalability: Modularity enhances the maintainability and scalability of our application. Changes or updates can be made to specific modules without affecting the entire codebase. This makes it easier to add new features, fix bugs, and adapt to evolving requirements.

In summary, our Android application's architecture and design principles demonstrate a strong emphasis on modularity. The Clean Architecture layers, adherence to SOLID principles, and separation of concerns collectively contribute to a codebase that is organized, maintainable, and conducive to future development and expansion.


Application Extensibility

General Information

Application extensibility is a software design principle that focuses on creating software in a way that allows for the easy and efficient addition of new features, functionalities, or modules without significantly modifying the existing codebase. Extensible applications are designed to accommodate changes and enhancements without causing disruption to the existing system. This flexibility is crucial in keeping software adaptable to evolving user needs and market trends.

Here are the key aspects of application extensibility within the context of software development:

  • Benefits of Application Extensibility:
    1. Future-Proofing: An extensible application can adapt to new requirements and technologies over time, reducing the need for complete rewrites.
    2. Scalability: New features can be added without overhauling the entire application, ensuring scalability and efficient resource utilization.
    3. Customization: Users or developers can extend the application to meet specific needs without modifying the core code.
    4. Market Agility: Applications can quickly respond to changes in the market by incorporating new trends or demands.
    5. Reduced Technical Debt: By designing with extensibility in mind, developers avoid accumulating technical debt caused by constant code modifications.
  • Key Concepts and Practices:
    1. Modularity: An extensible application should be built with modular components that can be easily replaced or extended without affecting other parts.
    2. Abstraction: Design interfaces and APIs that are abstract and stable, shielding users from underlying implementation details.
    3. Loose Coupling: Minimize dependencies between modules to avoid cascading changes when adding new features.
    4. Dependency Injection: Use dependency injection to provide necessary components, making it easier to replace or extend existing functionality.
    5. Open-Closed Principle: Follow this principle, which states that software entities (classes, modules, functions) should be open for extension but closed for modification. This means we can add new functionality without altering existing code.
    6. Plugin Architecture: Design applications to support plugins or extensions that can be loaded dynamically at runtime to enhance functionality.
  • Examples of Extensibility:
    1. Plugin System: Allow third-party developers to create plugins that integrate seamlessly with our application, adding new features without altering the core codebase.
    2. Configurable Workflows: Design workflows or processes that can be configured or customized without modifying the underlying logic.
    3. APIs and Hooks: Provide APIs and hooks that allow developers to integrate their own code or extend existing behaviour.
    4. Themes and Styles: Allow users to customize the application's appearance and behaviour through themes and styles.
    5. Module/Add-on System: Create an architecture that allows users to install and activate new modules or add-ons to enhance functionality.

By designing applications with extensibility in mind, developers can ensure that their software remains adaptable and relevant over time. An extensible application is better equipped to meet the changing needs of users, embrace new technologies, and maintain a competitive edge in the market.

Application-Specific Information

Our application exhibits extensibility in the following ways:

  1. Clean Architecture Layers: The Clean Architecture structure we have employed promotes extensibility by separating concerns into distinct layers. New features or changes can be added in specific layers without requiring modifications to other layers, reducing the risk of unintended side effects.
  2. Single Responsibility Principle (SRP): The adherence to the SRP ensures that each class or interface has a single responsibility. This modularity allows us to extend or modify a specific component without affecting the entire system.
  3. Open-Closed Principle (OCP): Our design follows the OCP by using abstract classes and interfaces that can be extended by subclasses. This allows us to introduce new functionality by creating new classes rather than modifying existing ones, ensuring that existing code remains stable.
  4. Dependency Inversion Principle (DIP): Dependency inversion, achieved through the use of interfaces and dependency injection, allows for the replacement of concrete implementations with new ones. This makes it easier to introduce new features without disrupting the existing codebase.
  5. Interfaces and Abstraction: The use of interfaces and abstract classes provides a clear contract between components. This abstraction allows new implementations to be introduced without affecting the existing interactions between modules.
  6. Modular Components: Our application's architecture is composed of modular components, each with a specific responsibility. This modularity enables the addition of new features or modules without impacting the rest of the application.
  7. Low Coupling: The low coupling between components achieved through interfaces and dependency inversion enhances extensibility. Changes in one component are less likely to affect others, making it easier to extend the application.
  8. Packaging by Layers: Packaging classes and interfaces by Clean Architecture layers contributes to extensibility. New features or changes can be introduced at specific layers, minimizing the impact on other layers and facilitating the addition of new functionalities.
  9. High Cohesion: Each class and interface has high cohesion due to its single responsibility. This design principle ensures that new features can be added without conflicting with existing behaviour.

In summary, our Android application's design and architecture demonstrate a strong foundation for extensibility. The combination of Clean Architecture, SOLID Design Principles, interfaces, and modular components makes it easier to introduce new features, adapt to changing requirements, and extend the application's functionality over time without jeopardizing its stability or existing features.


Application (SOLID) Design Principles

General Information

The SOLID design principles are a set of five principles that guide software developers in creating well-structured, maintainable, and scalable object-oriented software. These principles were introduced by Robert C. Martin and represent a foundation for writing clean and robust code that is easy to understand, modify, and extend. Each principle focuses on a specific aspect of software design, contributing to overall software quality.

Here's a brief overview of each SOLID principle within the context of software development:

  1. Single Responsibility Principle (SRP): This principle states that a class should have only one reason to change. In other words, a class should have a single responsibility or job. This helps in keeping the code focused, easier to understand, and less prone to change due to unrelated modifications.
  2. Open/Closed Principle (OCP): The Open/Closed Principle suggests that software entities (classes, modules, functions) should be open for extension but closed for modification. This means that we should be able to add new functionality without altering the existing code. This principle promotes modularity and reduces the risk of introducing bugs when adding new features.
  3. Liskov Substitution Principle (LSP): The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In simpler terms, derived classes should be able to substitute their base classes without causing unexpected behaviour. This principle ensures that subclasses adhere to the same contract as their parent classes.
  4. Interface Segregation Principle (ISP): The Interface Segregation Principle advises that a class should not be forced to implement interfaces that it doesn't use. In other words, we should design small, focused interfaces tailored to the needs of the classes that implement them. This prevents classes from being burdened with unnecessary methods.
  5. Dependency Inversion Principle (DIP): The Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules; both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions. This principle promotes the use of interfaces or abstract classes to decouple components and make the codebase more flexible and easier to change.

Together, the SOLID principles provide a comprehensive framework for designing maintainable and extensible software. Adhering to these principles helps developers create code that is less tightly coupled, more modular, and less prone to unexpected side effects when modifications are made. By following these principles, developers can achieve better code quality, improved maintainability, and greater ease in accommodating changes and new features in their software projects.

Application-Specific Information

Our application employs SOLID Design Principles in the following ways:

  1. Single Responsibility Principle (SRP): Each class and interface in our application is designed to have a single responsibility. This adherence to SRP ensures that each component focuses on a specific task, making the codebase more maintainable and easier to understand.
  2. Open-Closed Principle (OCP): We have designed abstract classes that can be extended by subclasses. This practice aligns with the Open-Closed principle, allowing us to introduce new functionality by extending existing classes without modifying their core implementations.
  3. Liskov Substitution Principle (LSP): By designing our classes and interfaces to enable substitutability, we are adhering to the Liskov Substitution principle. Subclasses can replace parent class instances without affecting the behaviour of the application, promoting a consistent and reliable system.
  4. Interface Segregation Principle (ISP): Our use of interfaces, especially the bijective relationship between interfaces and use cases, reflects adherence to the Interface Segregation principle. This design approach ensures that clients are not forced to depend on methods they do not use.
  5. Dependency Inversion Principle (DIP): Our use of interfaces enables dependency inversion between different layers of our application. This practice allows higher-level layers to depend on abstractions, promoting flexibility and modularity.

Our application's architecture also aligns with Clean Architecture's principles by categorizing classes and interfaces based on their responsibilities and layers. This separation of concerns helps maintain the integrity of each layer and ensures that changes in one layer do not affect other layers.

Overall, our application demonstrates a strong understanding and implementation of both SOLID principles and Clean Architecture. These principles should help our application remain flexible, maintainable, and adaptable as it evolves over time. Our emphasis on cohesion, low coupling, and layer separation will likely contribute to the success of our application in the future.


Application (Clean) Architecture

General Information

Clean Architecture is a software design approach introduced by Robert C. Martin that emphasizes the separation of concerns, maintainability, and testability of a software system. It aims to create an architecture that is independent of frameworks, databases, and other external details, focusing on the core business logic and user interactions. Clean Architecture helps developers create software that is flexible, modular, and easy to understand, while also promoting the use of modern software engineering practices.

The Clean Architecture consists of concentric circles or layers, each representing a distinct level of abstraction and responsibility. These layers are designed to be independent of one another, with the innermost layers containing the most critical business logic and the outer layers dealing with technical details and user interfaces.

Here are the main components or layers of Clean Architecture:

  1. Entities (Core Entities): At the center of Clean Architecture are the entities, which encapsulate the core business rules and data structures. Entities are independent of external frameworks and technologies. They define the most fundamental business logic and are usually the most stable part of the system.
  2. Use Cases (Interactors or Application Business Rules): Use cases encapsulate the application's business rules and orchestrate the flow of data between the entities and the outer layers. They represent high-level application-specific operations and ensure that the core business logic remains independent of external concerns.
  3. Interface Adapters (Controllers, Presenters, View Models): This layer adapts the use cases and entities to the frameworks and technologies used for presentation and user interaction. It transforms data from the use cases into a format suitable for the user interface, and vice versa.
  4. Frameworks and Drivers (UI, Databases, Web, etc.): The outermost layer consists of frameworks, tools, and technologies that interact with external resources and devices, such as databases, web frameworks, user interfaces, and external services. This layer should be the most replaceable part of the architecture

Key Principles of Clean Architecture:

  1. Dependency Rule: Dependencies between layers should always point inward. Higher-level layers should not depend on lower-level layers. This promotes maintainability and flexibility.
  2. Abstraction Principle: Inner layers are abstract and contain the core business logic. Outer layers are concrete and deal with technical details.
  3. Stable Dependencies Principle: High-level modules (inner layers) should be stable and should not depend on low-level modules (outer layers).
  4. Stable Abstractions Principle: High-level modules should be abstract and should not depend on details (concrete implementations).
  5. Separation of Concerns: Each layer is responsible for a specific concern, and the separation between layers promotes modularity and maintainability.
  6. Testability: The architecture encourages the isolation of business logic for testing without requiring external dependencies.

Clean Architecture is designed to address challenges like codebase maintainability, ease of testing, and adaptability to changes. By following this approach, developers can create software that is both robust and adaptable to evolving business requirements and technological changes.

Application-Specific Information

Our application aligns with the principles of Clean Architecture in the following ways:

  1. Clear Separation of Layers: Our application follows the Clean Architecture approach by categorizing classes and interfaces into distinct layers, such as entities, use cases, gateways, controllers, and presenters. This separation promotes modularity and allows for changes in one layer without affecting others.
  2. High-Level Description of Layers: Our architecture defines layers that align with Clean Architecture's recommended layers: entities (domain models), use cases (application logic), gateways (data access interfaces), controllers (UI logic), and presenters (UI rendering logic). This separation helps in keeping the responsibilities of each layer distinct.
  3. Dependency Inversion: The use of interfaces to define interactions between layers facilitates dependency inversion. This design principle allows higher-level layers to depend on abstractions (interfaces) rather than concrete implementations, which promotes flexibility and extensibility.
  4. Single Responsibility Principle: The application's adherence to the Single Responsibility Principle is evident in the design of classes and interfaces. Each class and interface has a single responsibility, promoting modularization and maintainability.
  5. Modularity and Reusability: Packaging classes and interfaces within their respective Clean Architecture layers promote modularity. This modular structure enhances code reusability since components can be more easily extracted and reused in different parts of the application or even in other projects.
  6. Use Case-Centric Design: The focus on use cases (application-specific actions) separates business logic from presentation details. This design supports easier testing, maintenance, and modification of business rules.
  7. Testability: Clean Architecture emphasizes the ability to test each layer independently. By following this approach, we can write unit tests for individual components without tightly coupling them to other layers.
  8. Low Coupling and High Cohesion: The architecture's use of interfaces and abstractions contributes to low coupling between components and high cohesion within components. This results in a more maintainable and flexible codebase.
  9. Extensibility and Scalability: The use of SOLID Design Principles, along with Clean Architecture's layering, abstraction, and dependency inversion, makes the application more extensible and scalable. New features can be added or existing features modified with minimal impact on the overall system.

In summary, our Android application's architecture aligns well with Clean Architecture principles. The separation of concerns, modularization, dependency inversion, and adherence to SOLID Design Principles contribute to a maintainable, testable, and scalable codebase. This approach will help us handle changes, extend functionality, and maintain a robust application over time.


Application Code Smells

General Information

Code smells are indicators or signs in the source code of a software system that suggest potential design or implementation issues. They are not bugs or errors in themselves but rather patterns or practices that might lead to problems such as reduced maintainability, increased complexity, and potential bugs in the future. Identifying and addressing code smells early can help prevent the accumulation of technical debt and improve the overall quality of the codebase.

Code smells are often identified through code reviews, automated tools, or manual analysis by experienced developers. There are numerous types of code smells, and each points to a specific problem or area of concern. Here are some common examples of code smells:

  1. Long Method: A method that is too long and complex, making it hard to understand and maintain. This is a sign that the method could be broken down into smaller, more focused methods.
  2. Large Class: A class that has too many methods and fields, suggesting it might be doing too much. This can be a hint to refactor the class into smaller, more specialized classes.
  3. Duplicated Code: Repeated code segments throughout the codebase, leading to maintenance issues and making changes error-prone. This should be refactored into reusable functions or classes.
  4. Comments: Excessive or unclear comments might indicate that the code isn't self-explanatory. Clearer code and well-named variables can often eliminate the need for excessive comments.
  5. Complex Conditionals: Nested or convoluted conditional statements that are hard to follow. This suggests the need for simplification and abstraction to improve readability.
  6. Shotgun Surgery: Frequent changes are required in multiple places for a single change in functionality. This indicates a lack of cohesion and might suggest that related code should be grouped together.
  7. Feature Envy: A class that uses the methods of another class excessively, which could mean that the functionality belongs to the other class.
  8. Inappropriate Intimacy: Classes that are too tightly coupled and share too much information. This can lead to reduced modularity and make changes difficult.
  9. Data Clumps: Multiple methods that use the same set of parameters. This might suggest that the parameters should be grouped into a separate data structure.
  10. Switch Statements: Extensive use of switch statements can indicate that polymorphism or other design patterns might provide a more flexible and maintainable solution.

It's important to note that code smells are not always clear-cut indicators of problems, and their significance can vary based on the context of the codebase and the specific requirements of the project. Addressing code smells typically involves refactoring the code to improve its design, readability, and maintainability. Refactoring helps eliminate these smells, making the code more robust and easier to understand for both the current and future development teams.

Application-Specific Information

Within our application, in particular, we were (and still are) mindful of the following code smells that could arise during development:

  1. Long and Complex Methods: As our application grows, there might be a risk of having long and complex methods that are responsible for multiple tasks. This can make the code harder to read, understand, and maintain. Consider breaking down these methods into smaller, focused functions that handle specific tasks.
  2. God Objects: While our adherence to SOLID principles and the architecture's layering is a positive step, we are cautious of creating classes or components that have an excessive number of responsibilities, also known as 'God Objects.' This can lead to a lack of cohesion and difficulty in managing these components.
  3. Overuse of Interfaces: While interfaces promote flexibility, excessive use of interfaces for every class can lead to over-engineering. We make sure that each interface serves a clear purpose and is necessary for abstraction. Unnecessary interfaces can clutter the codebase.
  4. Duplicated Code: If similar pieces of code are repeated in multiple places, it could lead to maintenance challenges. We keep an eye out for duplicated code and consider refactoring it into reusable functions or utility classes
  5. Inconsistent Naming Conventions: Inconsistent naming of classes, methods, and variables can make the codebase hard to understand. We make sure to follow a consistent naming convention throughout the application.
  6. Unnecessary Abstractions: We are cautious when introducing abstractions or design patterns that might not be necessary for the current scope of the application. Overcomplicating the design can make the code harder to understand and maintain.
  7. Overcomplicated Class Hierarchy: While adhering to SOLID principles is essential, we are careful not to create an overly complex class hierarchy. Too many levels of inheritance and abstraction can hinder understanding and make the codebase harder to manage.
  8. Lack of Proper Documentation: We ensure that the code itself is well-documented. A lack of proper comments and documentation can lead to confusion for developers who work on the code later.
  9. Performance Bottlenecks: As the application grows and handles more data, we are mindful of potential performance bottlenecks. Consequently, we use appropriate data structures, algorithms, and caching strategies to ensure the application's responsiveness.
  10. Ignoring User-Centered Design: While our application design is comprehensive, it's essential to continuously involve user feedback and usability testing to ensure that the application aligns with users' needs and expectations.

These potential code smells are areas to watch out for as our application evolves. Regular code reviews, refactoring sessions, and maintaining a strong feedback loop within our development team can help mitigate these issues and keep our codebase clean and maintainable.


Application Design Patterns

General Information

Design patterns are reusable solutions to common problems that arise during software design and development. They provide established best practices for structuring code, solving specific design challenges, and creating software that is maintainable, scalable, and efficient. Design patterns help developers communicate and share their knowledge by providing a common vocabulary and framework for solving recurring problems in software development.

Design patterns are not specific pieces of code; rather, they are general templates that outline the relationships and interactions between different components of a software system. They help guide the architecture and organization of code to achieve certain goals while promoting modularity and flexibility.

There are several categories of design patterns, and each category addresses a different aspect of software design. Here are some common categories and examples of design patterns within each:

  1. Creational Patterns: These patterns focus on how objects are created, ensuring that objects are instantiated in a flexible and controlled manner.
    • Singleton: Ensures that a class has only one instance and provides a global access point to that instance.
    • Factory Method: Defines an interface for creating objects but allows subclasses to decide which class to instantiate.
  2. Structural Patterns: These patterns deal with the composition of classes and objects to form larger structures and relationships.
    • Adapter: Allows incompatible interfaces to work together by providing a wrapper that translates one interface to another.
    • Decorator: Allows behaviour to be added to individual objects, either statically or dynamically, without affecting the behaviour of other objects.
  3. Behavioral Patterns: These patterns define how objects interact and communicate with each other, focusing on the behaviour and responsibilities of objects.
    • Observer: Defines a dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically.
    • Strategy: Defines a family of interchangeable algorithms, allowing the client to choose an appropriate algorithm at runtime.
  4. Concurrency Patterns: These patterns address the challenges of managing concurrent execution and communication between threads or processes.
    • Producer-Consumer: Coordinates the interaction between producers, which produce data, and consumers, which process the data.
    • Mutex: Ensures that only one thread can access a shared resource at a time, preventing race conditions.
  5. Architectural Patterns: These patterns provide high-level structures for organizing and building entire systems or components.
    • MVC (Model-View-Controller): Separates an application into three interconnected components: Model (data), View (presentation), and Controller (user input).

Design patterns should be chosen and applied based on the specific problem or challenge at hand. They offer guidance, but their use should not be dogmatic; the context of the application and the trade-offs involved should be considered. By using design patterns, developers can create more maintainable, reusable, and well-structured code that adheres to proven best practices in software design.

Application-Specific Information

We have implemented many beneficial design patterns for our Android application. These patterns help us achieve better organization, maintainability, and flexibility in our codebase. Some of these design patterns include:

  1. Factory Method Pattern: The Factory Method pattern is used to create instances of different types of course cards or program recommendations based on user preferences. This pattern provides a way to delegate the responsibility of object creation to subclasses, ensuring that the appropriate type of object is created based on user input.
  2. Builder Pattern: The Builder pattern is used when creating complex objects like course cards or program recommendations. It allows us to separate the construction of a complex object from its representation, making the creation process more flexible and understandable.
  3. Strategy Pattern: The Strategy pattern is applied to the recommendation system. Different strategies could be used for matching students with courses and programs based on their preferences and academic history. This pattern enables us to encapsulate interchangeable algorithms and make them easily switchable.
  4. Observer Pattern: The Observer pattern is used to update the course or program recommendations when a user updates their preferences or academic history. This pattern allows objects to subscribe to changes in another object's state and get notified automatically.
  5. Facade Pattern: The Facade pattern simplifies the interaction between different layers or components in our application. For instance, our application contains a facade that provides a simplified interface for the user to update preferences and receive updated course recommendations without exposing the underlying complexity.
  6. Adapter Pattern: For future development, since our application needs to interact with external services or APIs to gather data, the Adapter pattern could help us by creating an interface that converts the methods of one class into methods that are compatible with the APIs that we need to use.
  7. Singleton Pattern: The Singleton pattern is used for managing the user's account information or preferences. This pattern ensures that a (User, Survey, etc.) class has only one instance and provides a global access point to that instance.
  8. Decorator Pattern: For future development, the Decorator pattern could be applied to enhance the functionality of course cards or program recommendations dynamically. For example, the user could decorate a course card with additional information or options based on user interactions.
  9. Command Pattern: Since our application involves user interactions that trigger various actions (mostly within the Android activity files), the Command pattern helps encapsulate these actions into command objects/methods, allowing for easier handling and extensibility.

Overall, these patterns enhance the maintainability, flexibility, and readability of our codebase.


Application Accessibility

General Information

Application accessibility, also known as web accessibility or digital accessibility, refers to the practice of designing and developing software applications in a way that ensures people with disabilities can access and interact with the application's content, features, and functionalities. The goal of application accessibility is to provide an inclusive user experience for individuals with various disabilities, including visual, auditory, cognitive, and motor impairments.

Accessibility is not just a legal or ethical requirement; it also makes good business sense. Accessible applications cater to a broader user base, including those with disabilities, elderly users, and users accessing the application through assistive technologies.

Here are some key aspects of application accessibility:

  1. Web Content Accessibility Guidelines (WCAG): The WCAG is an internationally recognized set of guidelines developed by the Web Accessibility Initiative (WAI) of the World Wide Web Consortium (W3C). These guidelines provide a comprehensive framework for creating accessible web content. They are organized into four main principles: Perceivable, Operable, Understandable, and Robust (POUR), each containing specific success criteria.
  2. Accessible User Interface Design: Design the user interface with accessibility in mind. Ensure that fonts are readable, colour contrast is sufficient, and user interface components are appropriately labelled. Provide alternative text for images, captions for videos, and transcripts for audio content.
  3. Keyboard Navigation: Make sure all functionality of the application can be accessed and operated using a keyboard. Keyboard navigation is essential for users who cannot use a mouse or other pointing devices
  4. Screen Reader Compatibility: Ensure that the application works well with screen readers, which are assistive technologies that read out the content to users who are visually impaired. Use semantic HTML elements to provide a meaningful structure to the content.
  5. Accessible Forms and Controls: Design forms and interactive elements (buttons, links, dropdowns, etc.) in a way that is easy to understand and operate for users with disabilities. Provide proper labels, instructions, and error messages.
  6. Focus Indicators: Highlight the currently focused element so that users navigating through the keyboard can easily identify where they are on the page
  7. Avoiding Flashing Content: Avoid content that flashes or blinks rapidly, as it can trigger seizures for individuals with photosensitive epilepsy.
  8. Testing with Assistive Technologies: Regularly test our application using assistive technologies such as screen readers and keyboard navigation tools. This helps identify any accessibility issues that may not be immediately apparent.
  9. Continuous Improvement: Accessibility is an ongoing effort. Regularly audit and update our application to ensure it meets the latest accessibility standards and guidelines.
  10. Education and Training: Educate our development team about accessibility best practices and the importance of creating inclusive applications. Training can help foster a culture of accessibility within the organization.

By prioritizing accessibility in our software development process, we can create applications that are usable and beneficial to a wider range of users, regardless of their abilities. Making our application accessible means providing equal access to information and functionality, and it demonstrates a commitment to inclusivity and social responsibility.

Application-Specific Information

Our application promotes accessibility in the following ways:

  1. Contrast and Color Choices: Our application's colour choices have sufficient contrast to make content easily readable for users with low vision. We also use text labels or icons to supplement visual cues.
  2. Text Size and Font: Our application uses fonts that are clear and legible, avoiding overly stylized or decorative fonts that may be difficult to read.
  3. Semantic Markup: Our application uses semantic markup for UI elements. For example, we use headings, lists, and paragraphs appropriately to help screen readers interpret and convey the content's structure.
  4. Accessible Forms: The forms within our app are properly labelled and use input types that are appropriate for the content. We use placeholder text only as a supplementary hint and provide clear labels for input fields.
  5. Feedback and Error Handling: Our application provides clear and descriptive feedback for errors or validation issues (such as with login credentials validation). We use visual cues to communicate errors, ensuring that users can understand and correct issues.
  6. Focus Indicators: We ensure that focus indicators are visible and clear when navigating through our application. This helps users understand where the current focus is located.
  7. Gesture-Based Interaction: While swipe gestures are popular, our application ensures that users have alternative ways to perform essential actions since some users may have difficulty performing specific gestures.

For future development, we may implement:

  1. Screen Reader Compatibility: Screen reader compatibilities will ensure that our application is compatible with screen readers like TalkBack for visually impaired users. We can use proper accessibility labels for UI elements, so screen readers can provide meaningful information to users. This includes buttons, text fields, and other interactive elements.
  2. Keyboard Navigation: Keyboard navigation options ensure that users can navigate through our app using keyboard input alone. This is important for users who may not be able to use touch gestures. We can ensure that the keyboard focus is visually clear and moves logically through the interface.
  3. Alternative Text for Images: We can provide alternative text for images, especially those that convey important information. This helps users with visual impairments understand the content even if they can't see the images.
  4. Aria Roles and Attributes: If our app uses web technologies/widgets (e.g., WebView widget) in the future, we can make use of ARIA roles and attributes to enhance accessibility for web content displayed within the app.
  5. User Testing: We can conduct usability testing with a diverse group of users, including those with disabilities. Their feedback can help identify areas that need improvement and ensure our app is truly accessible.
  6. Accessibility Guidelines: We can familiarize ourselves with the Android accessibility guidelines and best practices provided by Google. These resources offer valuable insights into making our application more accessible.

Overall, accessibility is an ongoing process that depends upon user feedback and evolving best practices. By prioritizing accessibility, we will create a more inclusive and user-friendly experience for all of our application's users.


Application Ethics

General Information

Application ethics, also known as software ethics or technology ethics, refers to the ethical considerations and responsibilities that software developers and technology companies have when creating, designing, and deploying software applications and technology solutions. It involves making decisions that align with ethical principles, respect user rights, consider potential societal impacts, and promote the well-being of individuals and communities.

Ethics in software development recognizes that technology can have far-reaching effects on individuals, society, and the environment. Developers have a role to play in ensuring that the applications they create are not only functional and innovative but also adhere to moral and ethical standards.

Here are some key aspects of application ethics:

  1. Privacy and Data Protection: Developers should prioritize the privacy and security of user data. This includes obtaining informed consent for data collection, handling sensitive information responsibly, and implementing robust security measures to prevent data breaches.
  2. Inclusivity and Accessibility: Applications should be designed and developed to be accessible to all users, regardless of their abilities. Prioritizing inclusivity ensures that everyone can benefit from the application's features and functionalities.
  3. Transparency and Accountability: Developers should be transparent about how user data is collected, used, and shared. Transparency builds trust and allows users to make informed decisions about their interactions with the application.
  4. Bias and Fairness: Developers should strive to eliminate bias and discrimination in algorithms and decision-making processes embedded in applications. This includes addressing issues related to AI and machine learning that may inadvertently perpetuate biases.
  5. Safety and Reliability: Applications should be designed with safety in mind, especially in areas such as healthcare, transportation, and critical infrastructure. Rigorous testing and quality assurance practices help ensure that software is reliable and doesn't put users at risk.
  6. Societal Impact: Developers should consider the broader societal impact of their applications. This involves anticipating potential negative consequences and taking steps to mitigate harm.
  7. Environmental Impact: Software development can have environmental implications, such as energy consumption and electronic waste. Developers should explore ways to minimize the ecological footprint of their applications.
  8. Intellectual Property and Copyright: Developers should respect intellectual property rights and adhere to copyright laws when using and distributing code, libraries, and other resources
  9. User Empowerment: Empower users with control over their data and interactions with the application. Allow users to customize settings, manage permissions, and make informed choices about their digital experiences.
  10. Ethical Decision-Making: Developers should engage in ethical decision-making when faced with dilemmas that involve conflicting interests, potential harm, or ethical grey areas. Consulting ethical frameworks and seeking guidance can aid in making responsible choices.

Application ethics requires a holistic view of the impact technology can have on individuals, society, and the environment. Developers, along with other stakeholders, have a responsibility to consider these ethical dimensions throughout the software development lifecycle, from conception to deployment and beyond. By prioritizing ethics, developers can contribute to a more ethical and responsible technological landscape.

Application-Specific Information

As our application interacts with students and their academic data, there are several ethical considerations to keep in mind:

  1. Data Privacy and Security: We must ensure that users' personal and academic information is handled securely and with the utmost respect for their privacy. Consequently, we must implement robust data encryption, secure authentication mechanisms, and follow best practices for data storage and transmission.
  2. Transparency and Consent: We must clearly communicate to users how their data will be used and shared within the application. This includes obtaining explicit consent for data collection/processing and allowing users to have control over their data through settings and preferences.
  3. Fairness and Bias: We must strive to eliminate any biases in the recommendation algorithms used to match students with courses. Bias in algorithms can lead to unfair outcomes and reinforce existing inequalities. Consequently, we must regularly audit and test our algorithms to identify and address potential biases.
  4. Informed Decision-Making: Our application should empower users to make informed decisions about their education. Consequently, we must provide accurate and comprehensive information about courses and programs, and ensure that user interactions with our application are transparent and straightforward.
  5. User Empowerment: We must empower users to have control over their own data. This means that we must allow them to edit or delete their account, data, and preferences as they see fit. Additionally, we must provide them with options for opting out of data collection or recommendation features.
  6. User Experience and Accessibility: We must prioritize accessibility to ensure that all users, regardless of their abilities, can access and use our application. An inclusive design approach helps avoid excluding users with disabilities from benefiting from our application.
  7. Accountability and Transparency: We must be transparent about our application's operations, including how data is processed, who has access to it, and how decisions are made. Additionally, we must clearly communicate any changes in terms of use, privacy policies, or algorithms.
  8. Avoiding Exploitation: We must avoid using students' personal and academic data for purposes beyond the core functionality of the application, such as selling data to third parties or targeting users with unrelated advertisements.
  9. Continuous Improvement: We must regularly review and assess the ethical implications of our application's features and operations. Additionally, we must be open to feedback from users and stakeholders to continually improve our ethical practices.
  10. User Support and Redress: We must provide mechanisms for users to report issues, provide feedback, and seek redress in case of any problems or concerns related to the application's functionality or ethical considerations.
  11. Legal and Regulatory Compliance: We must ensure that our application complies with relevant laws and regulations regarding data protection, privacy, and accessibility.
  12. Community Engagement: We should engage with the University of Toronto community, as well as other stakeholders, to gather feedback and input on the ethical considerations of our application. This can help us address concerns and align the application with the values and needs of its users.

By integrating these ethical considerations into our application's design, development, and operation, we can create a product that not only fulfils its functional goals but also respects and protects the rights and well-being of its users. Ethical practices will contribute to building trust and credibility for our application within the academic community.


Application Future Development

Building upon the existing user stories and the application's domain, here are some future development ideas that could enhance the course-matching application:

  1. Advanced Matching Algorithms: Develop more sophisticated algorithms for course matching. Incorporate machine learning techniques to provide more accurate and personalized course recommendations based on students' academic histories and preferences.
  2. Real-time Collaboration and Discussions: Add a feature that allows users to engage in real-time discussions about courses and programs. This could be through chat rooms, discussion forums, or even integration with existing social media platforms.
  3. Interactive Course Syllabi: Provide interactive course syllabi that include details about assignments, reading materials, assessment methods, and schedules. This can give students a comprehensive overview of what to expect from each course.
  4. Integration with Calendar Apps: Allow users to directly integrate their recommended courses and program schedules with their preferred calendar applications, making it easier to manage their academic commitments.
  5. Peer Reviews and Ratings: Enable students to rate and review courses they've taken. These peer reviews can provide additional insights into the quality and content of courses, helping other students make informed decisions.
  6. Gamified Learning Paths: Implement gamification elements to motivate students to complete certain courses or achieve specific learning outcomes. This could include badges, achievements, and leaderboards.
  7. Personalized Learning Plans: Develop a feature that helps students create personalized learning plans for each semester, considering their academic goals, interests, and graduation requirements.
  8. Career Path Insights: Provide insights into how specific courses and programs align with potential career paths. This can help students make more informed decisions about their educational journey.
  9. Intelligent Notifications: Implement a notification system that alerts users about upcoming deadlines, enrollment dates, and important updates related to their selected courses and programs.
  10. Integration with University Services: Collaborate with the university to integrate the application with existing university services, such as the registration system, library resources, and academic advising.
  11. Interactive Visualization of Degree Progress: Create visual representations of a student's degree progress, showing completed and remaining requirements. This can help students track their academic journey more effectively.
  12. Virtual Campus Tours: Provide virtual campus tours to help students explore the university's physical facilities and get a sense of the campus environment.
  13. Integration with Academic Advisors: Allow students to connect with academic advisors through the application, facilitating better communication and guidance in selecting courses and programs.
  14. Integration with Alumni Network: Provide access to the university's alumni network, enabling students to connect with graduates who can offer insights into the practical applications of their chosen courses and programs.
  15. Interactive Learning Resources: Curate and provide supplementary learning resources, such as videos, articles, and quizzes, to support students' understanding of course content.

These future development ideas aim to enhance the user experience, provide more personalized recommendations, and offer additional tools and resources to help students navigate their academic journey more effectively.


Miscellaneous Information

Project Reflection

Group Reflection

  • Challenges:
    • Adhering to recommended software application architecture and design principles.
    • Testing and debugging the software application.
    • Completing the project on a part-time basis within the span of a few months.
  • Successes:
    • Scraping data from the web to create clean datasets.
    • Seamlessly assigning and completing work based on an individual's user story.
    • Developing a fully-functional Android application.

Individual Reflections

The following individuals were involved in the development of this software application. Their individual project reflections are expressed below:

  • Uyiosa Iyekekpolor
    • Role: Data Specialist and Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]
  • Gagandeep Singh Lubana
    • Role: Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]
  • Lavya Vaishno
    • Role: Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]
  • Dev Vora
    • Role: Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]
  • Manav Singh
    • Role: Data Specialist and Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]
  • Jaspreet Khela
    • Role: Team Lead and Software Developer
    • Reflection:
      • Challenges:
        • [Coming Soon]
      • Successes:
        • [Coming Soon]

Questions

If we have questions for our team, please email us.


External Contributions

This project is currently not open for external contributions.


License

This project has an MIT License.

About

This is a course- and program-matching Android application for University of Toronto students.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages