rx_bloc_cli 3.5.2 copy "rx_bloc_cli: ^3.5.2" to clipboard
rx_bloc_cli: ^3.5.2 copied to clipboard

rx_bloc_cli that enables quick project setup including: flavors, localization [intl], state management [rx_bloc], routing [go_router], design system, analytics [firebase], tests

example/README.md

Table of contents #

  1. Getting Started
  2. Project structure
  3. Architecture
  4. Routing
  5. Adding a new feature
  6. Localization
  7. Firebase Analytics
  8. Http client
  9. Design system
  10. Golden tests
  11. Server
  12. Push notifications
  13. Social Logins
  14. Dev Menu
  15. Patrol integration tests
  16. Realtime communication
  17. Feature OTP
  18. CI/CD
  19. Feature Pin Code
  20. Auth Matrix
  21. Next Steps

Getting started #

Before you start working on your app, make sure you familiarize yourself with the structure of the generated project and the essentials that are included with it.

Note: The app contains features that request data from API endpoints hosted on a local server. For the app to function properly, make sure the local server is up and running. For more info, check out the server topic.

Project structure #

Classes and Libraries Description
Application-Specific Classes
lib/base/app The root widget used in the application entry points (main.dart, main_sit.dart, etc.)
lib/base/app/config Environment-specific configurations and application-specific constants.
lib/base/app/initialization Initialization of Firebase, Push Notifications, Preferred Orientation, etc.
lib/base/data_sources/local Application-wide local data sources, such as shared preferences, secured storage, etc.
lib/base/data_sources/remote Application-wide remote data sources such as APIs. Here is placed all retrofit code.
lib/base/theme/design_system A catalog of colors, gradients, images (light and dark mode), and spacing used in the Flutter theme and widgets.
lib/base/common_mappers Application-wide mappers, responsible for mapping DTOs to business models such as Dio error mapper, platform error mapper, etc.
lib/base/common_ui_components Application-wide reusable widgets (buttons, controls, list tiles, etc.)
lib/base/data_sources/remote/interceptors Custom interceptors that can monitor, rewrite, and retry calls.
lib/base/data_sources/remote/http_clinets Application-wide HTTP clients.
lib/base/common_blocs Generic purpose BLoC classes used in more than one feature.
lib/base/common_services Generic purpose service classes used in more than one feature.
lib/base/repositories Repository classes of the application.
lib/base/models Business model classes of the application.
lib/base/extensions Application-specific utility functions.
lib/base/di Application-wide dependency injection. All classes (BLoC, services, repositories, design system, config and data sources) are accessible from within each feature or project library.
Library-Specific Classes
lib/library_{name} Library-specific UI components.
lib/library_{name}/models Business models representing the success and error state of this library.
lib/library_{name}/blocs Business Logic Component that manages the library-specific state.
lib/library_{name}/services Pure Dart classes responsible for domain-specific business logic of this library.
lib/library_{name}/repositories Pure Dart classes responsible for data provisioning regardless of the data source of this library.
lib/library_{name}/data_sources Classes responsible for storing and retrieving data of this library.
Note: A library may, or may not contain all components described above based on its needs. This structure should be treated as a guideline.
Feature-Specific Classes
lib/feature_{name} Feature-specific UI components.
lib/feature_{name}/models Business models representing the success and error state of this feature.
lib/feature_{name}/blocs Business Logic Component that manages the feature-specific state.
lib/feature_{name}/services Pure Dart classes responsible for domain-specific business logic of this feature.
Note: A feature may, or may not contain models based on its needs.
Application-Specific Libraries
lib/lib_auth The OAuth2 (JWT) based authentication and token management library.
lib/lib_social_logins Authentication with Apple, Google and Facebook library.
lib/lib_permissions The ACL based library that handles all the in-app routes and custom actions as well.
lib/lib_router Generally available router related classes. The main router of the app is lib/lib_router/routers/router.dart.
lib/lib_router/routes Declarations of all nested pages in the application are located here.
lib/lib_dev_menu A library that helps application debugging by easily set and access proxy debugging services Charles and Alice.
lib/lib_pin_code Contains a number of useful widgets that can help you with building pin code with biometrics screens.
Pre-Built Features
lib/feature_otp Contains a number of useful widgets that can help you with building SMS/pin code screens or workflows for your app.
DevOps
fastlane Fastlane directory containing Fastfile configuration.
devops DevOps related files (build/deployment credentials, certificates, provisioning profiles, build artifacts, etc.).

Architecture #

For in-depth review of the following architecture watch this presentation.

Rx Bloc Architecture

