Giter Site home page Giter Site logo

leventkantaroglu / flutter-clean-architecture-riverpod Goto Github PK

View Code? Open in Web Editor NEW

This project forked from uuttssaavv/flutter-clean-architecture-riverpod

1.0 0.0 1.0 135 KB

Clean architecture in Flutter using Riverpod.

Home Page: https://medium.com/@theutsavg1/implementing-clean-architecture-with-riverpod-for-modular-flutter-apps-7d21acfa9db0

Shell 0.18% Ruby 0.97% Objective-C 0.03% Kotlin 0.09% Dart 98.44% Swift 0.29%

flutter-clean-architecture-riverpod's Introduction

Coverage HitCount

Flutter Clean Architecture with Riverpod

A Flutter app that uses the "Dummy Json" api.

Features

  • Login
  • Fetch products
  • Search products
  • Pagination

What is used in this project?

  • Riverpod Used for state management

  • Freezed Code generation

  • Dartz Functional Programming Either<Left,Right>

  • Auto Route Navigation package that uses code generation to simplify route setup

  • Dio Http client for dart. Supports interceptors and global configurations

  • Shared Preferences Persistent storage for simple data

  • Flutter and Dart And obviously flutter and dart ๐Ÿ˜…

Project Description

Data

The data layer is the outermost layer of the application and is responsible for communicating with the server-side or a local database and data management logic. It also contains repository implementations.

a. Data Source

Describes the process of acquiring and updating the data. Consist of remote and local Data Sources. Remote Data Source will perform HTTP requests on the API. At the same time, local Data sources will cache or persist data.

b. Repository

The bridge between the Data layer and the Domain layer. Actual implementations of the repositories in the Domain layer. Repositories are responsible for coordinating data from the different Data Sources.

Domain

The domain layer is responsible for all the business logic. It is written purely in Dart without flutter elements because the domain should only be concerned with the business logic of the application, not with the implementation details.

a. Providers

Describes the logic processing required for the application. Communicates directly with the repositories.

b. Repositories

Abstract classes that define the expected functionality of outer layers.

Presentation

The presentation layer is the most framework-dependent layer. It is responsible for all the UI and handling the events in the UI. It does not contain any business logic.

a. Widget (Screens/Views)

Widgets notify the events and listen to the states emitted from the StateNotifierProvider.

b. Providers

Describes the logic processing required for the presentation. Communicates directly with the Providers from the domain layer.

Project Description

  • main.dart file has services initialization code and wraps the root MyApp with a ProviderScope
  • main/app.dart has the root MaterialApp and initializes AppRouter to handle the route throughout the application.
  • services abstract app-level services with their implementations.
  • The shared folder contains code shared across features
    • theme contains general styles (colors, themes & text styles)
    • model contains all the Data models needed in the application.
    • http is implemented with Dio.
    • storage is implemented with SharedPreferences.
    • Service locator pattern and Riverpod are used to abstract services when used in other layers.

For example:

final storageServiceProvider = Provider((ref) {
  return SharedPrefsService();
});

// Usage:
// ref.watch(storageServiceProvider);
  • The features folder: the repository pattern is used to decouple logic required to access data sources from the domain layer. For example, the DashboardRepository abstracts and centralizes the various functionality required to fetch the Product from the remote.
abstract class DashboardRepository {
  Future<Either<AppException, PaginatedResponse>> fetchProducts({required int skip});

  Future<Either<AppException, PaginatedResponse>> searchProducts({required int skip, required String query});
}

The repository implementation with the DashboardDatasource:

class DashboardRepositoryImpl extends DashboardRepository {
  final DashboardDatasource dashboardDatasource;
  DashboardRepositoryImpl(this.dashboardDatasource);

  @override
  Future<Either<AppException, PaginatedResponse>> fetchProducts(
      {required int skip}) {
    return dashboardDatasource.fetchPaginatedProducts(skip: skip);
  }

  @override
  Future<Either<AppException, PaginatedResponse>> searchProducts(
      {required int skip, required String query}) {
    return dashboardDatasource.searchPaginatedProducts(
        skip: skip, query: query);
  }
}

Using Riverpod Provider to access this implementation:

