From 979623d91a0ca605fc0d00f89ae7c225a3bd65c7 Mon Sep 17 00:00:00 2001 From: Chintan Prajapati Date: Mon, 18 Nov 2024 15:24:17 +0530 Subject: [PATCH] Added: Mobile App Thought Processes & POC --- content/1.getting-started/1.index.md | 69 +- .../1.getting-started/2.code-of-conduct.md | 29 + content/1.getting-started/2.installation.md | 32 - content/1.getting-started/3.usage.md | 142 ---- content/2.mobile-app/1.onboarding/1.index.md | 29 + .../2.mobile-app/1.onboarding/2.tech-stack.md | 6 + content/2.mobile-app/1.onboarding/_dir.yml | 1 + .../2.thought-processses/1.index.md | 15 + .../2.thought-processses/2.architecture.md | 105 +++ .../2.thought-processses/3.decoupling.md | 62 ++ .../4.state-management.md | 28 + .../2.thought-processses/_dir.yml | 1 + content/2.mobile-app/3.poc/1.index.md | 8 + content/2.mobile-app/3.poc/2.architecture.md | 723 ++++++++++++++++++ .../2.mobile-app/3.poc/3.code-generation.md | 6 + content/2.mobile-app/3.poc/_dir.yml | 1 + content/2.mobile-app/_dir.yml | 1 + 17 files changed, 1043 insertions(+), 215 deletions(-) create mode 100644 content/1.getting-started/2.code-of-conduct.md delete mode 100644 content/1.getting-started/2.installation.md delete mode 100644 content/1.getting-started/3.usage.md create mode 100644 content/2.mobile-app/1.onboarding/1.index.md create mode 100644 content/2.mobile-app/1.onboarding/2.tech-stack.md create mode 100644 content/2.mobile-app/1.onboarding/_dir.yml create mode 100644 content/2.mobile-app/2.thought-processses/1.index.md create mode 100644 content/2.mobile-app/2.thought-processses/2.architecture.md create mode 100644 content/2.mobile-app/2.thought-processses/3.decoupling.md create mode 100644 content/2.mobile-app/2.thought-processses/4.state-management.md create mode 100644 content/2.mobile-app/2.thought-processses/_dir.yml create mode 100644 content/2.mobile-app/3.poc/1.index.md create mode 100644 content/2.mobile-app/3.poc/2.architecture.md create mode 100644 content/2.mobile-app/3.poc/3.code-generation.md create mode 100644 content/2.mobile-app/3.poc/_dir.yml create mode 100644 content/2.mobile-app/_dir.yml diff --git a/content/1.getting-started/1.index.md b/content/1.getting-started/1.index.md index 3b215c3..1bb4839 100644 --- a/content/1.getting-started/1.index.md +++ b/content/1.getting-started/1.index.md @@ -1,53 +1,40 @@ --- title: Introduction -description: Welcome to Nuxt UI Pro documentation template. +description: Welcome to the VaahStore documentation --- -This template is a ready-to-use documentation template made with [Nuxt UI Pro](https://ui.nuxt.com/pro), a collection of premium components built on top of [Nuxt UI](https://ui.nuxt.com) to create beautiful & responsive Nuxt applications in minutes. +VaahStore is a cutting-edge, open-source headless e-commerce platform designed to revolutionize the online shopping experience. VaahStore offers a fast, efficient, and feature-rich solution for businesses of all sizes. -There are already many websites based on this template: +## Key Features -- [Nuxt](https://nuxt.com) - The Nuxt website -- [Nuxt UI](https://ui.nuxt.com) - The documentation of `@nuxt/ui` and `@nuxt/ui-pro` -- [Nuxt Image](https://image.nuxt.com) - The documentation of `@nuxt/image` -- [Nuxt Content](https://content.nuxt.com) - The documentation of `@nuxt/content` -- [Nuxt Devtools](https://devtools.nuxt.com) - The documentation of `@nuxt/devtools` -- [Nuxt Studio](https://nuxt.studio) - The pro version of Nuxt Content +- Headless Architecture + - Unrestricted flexibility to build custom storefronts and integrate with any device or platform. +- Open-Source + - Empowering developers to customize and extend the platform to meet specific needs. +- Fast and Efficient + - Optimized performance for seamless user experiences. +- Easy to Use + - Intuitive interface and developer-friendly tools for rapid development. +- Comprehensive Features + - Built-in functionalities like product catalogs, shopping carts, checkout processes, and more. -## Features +## Why Choose VaahStore? -- Powered by [Nuxt 3](https://nuxt.com) -- Built with [Nuxt UI](https://ui.nuxt.com) and [Nuxt UI Pro](https://ui.nuxt.com/pro) -- Write content with [MDC syntax](https://content.nuxt.com/usage/markdown) thanks to [Nuxt Content](https://content.nuxt.com) -- Compatible with [Nuxt Studio](https://nuxt.studio) -- Auto-generated sidebar navigation -- Full-Text Search out of the box -- Beautiful Typography styles -- Dark mode support -- And more... +- Unleash Your Creativity + - Design **stunning, personalized storefronts without compromise**. +- Skyrocket Your Sales + - Deliver **blazing-fast** performance and seamless user experiences. +- Future-Proof Your Business + - Built on the latest technologies, VaahStore ensures you **stay ahead of the curve**. +- Take Control of Your E-commerce Destiny + - With complete **customization** and **extensibility**, you're in the driver's seat. +- Join a Thriving Community + - Benefit from a global community of developers, **sharing knowledge and innovation**. -## Play online +## Getting Started -You can start playing with this template in your browser using our online sandboxes: +To begin your journey with VaahStore, refer to our comprehensive documentation and community resources. Our dedicated team is committed to providing support and guidance throughout the development process. -::u-button ---- -class: mr-4 -icon: i-simple-icons-stackblitz -label: Play on StackBlitz -target: _blank -to: https://stackblitz.com/github/nuxt-ui-pro/docs/ ---- -:: - -::u-button ---- -class: mt-2 sm:mt-0 -icon: i-simple-icons-codesandbox -label: Play on CodeSandbox -target: _blank -to: https://codesandbox.io/s/github/nuxt-ui-pro/docs/ ---- -:: +## Join the VaahStore Community -Or open [Nuxt UI playground](https://ui.nuxt.com/playground). +Connect with fellow developers, share knowledge, and contribute to the future of e-commerce. Join our GitHub community to stay updated on the latest developments and best practices. diff --git a/content/1.getting-started/2.code-of-conduct.md b/content/1.getting-started/2.code-of-conduct.md new file mode 100644 index 0000000..ab35edf --- /dev/null +++ b/content/1.getting-started/2.code-of-conduct.md @@ -0,0 +1,29 @@ +--- +title: Code of Conduct +description: Let's keep it light and friendly, but serious about respect! +--- + +We want our community to be a fun and welcoming place for everyone. Here are a few guidelines to help us maintain that: + +## Play Nice + +- **No cyberbullying or trolling.** (We're all friends here, even if we disagree.) +- **Keep it clean.** (No offensive language or inappropriate content, please.) +- **Respect everyone's opinions.** (Even if they're really wrong.) +- **Be patient and understanding.** (We're all human, and sometimes we make mistakes.) + +## Unacceptable Behavior + +- **No bullying or hate speech.** (Seriously, it's not cool.) +- **Don't spam or troll.** (It's annoying, and nobody likes an annoying bot.) +- **Respect people's privacy.** (Keep your snooping to a minimum.) + +## If Something's Off + +If you see something that doesn't feel right, please let us know. We'll investigate and fix or take appropriate action. + +## Enforcement + +We reserve the right to take action against individuals who violate this code of conduct, including warnings, temporary bans, or permanent bans. + +By participating in our community, you agree to abide by this code of conduct. diff --git a/content/1.getting-started/2.installation.md b/content/1.getting-started/2.installation.md deleted file mode 100644 index 99cbb4c..0000000 --- a/content/1.getting-started/2.installation.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Installation -description: Get started with Nuxt UI Pro documentation template. ---- - -::callout ---- -icon: i-heroicons-light-bulb -target: _blank -to: https://nuxt.studio/themes/docs ---- -Use this template on Nuxt Studio and start your documentation in seconds. -:: - -## Quick Start - -You can start a fresh new project with: - -```bash [Terminal] -npx nuxi init -t github:nuxt-ui-pro/docs -``` - -or create a new repository from GitHub: - -1. Open -2. Click on `Use this template` button -3. Enter repository name and click on `Create repository from template` button -4. Clone your new repository -5. Install dependencies with your favorite package manager -6. Start development server - -That's it! You can now start writing your documentation in the [`content/`](https://content.nuxt.com/usage/content-directory) directory πŸš€ diff --git a/content/1.getting-started/3.usage.md b/content/1.getting-started/3.usage.md deleted file mode 100644 index 7de8e15..0000000 --- a/content/1.getting-started/3.usage.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Usage -description: Learn how to write and customize your documentation. ---- - -This is only a basic example of what you can achieve with [Nuxt UI Pro](https://ui.nuxt.com/pro/guide), you can tweak it to match your needs. The template uses several Nuxt modules underneath like [`@nuxt/content`](https://content.nuxt.com) for the content, [`@nuxtjs/fontaine`](https://github.com/nuxt-modules/fontaine) and [`@nuxtjs/google-fonts`](https://github.com/nuxt-modules/google-fonts) to change the font and [`nuxt-og-image`](https://nuxtseo.com/og-image/getting-started/installation) for social previews. - -::callout ---- -icon: i-heroicons-light-bulb -target: _blank -to: https://ui.nuxt.com/pro/guide/usage#structure ---- -Learn more on how to customize and structure a Nuxt UI Pro app! -:: - -## Writing content - -You can just start writing `.md` or `.yml` files in the [`content/`](https://content.nuxt.com/usage/content-directory) directory to have your pages updated. -The navigation will be automatically generated in the left aside and in the mobile menu. You will also be able to go through your content with full-text search. - -::callout{icon="i-heroicons-light-bulb"} -This template relies on a [catch-all route](https://nuxt.com/docs/guide/directory-structure/pages#catch-all-route) but you can achieve the same thing with the [document-driven mode](https://content.nuxt.com/document-driven/introduction). -:: - -## App Configuration - -In addition to `@nuxt/ui-pro` configuration through the `app.config.ts`, this template lets you customize the `Header`, `Footer` and the `Table of contents` components. - -### Header - -```ts [app.config.ts] -export default defineAppConfig({ - header: { - // Logo configuration - logo: { - // Light mode - light: { - src: '', - alt: '', - class: '' - }, - // Dark mode - dark: { - src: '', - alt: '', - class: '' - } - }, - // Show or hide the search bar - search: true, - // Show or hide the color mode button - colorMode: true, - // Customize links - links: [{ - icon: 'i-simple-icons-github', - to: 'https://github.com/nuxt-ui-pro/docs', - target: '_blank', - 'aria-label': 'Docs template on GitHub' - }] - } -}) -``` - -### Footer - -```ts [app.config.ts] -export default defineAppConfig({ - footer: { - // Update bottom left credits - credits: 'Copyright Β© 2023', - // Show or hide the color mode button - colorMode: false, - // Customize links - links: [{ - icon: 'i-simple-icons-nuxtdotjs', - to: 'https://nuxt.com', - target: '_blank', - 'aria-label': 'Nuxt Website' - }, { - icon: 'i-simple-icons-discord', - to: 'https://discord.com/invite/ps2h6QT', - target: '_blank', - 'aria-label': 'Nuxt UI on Discord' - }, { - icon: 'i-simple-icons-x', - to: 'https://x.com/nuxt_js', - target: '_blank', - 'aria-label': 'Nuxt on X' - }, { - icon: 'i-simple-icons-github', - to: 'https://github.com/nuxt/ui', - target: '_blank', - 'aria-label': 'Nuxt UI on GitHub' - }] - } -}) -``` - -### Table of contents - -```ts [app.config.ts] -export default defineAppConfig({ - toc: { - // Title of the main table of contents - title: 'Table of Contents', - // Bottom TOC configuration - bottom: { - // Title of the bottom table of contents - title: 'Community', - // URL of your repository content folder - edit: '', - // Customize links - links: [{ - icon: 'i-heroicons-star', - label: 'Star on GitHub', - to: 'https://github.com/nuxt/ui', - target: '_blank', - }, { - icon: 'i-heroicons-book-open', - label: 'Nuxt UI Pro docs', - to: 'https://ui.nuxt.com/pro/guide', - target: '_blank', - }, { - icon: 'i-simple-icons-nuxtdotjs', - label: 'Purchase a license', - to: 'https://ui.nuxt.com/pro/purchase', - target: '_blank', - }] - } - } -}) -``` - -::callout ---- -icon: i-heroicons-light-bulb -target: _blank -to: https://nuxt.studio/docs/developers/data#appconfigts ---- -A dedicated interface is provided to edit those configurations on Nuxt Studio. -:: diff --git a/content/2.mobile-app/1.onboarding/1.index.md b/content/2.mobile-app/1.onboarding/1.index.md new file mode 100644 index 0000000..7e34edc --- /dev/null +++ b/content/2.mobile-app/1.onboarding/1.index.md @@ -0,0 +1,29 @@ +--- +title: Introduction +description: Starting with Mobile App Onboarding +--- + +VaahStore Mobile App is a feature-rich mobile application, designed to provide a seamless shopping experience on smartphones and tablets. It leverages the power of the VaahStore backend API to offer a wide range of features. + +## Features + +- Product Browsing + - Explore a vast catalog of products, categorized and filtered for easy navigation. +- Product Details + - View detailed product information, including high-quality images, descriptions, and customer reviews. +- Shopping Cart + - Add products to the cart, modify quantities, and apply discounts. +- Secure Checkout + - Process payments securely using various payment methods. +- Order Tracking + - Track the status of orders, from placement to delivery. +- User Accounts + - Create and manage user accounts, including address book, order history, and wishlists. +- Push Notifications + - Receive personalized notifications for order updates, promotions, and new arrivals. +- Recommendations + - Personalized Recommendations to cater each individual +- Robust Logging and Analytics + - Monitor app performance & issues. Also collect insights about what users prefer. +- And Much More + - All the things you think should be there, If not let us know and we will try to fit them in our roadmap. diff --git a/content/2.mobile-app/1.onboarding/2.tech-stack.md b/content/2.mobile-app/1.onboarding/2.tech-stack.md new file mode 100644 index 0000000..456720b --- /dev/null +++ b/content/2.mobile-app/1.onboarding/2.tech-stack.md @@ -0,0 +1,6 @@ +--- +title: Tech Stack +description: Technologies powering our platform. +--- + +## TODO: diff --git a/content/2.mobile-app/1.onboarding/_dir.yml b/content/2.mobile-app/1.onboarding/_dir.yml new file mode 100644 index 0000000..2840a91 --- /dev/null +++ b/content/2.mobile-app/1.onboarding/_dir.yml @@ -0,0 +1 @@ +title: Onboarding diff --git a/content/2.mobile-app/2.thought-processses/1.index.md b/content/2.mobile-app/2.thought-processses/1.index.md new file mode 100644 index 0000000..be58128 --- /dev/null +++ b/content/2.mobile-app/2.thought-processses/1.index.md @@ -0,0 +1,15 @@ +--- +title: Introduction +description: Thought process behind reaching to the decisions +--- + +## Topics this section will cover + +- Project Architecture +- Decoupling Dependencies +- State Management Patterns +- Design Patterns +- Performance Optimization +- Security Considerations +- Code Quality and Maintainability +- Testing Strategies diff --git a/content/2.mobile-app/2.thought-processses/2.architecture.md b/content/2.mobile-app/2.thought-processses/2.architecture.md new file mode 100644 index 0000000..a2809b7 --- /dev/null +++ b/content/2.mobile-app/2.thought-processses/2.architecture.md @@ -0,0 +1,105 @@ +--- +title: Architecture +description: All about project architecture +--- + +## What will we discuss about? + +There are so many architectures but two of them needs some discussion here. + +- Model-View-Controller +- Clean Architecture + +## What we want to avoid intentionally for now? + +- MVVM + - MVVM implies databindings between the view and the viewmodel, which means, in practice, the view objects mostly are the ones commanding the viewmodel. MVVM seems to me a simplification of MVC, to show the model "as is" behind the scenes. And because we are already talking about MVC, talking MVVM doesn't makes sense. + - Also I think, overuse of data binding can lead to tight coupling between the View and ViewModel, making the code less modular and harder to test. +- Layered Architecture + - Layered Architecture has unidirectional dependencies, with lower layers depending on higher ones. While Clean Architecture Dependencies point inwards towards the core, making the core independent of external factors. So I am gonna exclude Layered Architecture & will focus on Clean Architecture. Also Clean Architecture offers high degree of flexibility, testability, and maintainability on top of what Layered Architecture does. +- Hexagonal Architecture + - If one use-case (application-service) is dependent on another, and so on, resulting in a tangled chain of calls where people usually get lost. Usually, this web of dependencies is an anti-pattern that hinders the clarity and simplicity that Hexagonal Architecture aims to provide. + +## Model-View-Controller + +MVC separates an application into three interconnected parts: + +- Model + - Represents the data of the application. +- View + - Responsible for the user interface +- Controller + - Handles the logic between the Model and the View. + +``` +./lib +β”œβ”€β”€ models +β”‚ β”œβ”€β”€ data +β”‚ └── ... +β”œβ”€β”€ Views +β”‚ └── ... +β”œβ”€β”€ Controllers +β”‚ └── ... +β”œβ”€β”€ Services +β”‚ └── ... +β”œβ”€β”€ Repositories +β”‚ └── ... +└── routes +``` + +### Pros + +- **Simplicity**: Easier to implement for small applications. +- **Quick to start**: Suitable for smaller projects or MVP applications. +- **Familiarity**: A widely recognized pattern, making it easier to onboard new developers. + +### Cons + +- **Tight Coupling**: The controller is responsible for both business logic and UI updates, leading to a tight coupling between the components. +- **Scalability Issues**: As the application grows, managing complexity can become difficult because the logic might get mixed in the controller. +- **Limited Separation of Concerns**: This pattern often doesn't separate responsibilities as clearly, leading to potential maintenance challenges in large apps. + +## Clean Architecture + +- Data Layer + - **Models**: Define the data structures for your application, keeping them simple and focused on the data they represent. + - **Services**: Implement services for network requests, local storage, and other data operations. Use appropriate libraries to handle these tasks. +- Domain Layer + - **Interfaces**: Define clear interfaces for repositories, specifying the methods to interact with data. + - **Repositories**: Implement these interfaces, using the data layer to fetch and store data. Keep the repository logic independent of the data source (e.g., network, database). +- Presentation Layer + - **Pages**: Build the UI screens, using widgets to compose the layout and handle user interactions. + - **Widgets**: Create reusable UI components to improve code organization and maintainability. + +``` +./lib +β”œβ”€β”€ data +β”‚ β”œβ”€β”€ models +β”‚ └── services +β”œβ”€β”€ domain +β”‚ β”œβ”€β”€ interfaces +β”‚ └── repositories +β”œβ”€β”€ presentation +β”‚ β”œβ”€β”€ pages +β”‚ └── widgets +└── routes +``` + +### Pros + +- **Separation of Concerns**: Clean Architecture separates concerns into distinct layers, making it easier to maintain and scale as the app grows. +- **Flexibility**: The architecture can easily accommodate changes in UI frameworks, data sources, or external dependencies without affecting the core business logic. +- **Scalability**: Clean Architecture is designed to handle complexity and larger applications efficiently. +- **Testability**: Since the business logic and UI are decoupled, it’s easier to test different parts of the app independently. + +### Cons + +- **Complexity**: The setup is more complex compared to MVC, and might be overkill for simple apps. +- **Learning Curve**: For beginners or developers new to the architecture, there might be an initial learning curve to understand the separation of layers and their interactions. +- **Verbose Code**: Because of the multiple layers and classes involved, it can lead to more boilerplate code. + +## Decision Making + +For production-grade apps, Clean Architecture is often preferred. Where rapid development is key, and you can afford some level of coupling between logic and UI - you should use MVC. Clean Architecture is better suited for larger, more complex apps that need to scale, be maintainable over time, and where testability and decoupling of concerns are priorities. + +We have decided to go ahead with Clean Architecture. diff --git a/content/2.mobile-app/2.thought-processses/3.decoupling.md b/content/2.mobile-app/2.thought-processses/3.decoupling.md new file mode 100644 index 0000000..3096e0e --- /dev/null +++ b/content/2.mobile-app/2.thought-processses/3.decoupling.md @@ -0,0 +1,62 @@ +--- +title: Decoupling Dependencies +description: +--- + +Tightly coupled dependencies between components can lead to several issues like Increased complexity, Reduced testability, Reduced maintainability, etc. + +## Proposed Solutions + +To address these challenges, we propose two effective strategies: Singleton And Context-Aware Dependency Retrieval + +## Singleton + +In this approach, a singleton acts as a centralized container for all repository and service instances. + +- Create a Singleton + - Design a singleton class to hold all repository and service instances. + - Initialize the singleton asynchronously during the app's startup phase, typically during the splash screen. +- Access the Singleton + - Once the singleton is initialized, it can be accessed from anywhere in the app. + - However, **limit its usage** to passing necessary instances to Blocs, Controllers, or Providers during their injection. + - **Avoid excessive use** of the singleton to prevent tight coupling and global state management. +- Selective Instance Passing + - When injecting dependencies into a Bloc, Controller, or Provider, pass only the **specific instances** required for its functionality. This approach promotes a more granular and focused dependency injection process. + +### Pros + +- Centralized initialization of dependencies. +- Clear separation of the initialization phase and dependency usage phase. +- Ensures that dependencies are only instantiated once, avoiding redundant resource usage. + +### Cons + +- The app relies on all dependencies being initialized upfront, which may increase the splash screen loading time. + +## Context-Aware Dependency Retrieval + +This approach introduces a core Bloc, Controller, or Provider, referred to as the "core instance," to store all repository and service instances. Dependencies are accessed dynamically using the BuildContext to locate the core instance. This approach emphasizes context-aware dependency resolution and reduces the reliance on global access patterns. + +- Create a Core Instance + - Define a Bloc, Controller, or Provider to act as the central storage for dependencies. +- Inject Dependencies + - Initialize all required repository and service instances within the core instance. + - Provide the core instance to the widget tree using a dependency injection mechanism. +- Dynamic Retrieval + - Use the BuildContext to locate the core instance dynamically whenever dependencies are needed. + - Extract only the specific instances required for a given Bloc, Controller, or Provider. + +### Pros + +- Context-aware dependency resolution promotes structured and localized access patterns. +- Simplifies testing by mocking the core instance for specific widget trees. +- Reduces global state access, aligning with best practices in state management. + +### Cons + +- Requires proper handling of BuildContext to avoid accessing dependencies outside of the widget tree. +- Dependency retrieval via BuildContext can become verbose if not managed properly. + +## Decision Making + +- Going ahead with Singleton makes sense for the project we have diff --git a/content/2.mobile-app/2.thought-processses/4.state-management.md b/content/2.mobile-app/2.thought-processses/4.state-management.md new file mode 100644 index 0000000..c5dc272 --- /dev/null +++ b/content/2.mobile-app/2.thought-processses/4.state-management.md @@ -0,0 +1,28 @@ +--- +title: State Management +description: Managing State in the Project +--- + +## BLoC + +> BLoC, short for Business Logic Component, enforces a unidirectional data flow, ensuring a clear separation of concerns and promoting testability. + +### Principles + +- Events Drive State Changes + - BLoC receives events triggered by user interactions or other parts of the application. These events signal a potential change in the state. +- State Updates via Emitting New States + - Based on the received event, the BLoC processes it and emits a new state that reflects the updated application state. +- UI Reacts to State Changes + - The UI observes the state emitted by the BLoC and rebuilds itself accordingly, ensuring the UI always reflects the current state. + +### Pros + +- Predictable State Flow + - BLoC enforces a unidirectional data flow, making it easier to reason about how state changes occur within your application. +- Separation of Concerns + - BLoC separates the UI from the business logic, leading to cleaner and more maintainable code. +- Scalability + - The BLoC pattern scales well for complex applications with intricate state management requirements. +- Improved Testability + - By isolating state logic within the BLoC, you can write unit tests to ensure the BLoC reacts correctly to different events. diff --git a/content/2.mobile-app/2.thought-processses/_dir.yml b/content/2.mobile-app/2.thought-processses/_dir.yml new file mode 100644 index 0000000..9706552 --- /dev/null +++ b/content/2.mobile-app/2.thought-processses/_dir.yml @@ -0,0 +1 @@ +title: Thought Processes diff --git a/content/2.mobile-app/3.poc/1.index.md b/content/2.mobile-app/3.poc/1.index.md new file mode 100644 index 0000000..ea3783f --- /dev/null +++ b/content/2.mobile-app/3.poc/1.index.md @@ -0,0 +1,8 @@ +--- +title: Introduction +description: Proof of Concepts +--- + +## Topics this section will cover + +- Architecture PoC diff --git a/content/2.mobile-app/3.poc/2.architecture.md b/content/2.mobile-app/3.poc/2.architecture.md new file mode 100644 index 0000000..df9d2ae --- /dev/null +++ b/content/2.mobile-app/3.poc/2.architecture.md @@ -0,0 +1,723 @@ +--- +title: Architecture PoC +description: +--- + +``` +./lib +β”œβ”€β”€ data +β”‚ β”œβ”€β”€ models +β”‚ └── services +β”œβ”€β”€ domain +β”‚ β”œβ”€β”€ interfaces +β”‚ └── repositories +β”œβ”€β”€ presentation +β”‚ β”œβ”€β”€ pages +β”‚ └── widgets +└── routes +``` + +## Main entry point + +`app_config.dart` + +This file is the main entry point of the application. It initializes the DependencyManager and sets up the application's initial configuration, including system UI styles. It then renders a FutureBuilder to handle the asynchronous initialization process of the DependencyManager. Based on the initialization result, it displays a loading screen, an error screen, or the main application. + +```dart + return FutureBuilder( + future: DependencyManager.init(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const _DefaultAppConfig( + child: _SplashScreen(), + ); + } else if (snapshot.hasError) { + return const _DefaultAppConfig( + child: _HardRestartScreen(), + ); + } else if (snapshot.hasData) { + return _AppConfigImpl( + env: env, + ); + } else { + return const _DefaultAppConfig( + child: _SomethingWentWrongScreen(), + ); + } + }, + ); +``` + +## Dependency Manager + +the DependencyManager acts as a central hub for managing application-wide dependencies. + +### Key Points + +- Focus on Repositories + - The DependencyManager primarily initializes and exposes repositories. Repositories encapsulate data access logic, often combining data from multiple services. + - For instance, `AuthenticationRepository` combines data from `ApiService` and `StorageService`. This separation of concerns promotes cleaner code and easier testing. +- BLoCs and Repositories + - BLoCs, which manage application state and logic, can leverage the DependencyManager to access the required repositories. + - For instance, an `AuthenticationBloc` might depend on the `AuthenticationRepository`. +- Modular Design + - This approach promotes modularity by isolating dependency creation logic within the DependencyManager. BLoCs and other components don't need to be aware of how their dependencies are created, making the code more maintainable and testable. +- Singleton Pattern + - The DependencyManager utilizes the Singleton pattern to ensure only one instance exists throughout the application's lifetime. This guarantees consistent access to the initialized dependencies. + +### Issues with this Approach + +- Circular Dependency Issue + - It's something when first thing depends on second and second depends on first, creating a deadlock. + - Solution to this I explain later [here](#circular-dependency). + +```dart +Β  Β  final vaahApiService = ApiService( +Β  Β  Β  interceptors: [ +Β  Β  Β  Β  ApiTokenInterceptor(() => authenticationRepository), +Β  Β  Β  ], +Β  Β  ); + +Β  Β  final authenticationService = AuthenticationService.vaah( +Β  Β  Β  apiService: vaahApiService, +Β  Β  ); + +Β  Β  authenticationRepository = AuthenticationRepository( +Β  Β  Β  authenticationService: authenticationService, +Β  Β  ); +``` + +### Example + +- `init` Method + - This asynchronous method initializes the essential services & repositories +- For instance, `authenticationRepository` + - DependencyManager Creates the core `AuthenticationRepository` `instance`, injecting both `secureStorageService` and `authenticationService`. And Let's say some other repository depends on secureStorageService we do not need to create a new one, we can utilize existing one. +- Exposing variables + - To expose you need to create direct variable in `DependencyManager` with `static late final` + - For instance, `static late final AuthenticationRepository authenticationRepository;` + +`dependency_manager.dart` + +```dart +/// DependencyManager: Singleton Class +abstract class DependencyManager { + static bool _isInitialized = false; + + static late final AuthenticationRepository authenticationRepository; + + static Future init() async { + if (_isInitialized) { + return true; + } + + final secureStorageService = StorageService.secure(); + + final vaahApiService = ApiService( + interceptors: [ + ApiTokenInterceptor(() => authenticationRepository), + ], + )..addSerializerFunctions(VaahResponseSerializers.defaults()); + + final authenticationService = AuthenticationService.vaah( + apiService: vaahApiService, + ); + + authenticationRepository = AuthenticationRepository( + storageService: secureStorageService, + authenticationService: authenticationService, + ); + await authenticationRepository.init(); + + _isInitialized = true; + return _isInitialized; + } +} +``` + +### Usage + +```dart + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AuthenticationBloc( + authenticationRepository: DependencyManager.authenticationRepository, + )..add(const InitializationRequested()), + ), + ... + ], + child: child, + ); +``` + +## Services + +### Key Points + +Benefits of Layered Architecture for Services + +- Abstract Interfaces + - By defining abstract interfaces for your services, you create a clear contract that all concrete implementations must adhere to. This promotes flexibility as you can easily swap out different implementations without affecting the rest of your application. For example this is so useful when you need to feature flag some features. +- Improved Testability + - Abstract interfaces enable you to easily mock or stub service implementations during unit tests. This allows you to isolate the logic of your application components from the specific behavior of external services, making your tests more robust and easier to maintain +- Separation of Concerns + - this architecture promotes a clean separation of concerns. Services are responsible for specific functionalities, while other components interact with them through well-defined interfaces. This reduces coupling and improves code maintainability. +- Enhanced Maintainability + - By clearly defining service interfaces and their responsibilities, you make it easier to understand and maintain your application's architecture. Changes to service implementations can be made with minimal impact on other parts of the application as long as the interface remains unchanged. +- Future-Proofing + - The layered architecture provides a solid foundation for future enhancements. You can easily add new service implementations or modify existing ones without significantly impacting the rest of your application. + +### Example + +- This code defines a layered approach for local storage in a Flutter application + - `StorageService` is an abstract interface defining methods for get, set, and remove data. + - `StorageServiceNoOp` is a no-op implementation for testing purposes. + - `SecureStorageService` in an implementation using flutter_secure_storage for secure data storage. + +```dart +// lib/data/services/local_storage/storage_service.dart +abstract class StorageService { + const StorageService(); + + factory StorageService.noOp() => const StorageServiceNoOp(); + + factory StorageService.secure() => const SecureStorageService(); + + Future get({required String key}); + + Future set({required String key, required Object value}); + + Future remove({required String key}); +} + + +// lib/data/services/local_storage/no_op_service.dart +class StorageServiceNoOp implements StorageService { + const StorageServiceNoOp(); + + @override + Future get({required String key}) async => null; + + @override + Future set({required String key, required Object value}) async => null; + + @override + Future remove({required String key}) async => null; +} + + +// lib/data/services/local_storage/secure_storage_service.dart +class SecureStorageService implements StorageService { + const SecureStorageService({this.storage = const FlutterSecureStorage()}); + + final FlutterSecureStorage storage; + + @override + Future get({required String key}) async { + final match = await storage.read(key: key); + if (match == null) return null; + return jsonDecode(match) as T; + } + + @override + Future set({required String key, required Object value}) async { + await storage.write(key: key, value: jsonEncode(value)); + } + + @override + Future remove({required String key}) async { + await storage.delete(key: key); + } +} +``` + +### Usage + +```dart +final secureStorageService = StorageService.secure(); +``` + +## Repositories + +Centralizing Data Access Logic + +### Key points + +- Source of Truth + - Repositories act as a single source of truth for accessing and managing data within your application's domain layer. + - They encapsulate the logic for fetching, storing, and manipulating data, often combining data from multiple services. +- Separation of Concerns + - Repositories shield the rest of your application (BLoCs, UI components) from the complexities of dealing with different data sources and services directly + - Repositories promote a clean separation of concerns by isolating data access logic from the presentation layer (Blocs, UI). This improves code maintainability and testability. +- Data Transformation and Validation + - Repositories can handle data transformation and validation before exposing it to other layers. + +### Example + +- Authentication States + - Defines three states: unknown (initial), authenticated (user logged in), and unauthenticated (user logged out). +- AuthenticationRepository + - Handles user authentication logic. + - Stores the currently logged-in user. + - Provides a stream (`statusStream`) to notify about authentication state changes. + - Uses `storageService` to store/retrieve user data securely. + - Uses `authenticationService` to perform signup, signin, and signout operations. + +```dart +enum AuthenticationStatus { + unknown, + authenticated, + unauthenticated, +} + +extension AuthenticationStatusExtensions on AuthenticationStatus { + bool get isAuthenticated => this == AuthenticationStatus.authenticated; +} + +const String _userKey = 'secure_user'; + +class AuthenticationRepository implements DisposableRepository { + AuthenticationRepository({ + required this.storageService, + required this.authenticationService, + }); + + final StorageService storageService; + final AuthenticationService authenticationService; + + @override + Future init() async { + try { + final decodedUser = await storageService.get>( + key: _userKey, + ); + if (decodedUser == null) { + _unsetUser(); + } else { + final user = User.fromJson(decodedUser); + _setUser(user); + } + } catch (e) { + _unsetUser(); + } + } + + @override + Future dispose() async { + _statusController.close(); + } + + final BehaviorSubject _statusController = + BehaviorSubject.seeded(AuthenticationStatus.unknown); + + Stream get statusStream { + return _statusController.stream; + } + + AuthenticationStatus get statusCurrent { + return _statusController.value; + } + + User? _user; + + User? get user => _user; + + void _unsetUser() { + _user = null; + _statusController.add(AuthenticationStatus.unauthenticated); + } + + void _setUser(User user) { + _user = user; + _statusController.add(AuthenticationStatus.authenticated); + } + + Future signUp({ + required String email, + required String name, + required String username, + required String password, + }) async { + final authenticatedUser = await authenticationService.signUp( + email: email, + name: name, + username: username, + password: password, + ); + await storageService.set( + key: _userKey, + value: authenticatedUser.toJson(), + ); + + _setUser(authenticatedUser); + return authenticatedUser; + } + + Future signIn({ + required String username, + required String password, + }) async { + final authenticatedUser = await authenticationService.signIn( + username: username, + password: password, + ); + await storageService.set( + key: _userKey, + value: authenticatedUser.toJson(), + ); + + _setUser(authenticatedUser); + return authenticatedUser; + } + + Future signOut() async { + await authenticationService.signOut(); + await storageService.remove( + key: _userKey, + ); + + _unsetUser(); + } +} +``` + +## BLoC + +As discussed, only repositories are accessible from dependency manager, no services are exposed, BLoC can depend multiple repositories in order to operate. + +### Key Points + +- What does it do? + - Manages the state of the application. Handles events. + - Interacts with Repository(s) to perform operations. + - Emits a stream of State objects to notify the UI about changes. +- Improved Code Organization + - Separates business logic from the UI, making the code more modular and easier to understand. +- Enhanced Testability + - Allows you to easily test the BLoC's behavior in isolation. +- Improved Maintainability + - Makes it easier to modify and extend the application's logic without affecting other parts of the codebase. +- Enhanced User Experience + - Provides a more predictable and responsive user interface by managing state changes effectively. + +### Best Practices + +- Use descriptive and meaningful names for classes, events, states, and methods. +- Follow consistent naming conventions throughout your project for better readability and maintainability. + +#### Events + +- Descriptive action verbs +- Use action verbs that clearly describe the intent of the event +- Avoid generic names like `ButtonClicked` or `DataUpdated` + +#### States + +- Descriptive adjectives +- Use adjectives to describe the current state of the system. +- Avoid vague names like StateA, StateB. + +#### BLoC + +- Class Name + - Follow the convention of using "Bloc" as a suffix for Bloc classes. + - name should clearly indicate what the Bloc is responsible for. +- Event Handler Method Names + - Use the prefix `_on` followed by the corresponding event name. + - This convention is commonly used in Bloc implementations to clearly identify event handlers. + - e.g. `on(_onInitializationRequested);` + +### Example + +`lib/features/authentication/bloc/authentication_bloc.dart` + +- Extends Bloc and manages authentication state based on events. +- The AuthenticationBloc constructor injects an AuthenticationRepository for interacting with authentication logic. +- Event handlers (`_on*Requested`) handle specific events + - `_onInitializationRequested`: Checks the initial authentication state and emits AuthenticationSuccessful if a user is logged in, otherwise stays in the initial state. + - `_onSignUpRequested`, `_onSignInRequested`: Handle signup and signin requests respectively, interacting with the repository and emitting loading/success/failure states. + - `_onSignOutRequested`: Handles user signout, interacting with the repository and emitting loading/success/failure states. + +```dart +class AuthenticationBloc extends Bloc { + AuthenticationBloc({ + required this.authenticationRepository, + }) : super(const AuthenticationInitial()) { + on(_onInitializationRequested); + on(_onSignUpRequested); + on(_onSignInRequested); + on(_onSignOutRequested); + } + + final AuthenticationRepository authenticationRepository; + + void _onInitializationRequested( + InitializationRequested event, + Emitter emit, + ) { + if (authenticationRepository.statusCurrent.isAuthenticated && + authenticationRepository.user != null) { + emit(AuthenticationSuccessful(user: authenticationRepository.user!)); + } + } + + Future _onSignUpRequested( + SignUpRequested event, + Emitter emit, + ) async { + try { + emit(const AuthenticationLoading()); + final user = await authenticationRepository.signUp( + email: event.email, + name: event.name, + username: event.username, + password: event.password, + ); + emit(AuthenticationSuccessful(user: user)); + } catch (e, st) { + Log.exception(e, stackTrace: st, hint: 'user-signup-failed'); + emit(const AuthenticationFailed( + message: VaahStringConstants.somethingWentWrong, + )); + } + } + + Future _onSignInRequested( + SignInRequested event, + Emitter emit, + ) async { + try { + emit(const AuthenticationLoading()); + final user = await authenticationRepository.signIn( + username: event.username, + password: event.password, + ); + emit(AuthenticationSuccessful(user: user)); + } catch (e, st) { + Log.exception(e, stackTrace: st, hint: 'user-signin-failed'); + emit(const AuthenticationFailed( + message: VaahStringConstants.somethingWentWrong, + )); + } + } + + Future _onSignOutRequested( + SignOutRequested event, + Emitter emit, + ) async { + try { + emit(const AuthenticationLoading()); + await authenticationRepository.signOut(); + emit(const AuthenticationChanged(message: StringConstants.signedOutSuccessfully)); + } catch (e, st) { + Log.exception(e, stackTrace: st, hint: 'user-signout-failed'); + emit(const AuthenticationFailed( + message: VaahStringConstants.somethingWentWrong, + )); + } + } +} +``` + +`lib/features/authentication/bloc/authentication_event.dart` + +- AuthenticationEvent: Sealed class representing events that trigger state changes + +```dart +@immutable +sealed class AuthenticationEvent { + const AuthenticationEvent(); +} + +final class InitializationRequested extends AuthenticationEvent { + const InitializationRequested(); +} + +final class SignUpRequested extends AuthenticationEvent { + const SignUpRequested({ + required this.email, + required this.name, + required this.username, + required this.password, + }); + + final String email; + final String name; + final String username; + final String password; +} + +final class SignInRequested extends AuthenticationEvent { + const SignInRequested({ + required this.username, + required this.password, + }); + + final String username; + final String password; +} + +final class SignOutRequested extends AuthenticationEvent { + const SignOutRequested(); +} +``` + +`lib/features/authentication/bloc/authentication_state.dart` + +- AuthenticationState: Sealed class representing different states of authentication. + +```dart +@immutable +sealed class AuthenticationState { + const AuthenticationState(); +} + +final class AuthenticationInitial extends AuthenticationState { + const AuthenticationInitial(); +} + +final class AuthenticationLoading extends AuthenticationState { + const AuthenticationLoading(); +} + +final class AuthenticationSuccessful extends AuthenticationState { + const AuthenticationSuccessful({ + required this.user, + }); + + final User user; +} + +final class AuthenticationChanged extends AuthenticationState { + const AuthenticationChanged({ + required this.message, + }); + + final String message; +} + +final class AuthenticationFailed extends AuthenticationState { + const AuthenticationFailed({ + required this.message, + }); + + final String message; +} +``` + +### Usage + +On somewhere core level we initialize this bloc because it is used application wide, otherwise for other blocs for one screen only we can initialize for that screen only + +```dart + MultiBlocProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => AuthenticationBloc( + authenticationRepository: DependencyManager.authenticationRepository, + )..add(const InitializationRequested()), + ), + ], + child: ... , + ) +``` + +- BlocBuilder + - Primarily used to build UI based on the current state of a Bloc. + +```dart +BlocBuilder( + builder: (context, state) { + if (state is AuthenticationLoading) { + return const CircularProgressIndicator(); + } else if (state is AuthenticationSuccessful) { + return Text('Authentication Successful'); + } else if (state is AuthenticationFailed) { + return Text('Authentication Failed'); + } else { + return Text('Not authenticated'); + } + }, +) +``` + +- BlocListener + - Primarily used to perform side effects in response to state changes within a Bloc. + +```dart +BlocListener( + listener: (context, state) { + if (state is AuthenticationFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(state.message)), + ); + } + }, + child: // Your UI here, +) +``` + +- BlocConsumer + - Combines the functionality of both BlocListener and BlocBuilder. + +```dart +BlocConsumer( + listener: (context, state) { + if (state is AuthenticationFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(state.message)), + ); + } + }, + builder: (context, state) { + if (state is AuthenticationLoading) { + return const CircularProgressIndicator(); + } else if (state is AuthenticationSuccessful) { + return Text('Authentication Successful'); + } else if (state is AuthenticationFailed) { + return Text('Authentication Failed'); + } else { + return Text('Not authenticated'); + } + }, +) +``` + +## Circular Dependency + +### The problem + +```dart +Β  Β  final vaahApiService = ApiService( +Β  Β  Β  interceptors: [ +Β  Β  Β  Β  ApiTokenInterceptor(() => authenticationRepository), +Β  Β  Β  ], +Β  Β  ); + +Β  Β  final authenticationService = AuthenticationService.vaah( +Β  Β  Β  apiService: vaahApiService, +Β  Β  ); + +Β  Β  authenticationRepository = AuthenticationRepository( +Β  Β  Β  authenticationService: authenticationService, +Β  Β  ); +``` + +1. `ApiTokenInterceptor` needs `AuthenticationRepository` to retrieve the user's securely locally stored API token. +2. `AuthenticationRepository` depends on `AuthenticationService` for authentication logic. +3. `AuthenticationService` relies on `ApiService` to make API calls. +4. `ApiService` needs `ApiTokenInterceptor`. + +### The solution + +- `ApiService` is created with `ApiTokenInterceptor`. However, the interceptor uses a lambda function (`() => authenticationRepository`) to delay retrieving the `AuthenticationRepository` instance. +- `AuthenticationService` is created immediately after `ApiService` using the already created `ApiService` +- Finally, `AuthenticationRepository` is created immediately after `AuthenticationService`. +- One more thing to note here: We need to initialize services and repo in this perticular order (We can not initialize anything between given initializations). We always first initialize `ApiService`, then `AuthenticationService`, and then `AuthenticationRepository` -- `Immediately` one after another just to ensure that no other service that depends on `ApiService` makes any request inbetween. + +### The lambda function + +- The lambda function `() => authenticationRepository` is used within the `ApiTokenInterceptor`. +- This function delays the retrieval of the `authenticationRepository` until it's actually needed. +- By the time the interceptor needs the repository during an API call, init would have already completed creating all the services and the repositories. And thus needed repository instance would be available by that time. diff --git a/content/2.mobile-app/3.poc/3.code-generation.md b/content/2.mobile-app/3.poc/3.code-generation.md new file mode 100644 index 0000000..b6fe8d3 --- /dev/null +++ b/content/2.mobile-app/3.poc/3.code-generation.md @@ -0,0 +1,6 @@ +--- +title: Automatic code generation +description: +--- + +## TODO: diff --git a/content/2.mobile-app/3.poc/_dir.yml b/content/2.mobile-app/3.poc/_dir.yml new file mode 100644 index 0000000..507a322 --- /dev/null +++ b/content/2.mobile-app/3.poc/_dir.yml @@ -0,0 +1 @@ +title: Proof of Concepts diff --git a/content/2.mobile-app/_dir.yml b/content/2.mobile-app/_dir.yml new file mode 100644 index 0000000..2c9ce38 --- /dev/null +++ b/content/2.mobile-app/_dir.yml @@ -0,0 +1 @@ +title: Mobile App