piihuynh / flutter_auth_app_tdd_clean

Flutter auth app with TDD Clean Architecture

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool


Flutter App Auth πŸ“±

This is an App with Auth Functions like Login and Register. All API using reqres.in.
This app also implementing Flutter Clean Architecture with TDD.

light-theme.mp4
dark-theme.mp4

Pre-requisites πŸ“

Technology Recommended Version Installation Guide
Flutter v3.10.x Flutter Official Docs
Dart v3.0.x Installed automatically with Flutter

Get Started πŸš€

  • Clone this project
  • Run flutter pub get
  • Run flutter gen-l10n to generate localization files
  • Run flutter pub run build_runner build --delete-conflicting-outputs to generate freezes files
  • Run flutter run --flavor stg -t lib/main.dart --dart-define-from-file .env.stg.json for staging or
  • Run flutter run --flavor prd -t lib/main.dart --dart-define-from-file .env.prd.json for production
  • Run Test flutter test
  • To generate launcher icon based on Flavor dart pub run flutter_launcher_icons:main -f flutter_launcher_icons*
  • To generate mock class dart pub run build_runner build

Feature βœ…

  • BLoC State Management
  • Clean Architecture with TDD
    • Unit Test
    • Widget Test
    • BLoC test
  • Theme Configuration: System, Light, Dark
  • Multi-Language: English, Bahasa
  • Login, Register Example
  • Pagination Example
  • Autofill Username and Password

TODO πŸ“

  • Integration Test
  • Implement multi-flavor
  • Implement Go Router

Maestro Test πŸ§ͺ

574ae4cc-e3a9-4755-8494-81bfe8763e00.mp4

Architecture Proposal by Resocoder


architecture-proposal

Project Structure