Routing #

The routing throughout the app is handled by GoRouter.

You can use the IntelliJ RxBloC Plugin, which automatically does all steps instead of you, or to manualy add your route to the lib/lib_router/routes/routes.dart. Once the route is added one of the following shell scripts bin/build_runner_build.sh(or bin/build_runner_watch.sh) needs to be executed.

The navigation is handled by the business layer lib/lib_router/bloc/router_bloc so that every route can be protected if needed. You can push, pop, goToLocation push_replacement or go as follows

context.read<RouterBlocType>().events.push(MyNewRoute())
context.read<RouterBlocType>().events.pop(Object())
context.read<RouterBlocType>().events.goToLocation('Location')
context.read<RouterBlocType>().events.pushReplacement(MyNewRoute())

or

context.read<RouterBlocType>().events.go(MyNewRoute())

Deep linking #

Your app is already configured to use deep links. Although you may still want to do some adjustments.

iOS The configuration file can be found at ios/Runner/Info.plist Under the CFBundleURLTypes key there are two things you may want to change:

  1. CFBundleURLName unique URL used to distinguish your app from others that use the same scheme. The URL we build contains your project name, organization name and domain name you provided when setting up the project.
  2. CFBundleURLSchemes the scheme name is your organisation name followed by scheme.

Example:

    <key>FlutterDeepLinkingEnabled</key>
    <true/>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>testapp.primeholding.com</string>
        <key>CFBundleURLSchemes</key>
        <array>
        <string>primeholdingscheme</string>
        </array>
        </dict>
    </array>

You can test the deep-links on iOS simulator by executing the following command

Production

xcrun simctl openurl booted primeholdingscheme://testapp.primeholding.com/deepLinks/1

UAT

xcrun simctl openurl booted primeholdinguatscheme://testappuat.primeholding.com/deepLinks/1

SIT

xcrun simctl openurl booted primeholdingsitscheme://testappsit.primeholding.com/deepLinks/1

Development

xcrun simctl openurl booted primeholdingdevscheme://testappdevelopment.primeholding.com/deepLinks/1

Android The configuration file can be found at android/app/src/main/AndroidManifest.xml There is a metadata tag flutter_deeplinking_enabled inside <activity> tag with the .MainActivity name. You may want to change the host name which again contains your project name, organization name and domain name you provided when the project was set up.

See Deep linking documentation at flutter.dev for more information.

Access Control List #

Your app supports ACL out of the box, which means that the access to every page can be controlled remotely by the corresponding API endpoint /api/permissions

Expected API Response structure/data for anonymous users.

Note: The anonymous users have access to the splash route, but not to the profile route.

{
    'SplashRoute': true,
    'ProfileRoute': false,
    ...
}

Expected structure/data for authenticated users.

Note: The authenticated users have access to the splash and profile route.

{
    'SplashRoute': true,
    'ProfileRoute': true,
    ...
}

You can use the IntelliJ RxBloC Plugin, which automatically does all steps instead of you, or to manualy add the permission for your route to the lib/lib_permissions/models/route_permissions.dart.

Adding a new feature #

You can manually create new features as described above, but to speed up the product development you can use the IntelliJ RxBloC Plugin, which not just creates the feature structure but also integrates it to the prefered routing solution (auto_route, go_router or none)

If you decide to create your feature manually without using the plugin here is all necessary steps you should do to register this feature and to be able to use it in the application:

  1. Add your feature path in the RoutesPath class which resides under lib/lib_router/models/routes_path.dart:
   class RoutesPath {
      static const myNewFeature = ‘my-new-feature’;
      ...
   }
  1. Add you feature permission name in the RoutePermissions class which resides under lib/lib_permissions/models/route_permissions.dart:
   class RoutePermissions {
       static const myNewFeature = MyNewFeatureRoute’;
       ...
   }
  1. Next step is to declare the new features as part of the RouteModel enumeration which resides under lib/lib_router/models/route_model.dart:
   enum RouteModel {
       myNewFeature(
           pathName: RoutesPath.myNewFeature
           fullPath: '/my-new-feature',
           permissionName: RoutePermissions.myNewFeature,
       ),
       ...
   }
  1. As a final step the route itself should be created. All routes are situated under lib/lib_router/routes/ folder which contains different route files organised by the application flow. If the new route doesn’t fit the existing application flows it can be added to the routes.dart file which is the default file used by the IntelliJ plugin.
   @TypedGoRoute<MyFeatureRoute>(path: RoutesPath.myNewFeature)
   @immutable
   class MyFeatureRoute extends GoRouteData implements RouteDataModel {
       const MyFeatureRoute();
   
       @override
       Page<Function> buildPage(BuildContext context, GoRouterState state) =>
           MaterialPage(
             key: state.pageKey,
             child: const MyFeaturePage(),
           );
   
       @override
       String get permissionName => RouteModel.myNewFeature.permissionName;
   
       @override
       String get routeLocation => location;
   }