final dashboardRepositoryProvider = Provider<DashboardRepository>((ref) {
  final datasource = ref.watch(dashboardDatasourceProvider(networkService));

  return DashboardRepositoryImpl(datasource);
});

And finally accessing the repository implementation from the Presentation layer using a Riverpod StateNotifierProvider:

final dashboardNotifierProvider =
    StateNotifierProvider<DashboardNotifier, DashboardState>((ref) {
  final repository = ref.watch(dashboardRepositoryProvider);
  return DashboardNotifier(repository)..fetchProducts();
});

Notice how the abstract NetworkService is accessed from the repository implementation and then the abstract DashboardRepository is accessed from the DashboardNotifier and how each of these layers acheive separation and scalability by providing the ability to switch implementation and make changes and/or test each layer seaparately.

Testing

The test folder mirrors the lib folder in addition to some test utilities.

state_notifier_test is used to test the StateNotifier and mock Notifier.

mocktail is used to mock dependecies.

1. Testing the simple Provider provider:

test('dashboardDatasourceProvider is a DashboardDatasource', () {
    dashboardDataSource = providerContainer.read
    (dashboardDatasourceProvider(networkService));

    expect(
      dashboardDataSource,
      isA<DashboardDatasource>(),
    );
  });

And here is how we can test it separately from Flutter:

void main() {
  late DashboardDatasource dashboardDatasource;
  late DashboardRepository dashboardRepository;
  setUpAll(() {
    dashboardDatasource = MockRemoteDatasource();
    dashboardRepository = DashboardRepositoryImpl(dashboardDatasource);
  });
  test(
    'Should return AppException on failure',
    () async {
      // arrange
      when(() => dashboardDatasource.searchPaginatedProducts(skip: any(named: 'skip'), query: any(named: 'query')))
          .thenAnswer(
        (_) async => Left(ktestAppException),
      );

      // assert
      final response = await dashboardRepository.searchProducts(skip: 1, query: '');

      // act
      expect(response.isLeft(), true);
    },
  );
}

class MockRemoteDatasource extends Mock implements DashboardRemoteDatasource {}

To explore test coverage

run bash gencov.sh

Folder Structure