lib
β”œβ”€β”€ config.dart
β”œβ”€β”€ core
β”‚   β”œβ”€β”€ api
β”‚   β”‚   β”œβ”€β”€ api.dart
β”‚   β”‚   β”œβ”€β”€ dio_client.dart
β”‚   β”‚   β”œβ”€β”€ dio_interceptor.dart
β”‚   β”‚   β”œβ”€β”€ isolate_parser.dart
β”‚   β”‚   └── list_api.dart
β”‚   β”œβ”€β”€ app_route.dart
β”‚   β”œβ”€β”€ core.dart
β”‚   β”œβ”€β”€ core_mapper.dart
β”‚   β”œβ”€β”€ error
β”‚   β”‚   β”œβ”€β”€ error.dart
β”‚   β”‚   β”œβ”€β”€ exceptions.dart
β”‚   β”‚   └── failure.dart
β”‚   β”œβ”€β”€ localization
β”‚   β”‚   β”œβ”€β”€ generated
β”‚   β”‚   β”‚   β”œβ”€β”€ strings.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ strings_en.dart
β”‚   β”‚   β”‚   └── strings_id.dart
β”‚   β”‚   β”œβ”€β”€ intl_en.arb
β”‚   β”‚   β”œβ”€β”€ intl_id.arb
β”‚   β”‚   β”œβ”€β”€ l10n.dart
β”‚   β”‚   └── localization.dart
β”‚   β”œβ”€β”€ resources
β”‚   β”‚   β”œβ”€β”€ dimens.dart
β”‚   β”‚   β”œβ”€β”€ images.dart
β”‚   β”‚   β”œβ”€β”€ palette.dart
β”‚   β”‚   β”œβ”€β”€ resources.dart
β”‚   β”‚   └── styles.dart
β”‚   β”œβ”€β”€ usecase
β”‚   β”‚   └── usecase.dart
β”‚   └── widgets
β”‚       β”œβ”€β”€ button.dart
β”‚       β”œβ”€β”€ button_notification.dart
β”‚       β”œβ”€β”€ button_text.dart
β”‚       β”œβ”€β”€ circle_image.dart
β”‚       β”œβ”€β”€ color_loaders.dart
β”‚       β”œβ”€β”€ drop_down.dart
β”‚       β”œβ”€β”€ empty.dart
β”‚       β”œβ”€β”€ loading.dart
β”‚       β”œβ”€β”€ my_appbar.dart
β”‚       β”œβ”€β”€ parent.dart
β”‚       β”œβ”€β”€ spacer_h.dart
β”‚       β”œβ”€β”€ spacer_v.dart
β”‚       β”œβ”€β”€ text_f.dart
β”‚       β”œβ”€β”€ toast.dart
β”‚       └── widgets.dart
β”œβ”€β”€ dependencies_injection.dart
β”œβ”€β”€ features
β”‚   β”œβ”€β”€ auth
β”‚   β”‚   β”œβ”€β”€ auth.dart
β”‚   β”‚   β”œβ”€β”€ data
β”‚   β”‚   β”‚   β”œβ”€β”€ data.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_remote_datasources.dart
β”‚   β”‚   β”‚   β”‚   └── datasources.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ models
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login_response.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login_response.freezed.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login_response.g.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ models.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ register_response.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ register_response.freezed.dart
β”‚   β”‚   β”‚   β”‚   └── register_response.g.dart
β”‚   β”‚   β”‚   └── repositories
β”‚   β”‚   β”‚       β”œβ”€β”€ auth_repository_impl.dart
β”‚   β”‚   β”‚       └── repositories.dart
β”‚   β”‚   β”œβ”€β”€ domain
β”‚   β”‚   β”‚   β”œβ”€β”€ domain.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ entities
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ entities.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login.freezed.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ register.dart
β”‚   β”‚   β”‚   β”‚   └── register.freezed.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ repositories
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_repository.dart
β”‚   β”‚   β”‚   β”‚   └── repositories.dart
β”‚   β”‚   β”‚   └── usecases
β”‚   β”‚   β”‚       β”œβ”€β”€ post_login.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ post_login.freezed.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ post_login.g.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ post_register.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ post_register.freezed.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ post_register.g.dart
β”‚   β”‚   β”‚       └── usecases.dart
β”‚   β”‚   └── pages
β”‚   β”‚       β”œβ”€β”€ login
β”‚   β”‚       β”‚   β”œβ”€β”€ cubit
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ auth_cubit.dart
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ auth_cubit.freezed.dart
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ auth_state.dart
β”‚   β”‚       β”‚   β”‚   └── cubit.dart
β”‚   β”‚       β”‚   β”œβ”€β”€ login.dart
β”‚   β”‚       β”‚   └── login_page.dart
β”‚   β”‚       β”œβ”€β”€ pages.dart
β”‚   β”‚       └── register
β”‚   β”‚           β”œβ”€β”€ cubit
β”‚   β”‚           β”‚   β”œβ”€β”€ cubit.dart
β”‚   β”‚           β”‚   β”œβ”€β”€ register_cubit.dart
β”‚   β”‚           β”‚   β”œβ”€β”€ register_cubit.freezed.dart
β”‚   β”‚           β”‚   └── register_state.dart
β”‚   β”‚           β”œβ”€β”€ register.dart
β”‚   β”‚           └── register_page.dart
β”‚   β”œβ”€β”€ features.dart
β”‚   β”œβ”€β”€ general
β”‚   β”‚   β”œβ”€β”€ general.dart
β”‚   β”‚   └── pages
β”‚   β”‚       β”œβ”€β”€ main
β”‚   β”‚       β”‚   β”œβ”€β”€ main.dart
β”‚   β”‚       β”‚   β”œβ”€β”€ main_page.dart
β”‚   β”‚       β”‚   └── menu_drawer.dart
β”‚   β”‚       β”œβ”€β”€ pages.dart
β”‚   β”‚       β”œβ”€β”€ settings
β”‚   β”‚       β”‚   β”œβ”€β”€ cubit
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ cubit.dart
β”‚   β”‚       β”‚   β”‚   └── settings_cubit.dart
β”‚   β”‚       β”‚   β”œβ”€β”€ settings.dart
β”‚   β”‚       β”‚   └── settings_page.dart
β”‚   β”‚       └── splashscreen
β”‚   β”‚           β”œβ”€β”€ splash_screen_page.dart
β”‚   β”‚           └── splashscreen.dart
β”‚   └── users
β”‚       β”œβ”€β”€ data
β”‚       β”‚   β”œβ”€β”€ data.dart
β”‚       β”‚   β”œβ”€β”€ datasources
β”‚       β”‚   β”‚   β”œβ”€β”€ datasources.dart
β”‚       β”‚   β”‚   └── user_remote_datasources.dart
β”‚       β”‚   β”œβ”€β”€ models
β”‚       β”‚   β”‚   β”œβ”€β”€ models.dart
β”‚       β”‚   β”‚   β”œβ”€β”€ users_response.dart
β”‚       β”‚   β”‚   β”œβ”€β”€ users_response.freezed.dart
β”‚       β”‚   β”‚   └── users_response.g.dart
β”‚       β”‚   └── repositories
β”‚       β”‚       β”œβ”€β”€ repositories.dart
β”‚       β”‚       └── users_repository_impl.dart
β”‚       β”œβ”€β”€ domain
β”‚       β”‚   β”œβ”€β”€ domain.dart
β”‚       β”‚   β”œβ”€β”€ entities
β”‚       β”‚   β”‚   β”œβ”€β”€ entities.dart
β”‚       β”‚   β”‚   β”œβ”€β”€ users.dart
β”‚       β”‚   β”‚   └── users.freezed.dart
β”‚       β”‚   β”œβ”€β”€ repositories
β”‚       β”‚   β”‚   β”œβ”€β”€ repositories.dart
β”‚       β”‚   β”‚   └── users_repository.dart
β”‚       β”‚   └── usecases
β”‚       β”‚       β”œβ”€β”€ get_users.dart
β”‚       β”‚       β”œβ”€β”€ get_users.freezed.dart
β”‚       β”‚       β”œβ”€β”€ get_users.g.dart
β”‚       β”‚       └── usecases.dart
β”‚       β”œβ”€β”€ pages
β”‚       β”‚   β”œβ”€β”€ dashboard
β”‚       β”‚   β”‚   β”œβ”€β”€ cubit
β”‚       β”‚   β”‚   β”‚   β”œβ”€β”€ cubit.dart
β”‚       β”‚   β”‚   β”‚   β”œβ”€β”€ users_cubit.dart
β”‚       β”‚   β”‚   β”‚   β”œβ”€β”€ users_cubit.freezed.dart
β”‚       β”‚   β”‚   β”‚   └── users_state.dart
β”‚       β”‚   β”‚   β”œβ”€β”€ dashboard.dart
β”‚       β”‚   β”‚   └── dashboard_page.dart
β”‚       β”‚   └── pages.dart
β”‚       └── users.dart
β”œβ”€β”€ lzyct_app.dart
β”œβ”€β”€ main_prd.dart
β”œβ”€β”€ main_stg.dart
└── utils
    β”œβ”€β”€ ext
    β”‚   β”œβ”€β”€ context.dart
    β”‚   β”œβ”€β”€ ext.dart
    β”‚   β”œβ”€β”€ string.dart
    β”‚   └── text_theme.dart
    β”œβ”€β”€ helper
    β”‚   β”œβ”€β”€ common.dart
    β”‚   β”œβ”€β”€ constant.dart
    β”‚   β”œβ”€β”€ data_helper.dart
    β”‚   β”œβ”€β”€ data_helper.freezed.dart
    β”‚   β”œβ”€β”€ go_router_refresh_stream.dart
    β”‚   └── helper.dart
    β”œβ”€β”€ services
    β”‚   β”œβ”€β”€ firebase
    β”‚   β”‚   β”œβ”€β”€ firebase.dart
    β”‚   β”‚   β”œβ”€β”€ firebase_crashlogger.dart
    β”‚   β”‚   └── firebase_services.dart
    β”‚   β”œβ”€β”€ hive
    β”‚   β”‚   β”œβ”€β”€ hive.dart
    β”‚   β”‚   └── main_box.dart
    β”‚   └── services.dart
    └── utils.dart

