{"id":21976802,"url":"https://github.com/stuartapp/flutter-modular-architecture","last_synced_at":"2025-07-11T13:07:18.868Z","repository":{"id":77493287,"uuid":"488592401","full_name":"StuartApp/flutter-modular-architecture","owner":"StuartApp","description":"Sample project of a modular architecture for Flutter apps","archived":false,"fork":false,"pushed_at":"2022-06-03T16:02:57.000Z","size":1773,"stargazers_count":39,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-28T16:57:13.490Z","etag":null,"topics":["architecture","clean-architecture","flutter","modular"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StuartApp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-04T13:07:42.000Z","updated_at":"2025-03-16T18:44:08.000Z","dependencies_parsed_at":"2023-03-12T00:52:37.260Z","dependency_job_id":null,"html_url":"https://github.com/StuartApp/flutter-modular-architecture","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/StuartApp/flutter-modular-architecture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StuartApp%2Fflutter-modular-architecture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StuartApp%2Fflutter-modular-architecture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StuartApp%2Fflutter-modular-architecture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StuartApp%2Fflutter-modular-architecture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StuartApp","download_url":"https://codeload.github.com/StuartApp/flutter-modular-architecture/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StuartApp%2Fflutter-modular-architecture/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264816027,"owners_count":23668101,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","clean-architecture","flutter","modular"],"created_at":"2024-11-29T16:12:20.625Z","updated_at":"2025-07-11T13:07:18.861Z","avatar_url":"https://github.com/StuartApp.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bici sample app\n\nWith this app you can find available bikes and e-bikes near to you. The app allows you\nto locate bikes in a database that containes real time info of the public bicycle\nservices from more than 400 cities.\n\nThis project has been created as a demonstration of the modular architecture used\nin Flutter projects at [Stuart](https://stuart.com/blog/tech/).\n\nThis app relies on the public [API](http://api.citybik.es/v2/) provided by\n[CityBikes](https://citybik.es/).\n\n| ![Screenshot 1](documentation/screenshot-1.png) | ![Screenshot 2](documentation/screenshot-2.png) | ![Screenshot 3](documentation/screenshot-3.png) |\n|-------------------------------------------------|-------------------------------------------------|-------------------------------------------------|\n\nWhere are the maps? For sake of simplicity, this project doesn't contain maps. This is to avoid\nthe developers having to set a Google Maps API key in the project.\n\n# How to run it\n\n1. Install all dependencies:\n   ```shell\n   make pub_get_all\n   ```\n2. With a device connected run the app:\n   ```shell\n   flutter run\n   ```\nPlease check [Makefile](Makefile) to discover all the available `make` commands.\n\n# Architecture overview\n\nThis architecture will be mainly composed by **Use Cases** and **Features** packages that will\nwork as independent modules.\n\n![Architecture 1](documentation/architecture-1.png)\n\nThis architecture has deeply inspired the talk\n[Android at Scale @Square](https://www.droidcon.com/2019/11/15/android-at-scale-square/).\n\n## What is a Use Case?\n\nA Use Case orchestrates the flow of data to and from the Entities by implementing application\nspecific business rules. A Use Case belongs to the Domain layer and it is used by classes from\nPresentation layer.\n\nFor instance if in the Presentation layer there are some classes grouped in a Feature Package\nimplementing all the UI necessary for a user to log in, these classes for example will depend on\nsome Authentication Use Cases Package that could expose a LogInUseCase that will login the user\nafter receive a username and a password as parameters.\n\n## What is a Use Cases package?\n\nA Use Cases Package is the one that exposes Use Cases that can be used by Features Packages or\nother Use Cases Packages. These Use Cases grouped in the same package are related to each other.\nSome examples:\n\n**Authentication Use Cases Package:** Log in user, register new user, remember password, etc.\n\n**Articles Use Cases Package:** Get all articles, create new article, archive article, etc.\n\nA Use Cases Package only exposes Use Cases as public interfaces and also the related Domain Models.\nThe same package contains the implementations for all the declared Use Cases. This means that it\ncontains all the logic that belongs to the domain layer.\n\nIn the other hand, the package does not implement any logic that belongs to the data layer. This\nmeans that any Repository or other low level classes used by the Use Cases, needs to be\nimplemented somewhere else.\n\nThe place to implement all the data logic of the Use Cases is in the inner Adapter packages. These\ninner packages will implement interfaces exported in a file name `private_ports.dart`. This file\nwill export interfaces like Repositories which the Use Cases will depend on. Each Use Cases Package\nwill have at least one of the following inner packages:\n\n- **Real adapters:** Contains the real implementations for the interfaces defined in the\n  private_ports.dart file. These are the implementations that are used at production.\n- **Fake adapters:** Contains fake implementations for the interfaces defined in the\n  private_ports.dart file. This package can be used to wire up the dependencies for UI automated\n  testing or to create development apps (Apps to develop and test certain features in a isolated\n  context).\n\n:information_source: Notice here the use of words Ports and Adapters where they have the same\nmeaning as in the Port and Adapters architecture (Or Hexagonal architecture).\n\n![Architecture 2](documentation/architecture-2.png)\n\n## What is a Features Package?\n\nFeatures Packages have the UI and presentation code to implement a specific functionality of the\napp. Normally are composed of BLoCs, Screens (Or pages), Widgets and data classes that will be\nused in the communication between BLoCs and Screens/Widgets. This means that a Features Package\nwill contain only logic from the Presentation Layer and they will depend on Use Cases packages\nto get the implementation of the business logic needed for that feature.\n\nHere are some examples of possible Features Packages:\n\n- login: Groups all the screens related to login/register.\n- home: Groups all the code for the main screen.\n- articles: Groups all the code that let the user read articles.\n- publish_article: Groups all the code that let the user publish articles.\n- navigation_drawer: The side drawer with the menu to navigate.\n\n## Dependency management\n\nThis project uses construction-based dependency injection. This means that all the classes will\nreceive their dependencies as parameters of their constructor.\n\n```dart\nclass GetUserByIdUseCase {\n  final UserRepository userRepository;\n\n  GetUserByIdUseCase(this.userRepository); // Dependencies injected by constructor\n\n  @override\n  Stream\u003cUser\u003e call(String userId) {\n    return userRepository.getUserById(userId);\n  }\n}\n```\n\nTo satisfy these dependencies each package with concrete implementations will expose a file called\n`dependency_configurator.dart` with an implementation of an interface called\n[DependencyConfigurator](packages/shared/all/core/lib/src/dependencies/dependency_configurator.dart).\nThis interface defines a method that must be implemented and it receives a `GetIt` instance that can\nbe used to register all the dependencies that are provided by the package.\n[GetIt](https://pub.dev/packages/get_it) is simple but powerful service locator, but if you want\nyou can use any other tool with this architecture. `DependencyConfigurator` also receives a\n`DependencyConfiguratorContext` instance with extra configuration info.\n\n```dart\n// dependency_configuration_context.dart\nclass DependencyConfigurationContext {\n  final String apiBaseUrl;\n\n  ConfigurationContext({\n    required this.apiBaseUrl,\n  });\n}\n\n// dependency_configurator.dart\nabstract class DependencyConfigurator {\n  void configureDependencies(\n    DependencyConfiguratorContext context,\n    GetIt getIt,\n  );\n}\n```\n\nThen these `dependency_configurator.dart` can be used from a main module to register all the\ndifferent dependencies. For instance from main method we can call the following\n`configureDependencies()` method:\n\n```dart\n// dependencies.dart\nfinal configurators = [\n  ArticlesDependencyConfigurator(),\n  ArticlesFakeAdaptersDependencyConfigurator(),\n];\n\nvoid configureDependencies(DependencyConfigurationContext context) {\n  for (var configurator in configurators) {\n    configurator.configureDependencies(context, GetIt.instance);\n  }\n}\n```\n\nWhen a configured dependency needs to be injected, it can be done by using the method `inject()`.\nThese method will be used normally from code located in the Presentation layer in this way:\n\n```dart\nclass ListUsersScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (context) =\u003e ListUsersBloc(\n        inject\u003cGetAllUsersInteractor\u003e(), // Use of the inject() method\n        inject\u003cGetUserByIdInteractor\u003e(),\n      ),\n      child: Container(),\n    );\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstuartapp%2Fflutter-modular-architecture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstuartapp%2Fflutter-modular-architecture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstuartapp%2Fflutter-modular-architecture/lists"}