Help Instance Help

Getting Started - New App from Scratch

Create App Skeleton

To create a new app with m8ty, first create a flutter app:

flutter create <appname>

This will create a new skeleton for the application with all the necessary files and directories in a new directory with the same name as the application name.

Add Dependencies

Now it is time to add the m8ty libraries as dependencies.

The file pubspec.yaml requires the following minimal dependencies:

# We start here from the dependencies part dependencies: flutter: sdk: flutter json_annotation: ^4.4.0 m8ty_app: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_app.git m8ty_theme: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_theme.git m8ty_appsettings: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_appsettings.git m8ty_banking_home: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_banking_home.git m8ty_multibanking_intro: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_multibanking_intro.git m8ty_login: git: url: git@gitlab.m8ty.eu:app-framework/m8ty_login.git ## -> more dependencies # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 google_ml_kit: ^0.6.0 dev_dependencies: build_runner: built_value_generator: ^8.1.2 change_app_package_name: ^0.1.3 freezed: injectable_generator: json_serializable: url_launcher: flutter_native_splash: ^1.2.4 flutter_test: sdk: flutter # -> more stuff

If this is done, please update the dependencies:

flutter pub get

Add the m8ty App to the main.dart File

The original /lib/main.dart file created with Flutter is not required. With the m8ty Framework it is possible to add the main class functionality with the following code.

import 'dart:async'; import 'package:get_it/get_it.dart'; import 'package:m8ty_app/m8ty_app.dart'; import 'package:m8ty_appsettings/m8ty_appsettings.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_business_components/m8ty_business_components.dart'; import 'package:m8ty_login/m8ty_login.dart'; import 'package:m8ty_multibanking_intro/m8ty_multibanking_intro.dart'; import 'package:m8ty_security/m8ty_security.dart'; import 'config/config_dependencies.dart'; import 'config/config_initializers.dart'; import 'models/m8ty_config.dart'; import 'models/serializers.dart'; import 'provider.dart'; Future<void> main() async { return mainAppInit( serializers: standardSerializers, initializers: configInitializers, overrides: getProviderOverrides, configurators: [configureDependencies], changeListeners: [handleLogout], customerRouteHandlers: [ LoginHandler(introPage: PagesIntro.intro), ], moduleConfigs: (final WidgetRef ref) { return [ BankingIntroModuleConfig( IntroConfig( signUpEmail: PagesLogin.signUpEmail, login: PagesLogin.login, ), ), LoginModuleConfig(GetIt.instance<M8tyConfigModel>()), SecurityModuleConfig(SecurityConfig( changePassword: PagesLogin.changePassword, )), BusinessComponentsModuleConfig(), BasicComponentsModuleConfig(), AppSettingsModuleConfig(AppSettingsConfig(logout: () => ref.read(identityServiceProvider).logout())), ]; }); }

Create Config Models and Serializers

The config models and serializers are located under /lib/models/ This model can contain any app specific configurations. In this example we have some basic settings to show how such configurations should be developed.

The first model we create is a simple m8ty_config.dart:

import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_login/config.dart'; part 'm8ty_config.g.dart'; abstract class M8tyConfigModel implements Built<M8tyConfigModel, M8tyConfigModelBuilder>, IMockConfig, IBaseUrlProvider, ILoginConfig { static Serializer<M8tyConfigModel> get serializer => _$m8tyConfigModelSerializer; factory M8tyConfigModel([final void Function(M8tyConfigModelBuilder) updates]) = _$M8tyConfigModel; M8tyConfigModel._(); @override String get basePath; String? get activeProfile; @override String get defaultCurrency; @override int get timeoutVerificationEmailInSeconds; @override int get pollingIntervalInSeconds; bool get isMultiBanking; int get defaultRecentTransactionPageSize; } extension ProfileExtension on M8tyConfigModel { bool isMock() { return activeProfile == "mock"; } }

This file is not more than a simple pojo to read the JSON configuration of the app.

The second file is the serializers.dart file, which contains the definition of the serializers:

library serializers; import 'package:built_value/serializer.dart'; import 'package:built_value/standard_json_plugin.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_business_components/m8ty_business_components.dart'; import 'm8ty_config.dart'; part 'serializers.g.dart'; @SerializersFor([ M8tyConfigModel, ]) final Serializers serializers = _$serializers; Serializers standardSerializers = (serializers.toBuilder() ..addPlugin(StandardJsonPlugin()) ..merge(standardBusinessSerializers) ..addBuilderFactory(const FullType(ResponseData, [FullType(String)]), () => ResponseDataBuilder<String>())) .build();

