flutter-clean-architecture-riverpod

repository·master·Indexed 20 days ago

https://github.com/uuttssaavv/flutter-clean-architecture-riverpod

A reference implementation of Flutter Clean Architecture using Riverpod for state management. The project demonstrates the decoupling of business logic from UI and data sources using the Repository pattern and functional programming. It features a three-layer architecture (Data, Domain, and Presentation), utilizes Dio for network services, Auto Route for navigation, and implements functional error handling using the Either type.

Tokens
6.4K
Snippets
23
Records
25
Agent score
71%

What's inside flutter-clean-architecture-riverpod

  1. Project Folder Structure Overview

    master

    The project follows a structured directory pattern to maintain Clean Architecture:

    • lib/main/: App entry points and environment configurations.
    • lib/routes/: Navigation configuration using Auto Route.
    • lib/services/: Feature-specific implementations (Data, Domain, Presentation).
    • lib/shared/: Cross-cutting concerns:
      • data/: Shared local/remote data sources and network services (Dio).
      • domain/: Shared models (Freezed) and response parsers.
      • theme/: Global styles and text themes.
      • widgets/: Common UI components (loading, error states).
    • lib/features/: Domain-specific modules (e.g., authentication, dashboard) following the Data/Domain/Presentation split.
  2. How Clean Architecture layers work together

    master

    This project implements Clean Architecture divided into three primary layers to ensure separation of concerns and scalability:

    1. Data Layer (Outermost): Responsible for communicating with servers or local databases. It contains:

      • Data Sources: Remote (HTTP requests via Dio) and Local (caching/persistence via SharedPreferences).
      • Repositories (Implementations): The actual logic that coordinates data between different Data Sources.
    2. Domain Layer (Business Logic): Pure Dart layer containing no Flutter dependencies. It includes:

      • Repositories (Abstract Classes): Define the expected functionality/contracts for the outer layers.
      • Providers: Logic processing that communicates directly with the repositories.
    3. Presentation Layer (Framework Dependent): Handles UI and user events. It includes:

      • Widgets (Screens/Views): Listen to states emitted from StateNotifierProvider and notify events.
      • Providers: Presentation-specific logic that communicates with the Domain layer's providers.
  3. Customize the iOS launch screen assets

    master

    You can customize the iOS launch screen by replacing the image files located in the ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.

    Alternatively, you can manage these assets using Xcode:

    1. Open the iOS project using open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog.
    open ios/Runner.xcworkspace
  4. Implement the Repository Pattern with Riverpod

    master

    To decouple data access from business logic, use abstract repository classes in the Domain layer and provide their implementations via Riverpod. This allows you to switch implementations (e.g., for testing) without changing the business logic.

    Workflow:

    1. Define an abstract repository in the Domain layer.
    2. Implement the repository in the Data layer using a Data Source.
    3. Use a Riverpod Provider to inject the implementation.
    4. Access the implementation in the Presentation layer via a StateNotifierProvider.
    // 1. Domain Layer: Abstract Repository
    abstract class DashboardRepository {
      Future<Either<AppException, PaginatedResponse>> fetchProducts({required int skip});
    }
    
    // 2. Data Layer: Implementation
    class DashboardRepositoryImpl extends DashboardRepository {
      final DashboardDatasource dashboardDatasource;
      DashboardRepositoryImpl(this.dashboardDatasource);
    
      @override
      Future<Either<AppException, PaginatedResponse>> fetchProducts({required int skip}) {
        return dashboardDatasource.fetchPaginatedProducts(skip: skip);
      }
    }
    
    // 3. Dependency Injection via Riverpod
    final dashboardRepositoryProvider = Provider<DashboardRepository>((ref) {
      final datasource = ref.watch(dashboardDatasourceProvider(networkService));
      return DashboardRepositoryImpl(datasource);
    });
    
    // 4. Presentation Layer: Accessing via StateNotifier
    final dashboardNotifierProvider = StateNotifierProvider<DashboardNotifier, DashboardState>((ref) {
      final repository = ref.watch(dashboardRepositoryProvider);
      return DashboardNotifier(repository)..fetchProducts();
    });
  5. Setup and Run the project

    master

    Follow these steps to clone, prepare, and run the Flutter application locally.

    Prerequisites:

    • Flutter SDK installed
    • Dart installed

    Steps:

    1. Clone the repository.
    2. Navigate to the directory.
    3. Install dependencies.
    4. Run code generation (required for Freezed, Auto Route, etc.).
    5. Execute the app.
    git clone https://github.com/Uuttssaavv/flutter-clean-architecture-riverpod
    cd flutter-clean-architecture-riverpod
    flutter pub get
    flutter pub run build_runner build
    flutter run
  6. Initialize and run the application

    master

    The application entrypoint uses mainCommon to configure the environment, system UI, and Riverpod's ProviderScope.

    To run the app in production mode, call main() which defaults to AppEnvironment.PROD. To run with a different environment, call mainCommon with the desired AppEnvironment value.

    Key initialization steps performed in mainCommon:

    1. Environment Setup: Calls EnvInfo.initialize(environment) to set up environment-specific configurations.
    2. System UI: Configures the status bar using SystemChrome.setSystemUIOverlayStyle.
    3. Riverpod Integration: Wraps the root MyApp widget in a ProviderScope and attaches Observers() to the observers list to monitor provider changes.
    void main() => mainCommon(AppEnvironment.PROD);
    
    Future<void> mainCommon(AppEnvironment environment) async {
      WidgetsFlutterBinding.ensureInitialized();
      EnvInfo.initialize(environment);
      SystemChrome.setSystemUIOverlayStyle(
        SystemUiOverlayStyle.light.copyWith(
          statusBarColor: Colors.black,
          statusBarBrightness: Brightness.light,
        ),
      );
      runApp(ProviderScope(
        observers: [
          Observers(),
        ],
        child: MyApp(),
      ));
    }
  7. Access predefined TextThemes

    master

    The TextThemes class provides static getters to retrieve standard TextTheme configurations used throughout the application. You can access the main theme, a dark mode variant, or a primary color variant. These themes map standard Flutter TextTheme properties (like bodyLarge, displayMedium, etc.) to specific AppTextStyles defined in the project.

    // Access the main text theme
    TextTheme mainTheme = TextThemes.textTheme;
    
    // Access the dark mode text theme
    TextTheme darkTheme = TextThemes.darkTextTheme;
    
    // Access the primary color text theme
    TextTheme primaryTheme = TextThemes.primaryTextTheme;
  8. Manage application theme mode with appThemeProvider

    master

    The application uses a StateNotifierProvider named appThemeProvider to manage the current ThemeMode (light or dark). This provider is backed by AppThemeModeNotifier, which persists the user's theme preference using a StorageService via the APP_THEME_STORAGE_KEY constant.

    To change the theme, call the toggleTheme() method on the notifier. The notifier automatically handles reading the saved theme from storage on initialization via getCurrentTheme().

    // To toggle the theme (e.g., in a settings button)
    ref.read(appThemeProvider.notifier).toggleTheme();
    
    // To watch the current theme mode
    final themeMode = ref.watch(appThemeProvider);
  9. Access application theme data via AppTheme

    master

    The AppTheme class provides static getters to retrieve the ThemeData for both light and dark modes. These themes are pre-configured with the project's custom colors (AppColors), text styles (AppTextStyles, TextThemes), and scaffold backgrounds.

    • AppTheme.lightTheme: Returns ThemeData configured for light brightness.
    • AppTheme.darkTheme: Returns ThemeData configured for dark brightness.
    MaterialApp(
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      themeMode: ref.watch(appThemeProvider),
      // ...
    );
  10. Access the application color palette via AppColors

    master

    The AppColors class provides a centralized set of static color constants used throughout the application. You can access these colors directly using the class name to ensure UI consistency.

    Available colors:

    • primary: 0xff1DA1F2
    • error: 0xffFC698C
    • black: 0xff14171A
    • white: 0xffffffff
    • lightGrey: 0xffAAB8C2
    • extraLightGrey: 0xffE1E8ED
    import 'package:flutter/material.dart';
    import 'package:your_project_name/shared/theme/app_colors.dart';
    
    // Usage example
    Container(
      color: AppColors.primary,
    )