Now the new route can be navigated by calling one of the RouterBloc events (go(...), push(...)). Example:

context.read<RouterBlocType>().go(const MyFeatureRoute())

For more information you can refer to the official GoRouter and GoRouterBuilder documentation.

Localization #

Your app supports localization out of the box.

You define localizations by adding a translation file in the lib/l10n/arb/[language_code].arb directory. The language_code represents the code of the language you want to support (en, zh,de, ...). Inside that file, in JSON format, you define key-value pairs for your strings. Make sure that all your translation files contain the same keys!

If there are new keys added to the main translation file they can be propagated to the others by running the bin/sync_translations.py script. This script depends on the pyyaml library. If your python distribution does not include it you can install it by running pip3 install pyyaml.

Upon rebuild, your translations are auto-generated inside lib/assets.dart. In order to use them, you need to import the l10n.dart file from lib/l10n/l10n.dart and then access the translations from your BuildContext via context.l10n.someTranslationKey or context.l10n.featureName.someTranslationKey.

Remote localization lookup

Localization lookups are also supported. That means that you can request any remote localizations from a dedicated translations endpoint at app start (by default, all remote localizations are fetched during the splash screen). Grabbing any remote localization for existing features will replace the local translations with new ones.

The endpoint retrieving the updated translations should return an object containing key-value pairs (under the translations key) where the key is the language code, while the value is a translation file object containing values to be overwritten.

Example response:

{
   "translations":{
      "en":{
         "_ok":"Okay",
         "login___logIn":"Login via email"
      },
      "bg":{
         "_ok":"ok",
         "login___logIn":"Вход с имейл"
      }
   }
}

One thing to note is that the keys in the translations follow a naming scheme which consists of three parts: the feature name, separator and the translation key. The feature name by default represents the feature under which the translations are categorized followed by two underscores. It is optional and if omitted, the key overrides are placed within the main translations. The separator is a character used to separate the feature name and the translation key. The translation key is the key within the specified feature used in the app.

Example:

Translations feature: feature_notifications
Feature name: notifications__
Separator: _
Translation key: ok
Full translation key (feature name+separator+translation key): notifications___ok

Firebase Analytics #

Firebase analytics track how your app is used. Analytics are available for iOS, Android and Web and support flavors.

Before you start using analytics, you need to add platform specific configurations:

  1. The iOS configuration files can be found at ios/environments/[flavor]]/firebase/GoogleService-Info.plist
  2. For Android the configuration files are located at android/app/src/[flavor]/google-services.json
  3. All Web analytics configurations can be found inside lib/base/app/config/firebase_web_config.js

Every flavor represents a separate Firebase project that will be used for app tracking. For each flavor, based on the targeted platforms you'll have to download the configuration files and place them in the appropriate location mentioned above.

After replacing the Firebase config files with your own, the next step is to configure your app to use the appropriate Firebase options. To do that, you need to provide the Firebase.initializeApp method (within the configureApp function located inside the {project_root}/lib/base/app/initialization/app_setup.dart file) with the desired configuration.

Note: When ran as development flavor, .development is appended to the package name. Likewise, .sit or .uat is appended to the package name when using SIT or UAT flavor. If using separate analytics for different flavors, make sure you specify the full package name with the correct extension (for instance: com.companyname.projectname.development for the devevelopment environment).

Note 2: The app comes with a pre-generated configuration allowing the project to be launched out-of-the-box and should be replaced with your own configuration (see Firebase setup for more details).

Http client #

Your project has integrated HTTP-client, using dio and retrofit. That helps you to easily communicate with remote APIs and use interceptors, global configuration, form fata, request cancellation, file downloading, timeout etc.

To use its benefits you should define a data model in lib/base/models/, using json_annotation and json_serializable. Define your remote data source in folder lib/base/data_sources/remote/ with methods and real Url, using retrofit. In your dependencies class (for example: lib/feature_counter/di/counter_dependencies.dart ) specify which data source you are going to use in every repository.

JWT-based authentication and token management is supported out of the box.

Design system #