Test Project Structure


test
β”œβ”€β”€ features
β”‚   β”œβ”€β”€ auth
β”‚   β”‚   β”œβ”€β”€ data
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ models
β”‚   β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login_response_test.dart
β”‚   β”‚   β”‚   β”‚   β”‚   └── register_response_test.dart
β”‚   β”‚   β”‚   β”‚   └── repositories
β”‚   β”‚   β”‚   β”‚       └── auth_remote_datasources_test.dart
β”‚   β”‚   β”‚   └── repositories
β”‚   β”‚   β”‚       └── auth_repository_impl_test.dart
β”‚   β”‚   β”œβ”€β”€ domain
β”‚   β”‚   β”‚   └── usecases
β”‚   β”‚   β”‚       β”œβ”€β”€ post_login_test.dart
β”‚   β”‚   β”‚       └── post_register_test.dart
β”‚   β”‚   └── pages
β”‚   β”‚       β”œβ”€β”€ login
β”‚   β”‚       β”‚   β”œβ”€β”€ cubit
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ auth_cubit_test.dart
β”‚   β”‚       β”‚   β”‚   β”œβ”€β”€ auth_cubit_test.mocks.dart
β”‚   β”‚       β”‚   β”‚   └── auth_state_test.dart
β”‚   β”‚       β”‚   └── login_page_test.dart
β”‚   β”‚       └── register
β”‚   β”‚           β”œβ”€β”€ cubit
β”‚   β”‚           β”‚   β”œβ”€β”€ register_cubit_test.dart
β”‚   β”‚           β”‚   β”œβ”€β”€ register_cubit_test.mocks.dart
β”‚   β”‚           β”‚   └── register_state_test.dart
β”‚   β”‚           └── register_page_test.dart
β”‚   β”œβ”€β”€ general
β”‚   β”‚   └── pages
β”‚   β”‚       └── settings
β”‚   β”‚           β”œβ”€β”€ cubit
β”‚   β”‚           β”‚   └── settings_cubit_test.dart
β”‚   β”‚           └── settings_page_test.dart
β”‚   └── users
β”‚       β”œβ”€β”€ data
β”‚       β”‚   β”œβ”€β”€ datasources
β”‚       β”‚   β”‚   β”œβ”€β”€ models
β”‚       β”‚   β”‚   β”‚   └── users_response_test.dart
β”‚       β”‚   β”‚   └── repositories
β”‚       β”‚   β”‚       └── users_remote_datasources_test.dart
β”‚       β”‚   └── repositories
β”‚       β”‚       └── users_repository_impl_test.dart
β”‚       β”œβ”€β”€ domain
β”‚       β”‚   └── usecases
β”‚       β”‚       └── get_users_test.dart
β”‚       └── pages
β”‚           └── dashboard
β”‚               β”œβ”€β”€ cubit
β”‚               β”‚   β”œβ”€β”€ users_cubit_test.dart
β”‚               β”‚   β”œβ”€β”€ users_cubit_test.mocks.dart
β”‚               β”‚   └── users_state_test.dart
β”‚               └── dashboard_page_test.dart
└── helpers
    β”œβ”€β”€ fake_path_provider_platform.dart
    β”œβ”€β”€ json_reader.dart
    β”œβ”€β”€ paths.dart
    β”œβ”€β”€ stubs
    β”‚   β”œβ”€β”€ list_user_empty_response.json
    β”‚   β”œβ”€β”€ list_user_response.json
    β”‚   β”œβ”€β”€ login_success_response.json
    β”‚   β”œβ”€β”€ login_unsuccessful_response.json
    β”‚   β”œβ”€β”€ register_success_response.json
    β”‚   └── register_unsuccessful_response.json
    β”œβ”€β”€ test_mock.dart
    └── test_mock.mocks.dart



Buy me coffee if you love my works β˜•οΈ

buymeacoffe      ko-fi      paypal saweria



About

Flutter auth app with TDD Clean Architecture


Languages

Language:Dart 99.4%Language:Ruby 0.4%Language:Swift 0.1%Language:Kotlin 0.0%Language:Objective-C 0.0%