lib
โ”œโ”€โ”€ configs
โ”‚ โ””โ”€โ”€ app_configs.dart
โ”‚
โ”œโ”€โ”€ main
โ”‚ โ”œโ”€โ”€ app.dart
โ”‚ โ”œโ”€โ”€ app_env.dart
โ”‚ โ”œโ”€โ”€ main_dev.dart
โ”‚ โ”œโ”€โ”€ main_staging.dart
โ”‚ โ””โ”€โ”€ observers.dart
โ”‚
โ”œโ”€โ”€  configs
โ”‚ โ””โ”€โ”€ app_configs.dart
โ”œโ”€โ”€ routes
โ”‚ โ”œโ”€โ”€ app_route.dart
โ”‚ โ””โ”€โ”€ app_route.gr.dart
โ”‚
โ”œโ”€โ”€ services
โ”‚ โ””โ”€โ”€ user_cache_service
โ”‚   โ”œโ”€โ”€ data
โ”‚   โ”‚ โ”œโ”€โ”€ datasource
โ”‚   โ”‚ โ”‚ โ””โ”€โ”€ user_local_datasource.dart
โ”‚   โ”‚ โ””โ”€โ”€ repositories
โ”‚   โ”‚  โ””โ”€โ”€ user_repository_impl.dart
โ”‚   โ”œโ”€โ”€ domain
โ”‚   โ”‚ โ”œโ”€โ”€ providers
โ”‚   โ”‚ โ”‚ โ””โ”€โ”€ user_cache_provider.dart
โ”‚   โ”‚ โ””โ”€โ”€ repositories
โ”‚   โ”‚   โ””โ”€โ”€ user_cache_repository.dart
โ”‚   โ””โ”€โ”€ presentation
โ”‚
โ”œโ”€โ”€ shared
โ”‚ โ”œโ”€โ”€ data
โ”‚ โ”‚ โ”œโ”€โ”€ local
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ shared_prefs_storage_service.dart
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ storage_service.dart
โ”‚ โ”‚ โ””โ”€โ”€ remote
โ”‚ โ”‚   โ”œโ”€โ”€ dio_network_service.dart
โ”‚ โ”‚   โ”œโ”€โ”€ network_service.dart
โ”‚ โ”‚   โ””โ”€โ”€ remote.dart
โ”‚ โ”œโ”€โ”€ domain
โ”‚ โ”‚ โ”œโ”€โ”€ models
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ product
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ product_model.dart
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ product_model.freezed.dart
โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ product_model.g.dart
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ user
โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ user_model.dart
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ models.dart
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ paginated_response.dart
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ parse_response.dart
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ response.dart
โ”‚ โ”‚ โ””โ”€โ”€ providers
โ”‚ โ”‚   โ”œโ”€โ”€ dio_network_service_provider.dart
โ”‚ โ”‚   โ””โ”€โ”€ sharedpreferences_storage_service_provider.dart
โ”‚ โ”œโ”€โ”€ exceptions
โ”‚ โ”‚ โ””โ”€โ”€ http_exception.dart
โ”‚ โ”œโ”€โ”€ mixins
โ”‚ โ”‚ โ””โ”€โ”€ exception_handler_mixin.dart
โ”‚ โ”œโ”€โ”€ theme
โ”‚ โ”‚ โ”œโ”€โ”€ app_colors.dart
โ”‚ โ”‚ โ”œโ”€โ”€ app_theme.dart
โ”‚ โ”‚ โ”œโ”€โ”€ test_styles.dart
โ”‚ โ”‚ โ””โ”€โ”€ text_theme.dart
โ”‚ โ”œโ”€โ”€ widgets
โ”‚ โ”‚ โ”œโ”€โ”€ app_error.dart
โ”‚ โ”‚ โ””โ”€โ”€ app_loading.dart
โ”‚ โ””โ”€โ”€ globals.dart
โ”‚
โ”œโ”€โ”€  features
โ”‚ โ”œโ”€โ”€  authentication
โ”‚ โ”‚ โ”œโ”€โ”€  data
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€  datasource
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€  auth_local_data_source.dart
โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ auth_remote_data_source.dart
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ repositories
โ”‚ โ”‚ โ”‚   โ””โ”€โ”€ atuhentication_repository_impl.dart
โ”‚ โ”‚ โ”œโ”€โ”€  domain
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€  providers
โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ login_provider.dart
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ repositories
โ”‚ โ”‚ โ”‚   โ””โ”€โ”€ auth_repository.dart
โ”‚ โ”‚ โ””โ”€โ”€ presentation
โ”‚ โ”‚   โ”œโ”€โ”€  providers
โ”‚ โ”‚   โ”‚ โ”œโ”€โ”€  state
โ”‚ โ”‚   โ”‚ โ”‚ โ”œโ”€โ”€  auth_notifier.dart
โ”‚ โ”‚   โ”‚ โ”‚ โ”œโ”€โ”€  auth_state.dart
โ”‚ โ”‚   โ”‚ โ”‚ โ””โ”€โ”€  auth_state.freezed.dart
โ”‚ โ”‚   โ”‚ โ””โ”€โ”€ auth_providers.dart
โ”‚ โ”‚   โ”œโ”€โ”€  screens
โ”‚ โ”‚   โ”‚ โ””โ”€โ”€ login_screen.dart
โ”‚ โ”‚   โ””โ”€โ”€ widgets
โ”‚ โ”‚     โ”œโ”€โ”€  auth_field.dart
โ”‚ โ”‚     โ””โ”€โ”€ button.dart
โ”‚ โ”œโ”€โ”€  dashboard
....

Run this project

Clone this repository

git clone https://github.com/Uuttssaavv/flutter-clean-architecture-riverpod

Go to the project directory

cd flutter-clean-architecture-riverpod

Get all the packages

flutter pub get

Run the build runner command

flutter pub run build_runner build

Run the project

flutter run or simply press F5 key if you are using VSCode

About Me

Do visit my portfolio site or connect with me on linkedin

flutter-clean-architecture-riverpod's People

Contributors

leventkantaroglu avatar uuttssaavv avatar

Stargazers

 avatar

Forkers

ibrahimtalha0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.