A design system is a centralized place where you can define your app`s design. This includes typography, colors, icons, images and other assets. It also defines the light and dark themes of your app. By using a design system we ensure that a design change in one place is reflected across the whole app.

To access the design system from your app, you have to import it from the following locationlib/app/base/theme/design_system.dart'. After that, you can access different parts of the design system by using the BuildContext (for example: context.designSystem.typography.headline1 or context.designSystem.icons.someIcon).

Golden tests #

A golden test lets you generate golden master images of a widget or screen, and compare against them so you know your design is always pixel-perfect and there have been no subtle or breaking changes in UI between builds. To make this easier, we employ the use of the golden_toolkit package.

To get started, you just need to generate a list of LabeledDeviceBuilder and pass it to the runGoldenTests function. That's done by calling generateDeviceBuilder with a label, the widget/screen to be tested, as well as a Scenario. They provide an optional onCreate function which lets us execute arbitrary behavior upon testing. Each DeviceBuilder will have two generated golden master files, one for each theme.

Due to the way fonts are loaded in tests, any custom fonts you intend to golden test should be included in pubspec.yaml

In order for the goldens to be generated, we have provided VS Code and IDEA run configurations, as well as an executable bin/generate_goldens.sh. The golden masters will be located in goldens/light_theme and goldens/dark_theme. The failures folder is used in case of any mismatched tests.

Server #

Your app comes with a small preconfigured local server (written in Dart) that you can use for testing purposes or even expand it. It is built using shelf, shelf_router and shelf_static. The server comes with several out-of-the-box APIs that work with the generated app.

In order to run the server locally, make sure to run bin/start_server.sh. The server should be running on http://0.0.0.0:8080, if not configured otherwise.

Some of the important paths are:

Path Contains
bin/server/ The root directory of the server
bin/server/start_server.dart The main entry point of the server app
bin/server/config.dart All server-related configurations and secrets are located here
bin/server/controllers/ All controllers are located here
bin/server/models/ Data models are placed here
bin/server/repositories/ Repositories that are used by the controllers reside here
bin/server/di/ All server related dependencies are registered here

Note: When creating a new controller, make sure you also register it inside the ServerDependencies.registerControllers method in server_dependencies.dart. Respectively, register any dependencies in the ServerDependencies.registerDependencies in the same file.

Push notifications #

Firebase Cloud Messaging (FCM) allows your integrating push notifications in your very own app. You can receive notifications while the app is in the foreground, background or even terminated. It even allows for event callbacks customizations, such when the app is opened via a notification from a specific state. All customizable callbacks can be found inside lib/base/app/initialization/firebase_messaging_callbacks.dart.

In order to make the notifications work on your target platform, make sure you first add the config file in the proper location (as described in the Analytic section). For Web you also need to specify the vapid key inside lib/base/app/config/app_constants.dart and manually add the firebase web configuration to web/firebase-messaging-sw.js(for more info refer to this link).

Note: On Android, FCM doesn't display heads-up notifications (notifications when the app is in foreground) by default. To display them while in app, we use a custom package called flutter_local_notifications . This package also provides a way of customizing your notification icon which you can find at the android/src/main/res/drawable directory (supported types are .png and .xml).

Note: Since the app comes with a local server which can send notifications on demand, before using this feature, you need to create a server key for cloud messaging from the Firebase Console. Then you have to assign it to the firebasePushServerKey constant located inside the bin/server/config.dart file.

Social Logins Library #

Allows you to authenticate users in your app with Apple, Google and Facebook.

Apple Authentication

It uses the sign_in_with_apple package.
In order to make it work, fulfill the requirements described in its documentation.

Supports iOS.

Google Authentication

Google authentication uses google_sign_in package.

Follow the package documentation for registering your application and downloading Google Services file.(GoogleService-Info.plist/google-services.json)

Android: For android integration you will need to copy google-services.json file to android/app/src/{name_of_the_environment}/

iOS: For iOS integration you will need to copy GoogleService-Info.plist file to ios/environments/{name_of_the_environment}/firebase/
and copy reversed_client_id from GoogleService-Info.plist to ios/Flutter/{name_of_the_environment}.xcconfig file

For any other configurations refer to the google_sign_in package.

Facebook Authentication

Facebook authentication uses flutter_facebook_auth package.

Step 1:
In order to make it work you must register your app in facebook developer console.

Step 2:
There you will find your app_id, client_token and app_name.

Step 3:

  • 3.1 Android: Edit android/app/build.gradle, paste parameters from step 2 in
productFlavors{
 name_of_the_enviroment{
 dimension "default"
           applicationIdSuffix ""
           versionNameSuffix ""
           resValue "string", "facebook_app_id", "insert_facebook_app_id_here"
           resValue "string", "facebook_client_token", "insert_client_token_here"
   }
 }
  • 3.2 iOS: Edit ios/Flutter/(flavor-name).xcconfig and paste parameters from step 2.

Note: Some requirements to be able to run application with this version of facebook auth is

  • flutter_secure_storage package must be at least 8.0.0 version
  • for iOS in Podfile platform must be at least 12
  • for Android minSdkVersion must be at least 21.

All additional info about package and better explanation how to implement you can find in documentation flutter_facebook_auth_documentation.

Dev Menu #

Dev menu brick is a useful feature when it comes to debugging your app and/or easily accessing some common development specific information and settings. You can define secret inputs which after being triggered a defined number of times will execute a callback. From that callback you can define any app-specific behaviors like navigating to a screen, displaying a dev modal sheet with additional data or your own behaviors.

Widgets #

Within the dev_menu brick you can find the AppDevMenuGestureDetector widget and the showDevMenuBottomSheet function.

AppDevMenuGestureDetector

The AppDevMenuGestureDetector widget is a widget that is listening for user interactions (quick taps or long taps) and as a result executes a callback (onDevMenuPresented) once a certain amount of interactions has been made.

As a good use case, you can wrap your page widget with this widget so you are able to access the functionality while on the same page.

  AppDevMenuGestureDetector.withDependencies(
    context,
    navKey!,
    child: materialApp,
      onDevMenuPresented: () {
        showAppDevMenuBottomSheet(
          context.read<AppRouter>().rootNavigatorKey.currentContext!,
        );
      },
  );

By default after you trigger AppDevMenuGestureDetector you only need to add your proxy ip and restart app so you are all set to use Charles. Alice is working right out of the box.

Note: To disable dev menu you only need to edit run configuration (Development or SIT) and remove --dart-define="ENABLE_DEV_MENU=true" from additional run arguments.

Patrol Integration Tests #

Patrol allows developers to use native automation and custom finders to write integration tests faster. The application comes with partial integration of the patrol package Android and iOS. Before running the tests, make sure you've integrated patrol with native side of your project.

To run patrol integration tests install patrol_cli package. This package enables applications to use native automation features

Running the Tests

To run a test type a command patrol test --flavor flavor_name, or use one of the preconfigured shell scripts provided within Android Studio

Realtime Communication #

Provides base datasource, repository, service and utility classes for establishing a SSE connection. Register the classes into the DI system and configure the SSE endpoint by passing it as a parameter to SseRemoteDataSource. After this is done the event stream exposed by SseService can be used by any BLoC.

Feature OTP #

The feature_otp brick contains a number of useful widgets that can help you with building sms/pin code screens or workflows for your app.
The brick contains widgets for entering pin codes, pasting them, resend logic and more. For more info please visit widget_toolkit_otp

Auth Matrix #

The lib_auth_matrix brick contains classes, repositories, datasources and widgets that can help you with building a matrix authentication workflow for your app. It contains 4 new endpoints for initializing, verifying and canceling the matrix authentication process.

CI/CD #

The project comes preconfigured with a CI/CD solution which allows automated building and deploying of android and iOS apps. The solution is using Fastlane under the hood (check {app_directory}/fastlane/Fastfile for more info).

Before running the actual CD pipeline, you may need first to configure your project specific files and credentials. For more information, please check out the detailed guide on how to configure your Fastfile. Additionally, you can check a concrete implementation in this example.

Feature Pin Code #

The application provides a pin code functionality. Initially there is a create pin button, which navigates you to the CreatePinPage. There you can enter and then confirm the pin that you chose. Then a change pin button is displayed, from you are navigate to the UpdatePinPage, where you can enter the current pin and then enter and confirm the new pin. After a pin is created and saved, on the UpdatePinPage there is a biometrics button, from which you can enable usage of biometrics. On app restart, you are navigated to the VerifyPinPage where you can either authenticate with a pin or with biometrics. There is a session timeout listener, which navigates you to the VerifyPinPage after a configurable amount of inactivity time and after a configurable amount of time, when the app has been in background mode. For more info please visit widget_toolkit_pin

Next Steps #

  • Define the branching strategy that the project is going to be using.
  • Define application-wide loading state representation. It could be a progress bar, spinner, skeleton animation or a custom widget.
26
likes
140
pub points
0%
popularity

Publisher

verified publisherprimeholding.com

rx_bloc_cli that enables quick project setup including: flavors, localization [intl], state management [rx_bloc], routing [go_router], design system, analytics [firebase], tests

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

args, collection, flutter_lints, mason, path, xml

More

Packages that depend on rx_bloc_cli