Build the *.g.dart Files

Now it's time to create the missing parts for our model. For this we use the build_runner with the built_value_generator, and the json_serializable framework. The complete documentation can be found at flutter.dev.

One time generation:

flutter pub run build_runner build

If you wish to start a watch demon, which is looking for file changes, you can also use.

flutter pub run build_runner watch

After this step, the lib/model directory should contain 2 new files with a .g.dart file ending.

Add JSON Configuration

After we've created the models for our configuration, it is time to create the configuration itself. The JSON file is located under /assets/config/configuration.json and should contain now the following content:

{ "basePath": "https://dev.m8ty.eu/api", "timeoutVerificationEmailInSeconds": 660, "pollingIntervalInSeconds": 3, "isMultiBanking": true, "activeProfile": "prod", "useTransactionGenerator": false, "defaultCountry": "DE", "defaultCurrency": "EUR", "defaultRecentTransactionPageSize": 5 }

Create Configuration

The configuration of the app is located under /lib/config/.

Service Configuration

The service configuration creates singleton instances of all necessary services in our app.

Please create the file /lib/config/config_services.dart with the following content:

import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_business_components/dashboard/dashboard_preference_settings_service.dart'; import 'package:m8ty_business_components/m8ty_business_components.dart'; import 'package:m8ty_security/m8ty_security.dart'; import 'package:m8ty_theme/m8ty_theme.dart'; import '/config/config_multibanking.dart'; import '/config/config_navigation_service.dart'; import '/models/m8ty_config.dart'; Future<M8tyConfigModel> _loadConfig() async { final String data = await rootBundle.loadString('assets/config/configuration.json'); final jsonResult = json.decode(data); final M8tyConfigModel? m8tyConfig = GlobalSerializer.deserialize<M8tyConfigModel>(jsonResult); // set multibanking flag MultiBankingAppConfig(m8tyConfig!.isMultiBanking); return m8tyConfig; } Future<void> initServices() async { await GetIt.instance<SecuritySettingsService>().init(); await GetIt.instance<ThemeSettingsService>().init(); } Future<void> configureServices() async { GetIt.instance.registerSingleton<IUiErrorHandler>(DefaultErrorHandler()); final config = await _loadConfig(); logger.debug("basePath: ${config.basePath}"); GetIt.instance.registerSingleton<M8tyConfigModel>(config); GetIt.instance.registerSingleton<IBaseUrlProvider>(config); GetIt.instance.registerSingleton<IMockConfig>(config); final snackService = SnackService(); final dialogService = DialogService(); final securitySettings = SecuritySettingsService(UserSecuritySettingsLocaleStore()); GetIt.instance.registerSingleton<IContactsSecurityContactsService>( ContactsSecurityService(securitySettings), ); GetIt.instance.registerSingleton<INavigationConfigService>(NavigationConfigService()); GetIt.instance.registerSingleton<IDialogService>(dialogService); GetIt.instance.registerSingleton<IThemeProvider>(ThemeProvider()); GetIt.instance.registerSingleton<IDialogUIConnector>(dialogService); GetIt.instance.registerSingleton<ISnackService>(snackService); GetIt.instance.registerSingleton<ISnackUiConnector>(snackService); GetIt.instance.registerSingleton<IToastService>(ToastService()); GetIt.instance.registerSingleton<IRefreshServiceRegistry>(RefreshServiceRegistry()); GetIt.instance.registerSingleton<IPlaceSearchService>(PlaceSearchService()); GetIt.instance.registerSingleton<IGeoLocationService>(GeoLocationService()); GetIt.instance.registerSingleton<IStatisticsUserLocaleStorage>( StatisticsUserLocaleStorage(), ); GetIt.instance.registerSingleton<IDashboardPreferenceSettingsService>( DashboardPreferenceSettingsService(), ); GetIt.instance.registerSingleton<IDelayedExecutionService>(DelayedExecutionService()); GetIt.instance.registerSingleton<IBusyService>(BusyService()); GetIt.instance.registerSingleton<IFeatureToggleService>(DefaultFeatureToggleService()); final RecentOperationNotifier recentOperationNotifier = RecentOperationNotifier(); GetIt.instance.registerSingleton<RecentOperationNotifier>(recentOperationNotifier); GetIt.instance.registerSingleton<StatisticsManager>( StatisticsManager(StaticsLocale(), null, recentOperationNotifier), ); GetIt.instance.registerSingleton<SecuritySettingsService>(securitySettings); GetIt.instance.registerSingleton<ISecuritySettingsService>(securitySettings); GetIt.instance.registerSingleton<ThemeSettingsService>( ThemeSettingsService(ThemeUserLocaleStorage()), ); }

Prepare Multi-Banking Configuration

To prepare a configuration which is able to activate/deactivate multi-banking we create the file /lib/config/config_multibanking.dart:

class MultiBankingAppConfig { static bool _isMultiBankingEnabled = false; MultiBankingAppConfig(final bool isMultiBanking) { _isMultiBankingEnabled = isMultiBanking; } static bool get isMultiBankingEnabled => _isMultiBankingEnabled; }

Prepare Configuration Initializers

The configuration initializers are registering the initializers of the modules you want to use. To do that, create the file /lib/config/config_initializers.dart with the following content:

import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_business_components/m8ty_business_components.dart'; import 'package:m8ty_login/m8ty_login.dart'; import 'package:m8ty_personal_data/m8ty_personal_data.dart'; List<Future<void> Function()> configInitializers = [ BasicComponentsModuleConfig.init, BusinessComponentsModuleConfig.init, LoginModuleConfig.init, PersonalDataModuleConfig.init ];

Prepare Navigation of the App

As a next step we define the internal navigation of the pages. We do this here as a manual step, to be able to add more later.

Please create the file /lib/config/config_navigation_service.dart:

import 'package:m8ty_appsettings/navigation.dart'; import 'package:m8ty_banking_home/m8ty_banking_home.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_business_components/navigation.dart'; import 'package:m8ty_login/navigation.dart'; import 'package:m8ty_personal_data/navigation.dart'; import 'package:m8ty_security/m8ty_security.dart'; class NavigationConfigService extends NavigationConfigServiceBase { NavigationConfigService() : super([ ...navigationBusinessWithOptions(hasMoreContainer: false), ...navigationLogin, ...navigationAppSettingsAccounts, ...navigationPersonalData, ...navigationSecurity.overrideAllCategories(NavigationCategoriesBusiness.userProfile), ...navigationBankingHome, ]); }

Prepare Module Dependency Configuration

The last required configuration file is the /lib/config/config_dependencies.dart file. It finally creates the previously prepared configuration and initialization services.

To have a bit more security, it adds also the method handleLogout() to ensure, that no amounts are shown in an anonymous status. For that, it watches on the identity provider and if it changes its state to anonymous, all amounts will be blurred. You can add more things here, but so far this is a good example to show this kind of dependency between an event and the resulting action.

import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_login/m8ty_login.dart'; import '/config/config_services.dart'; Future<void> configureDependencies() async { await configureServices(); await initServices(); } void handleLogout(final WidgetRef ref) { final idProvider = ref.watch(identityProvider); if (idProvider.isAnonymous()) { logger.debug("reset blurred because of logout"); final AmountBlurredNotifier amountBlurredNotifier = ref.watch(amountBlurredProvider.notifier); if (amountBlurredNotifier.isBlurred) { M8tyDelayedExecutionServiceMixin().delayedExecutionService.execute(() => amountBlurredNotifier.switchBlurred()); } } }

Create the App Provider

As one of the last steps we create the app provider under /lib/provider.dart. This provider is used to set up/override the standard providers depending on the requirements.

import 'package:get_it/get_it.dart'; import 'package:m8ty_appsettings/m8ty_appsettings.dart'; import 'package:m8ty_basic_components/m8ty_basic_components.dart'; import 'package:m8ty_identity/m8ty_identity.dart'; import 'package:m8ty_login/m8ty_login.dart'; import 'models/m8ty_config.dart'; List<Override> getProviderOverrides() { final m8tyConfig = GetIt.instance<M8tyConfigModel>(); final standardOverrides = [ baseUrlProvider.overrideWithValue(m8tyConfig), authTokenProvider.overrideWithProvider(loginAuthTokenProvider), identityProviderProvider.overrideWithProvider(identityOverrideProvider), securityBasicSettingsServiceProvider.overrideWithValue(GetIt.instance<ISecuritySettingsService>()), ]; // Can be used to mock a backend while development or for static showcases if (m8tyConfig.isMock()) { standardOverrides.addAll([ ...appSettingsMockOverrides, ...loginMockOverrides, ]); } return standardOverrides; }

Conclusion / Next Steps

With these simple steps, you can create a new app from scratch. Theoretically, the app can now be started with:

flutter run --no-sound-null-safety --dart-define=environment=mock

Now tha App has not configured any pages or widgets which it can show.

Last modified: 30 September 2024