Press ESC to close

Flutter App : A package to speed up the creation of micro frontend(or independent features) structure in Flutter applications

A package to speed up the creation of micro frontend(or independent features) structure in Flutter applications (beta version)

Screen Shot 2022-02-03 at 00 32 35

Navigation between pages

Use [NavigatorInstance] to navigate between pages

NavigatorInstance.pop();
NavigatorInstance.pushNamed();
NavigatorInstance.pushNamedNative();
NavigatorInstance.pushReplacementNamed();
NavigatorInstance ...

Open native (Android/iOS) pages, in this way

It needs native implementation, you can see an example inside android folder

// If not implemented, always return null final isValidEmail = await NavigatorInstance.pushNamedNative<bool>( 'emailValidator', arguments: 'validateEmail:lorem@ipsum.com' ); print('Email is valid: $isValidEmail');
// Listen to all flutter navigation events NavigatorInstance.eventController.flutterLoggerStream.listen((event) { logger.d('[flutter: navigation_log] -> $event'); }); // Listen to all native (Android/iOS) navigation events (if implemented) NavigatorInstance.eventController.nativeLoggerStream.listen((event) {}); // Listen to all native (Android/iOS) navigation requests (if implemented) NavigatorInstance.eventController.nativeCommandStream.listen((event) {});

Define micro app configurations and contracts

Configure the preferences (optional)

  MicroAppPreferences.update(
    MicroAppConfig(
      nativeEventsEnabled: true, // If you want to dispatch and listen to events between native(Android/iOS) [default = false]
      pathSeparator: MicroAppPathSeparator.slash // It joins the routes segments using slash "/" automatically
    )
  );

Register all routes

This is just a suggestion of routing strategy (Optional)
It’s important that all routes are availble out of the projects, avoiding dependencies between micro apps.
Create all routes inside a new package, and import it in any project as a dependency. This will make possible to open routes from anywhere in a easy and transparent way.

Create the routing package: flutter create template=package micro_routes

// Export all routes class Application1Routes extends MicroAppRoutes { @override MicroAppBaseRoute get baseRoute => MicroAppBaseRoute('application1'); String get page1 => baseRoute.path('page1'); String get page2 => baseRoute.path('page2', ['segment1', 'segment2']); }

For example, you can open a page that is inside other MicroApp, in this way:

NavigatorInstance.pushNamed(OtherMicroAppRoutes().specificPage);
NavigatorInstance.pushNamed(Application1Routes().page1);

Expose all pages throuth a contract MicroApp (Inside external projects or features folder)

import 'package:micro_routes/exports.dart'; class Application1MicroApp extends MicroApp with Application1Routes { @override List<MicroAppPage> get pages => [ MicroAppPage(name: baseRoute.name, builder: (context, arguments) => const Initial()), MicroAppPage(name: page1, builder: (context, arguments) => const Page1()), MicroAppPage(name: page2, builder: (context, arguments) { final page2Params.fromMap(arguments); return Page2(params: page2Params); }), ]; }

Initialize the host, registering all micro apps

  • MicroHost is also a MicroApp, so you can register pages here too.
  • MyApp needs to extends MicroHostStatelessWidget or MicroHostStatefulWidget
  • The MicroHost is the root widget, and it has all MicroApps, and the MicroApps has all Micro Pages.
void main() { runApp(MyApp()); } class MyApp extends MicroHostStatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', navigatorKey: NavigatorInstance.navigatorKey, // Required onGenerateRoute: onGenerateRoute, // [onGenerateRoute] this is created automatically, so just use it, or override it, if needed. initialRoute: baseRoute.name, navigatorObservers: [ NavigatorInstance // Add NavigatorInstance here, if you want to get didPop, didReplace and didPush events ], ); } // Base route of host application @override MicroAppBaseRoute get baseRoute => MicroAppBaseRoute('/'); // Register all root [MicroAppPage]s here @override List<MicroAppPage> get pages => [ MicroAppPage(name: baseRoute.name, builder: (_, __) => const HostHomePage()) ]; // Register all [MicroApp]s here @override List<MicroApp> get microApps => [MicroApplication1(), MicroApplication2()]; }

Handling micro apps events

Dispatching events

// dispatching in channel "user_auth", only
MicroAppEventController()
    .emit(const MicroAppEvent(
        name: 'my_event',
        payload: {'data': 'lorem ipsum'},
        channels: ['user_auth'])
    );

Listen to events (MicroApp)s

// It listen to all events @override MicroAppEventHandler? get microAppEventHandler => MicroAppEventHandler((event) => logger.d([ event.name, event.payload])); // It listen to events with id equals 123, only! @override MicroAppEventHandler? get microAppEventHandler => MicroAppEventHandler((event) { logger.d([ event.name, event.payload]); }, id: '123'); // It listen to events with channels "chatbot" and "user_auth" @override MicroAppEventHandler? get microAppEventHandler => MicroAppEventHandler((event) { // User auth feature, asked to show a popup :) myController.showDialog(event.payload); }, channels: ['chatbot', 'user_auth']);

Managing events

MicroAppEventController().unregisterHandler(id: '123');
MicroAppEventController().unregisterHandler(channels: ['user_auth']);
MicroAppEventController().pauseAllHandlers();
MicroAppEventController().resumeAllHandlers();
MicroAppEventController().unregisterAllHandlers();

Initiating an event subscription anywhere in the application (inside a StatefulWidget, for example)

Using subscription

final subscription = MicroAppEventController().stream.listen((MicroAppEvent event) {
    logger.d(event);
  });

// later, in dispose method of the widget
@override
void dispose() {
    subscription.cancel();
    super.dispose();
}

Using handler

MicroAppEventController().registerHandler(MicroAppEventHandler(id: '1234'));

// later, in dispose method of the widget
@override
void dispose() {
    MicroAppEventController().unregisterSubscription(id: '1234');
    super.dispose();
}

Overriding onGenerateRoute method

If it fails to get a page route, ask for native(Android/iOS) to open the page

  @override
  Route? onGenerateRoute(RouteSettings settings, {bool? routeNativeOnError}) {
    //! If you wish native app receive requests to open routes, IN CASE there
    //! is no route registered in Flutter, please set [routeNativeOnError: true]
    return super.onGenerateRoute(settings, routeNativeOnError: true);
  }

If it fails to get a page route, show a default error page

@override Route? onGenerateRoute(RouteSettings settings, {bool? routeNativeOnError}) { final pageRoute = super.onGenerateRoute(settings, routeNativeOnError: false); if (pageRoute == null) { // If pageRoute is null, this route wasn't registered(unavailable) return MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(), body: const Center( child: Text('Page Not Found'), ), )); } return pageRoute; }

The following table shows how Dart values are received on the platform side and vice versa

Dart Kotlin Swift Java
null null nil null
bool Boolean NSNumber(value: Bool) java.lang.Boolean
int Int NSNumber(value: Int32) java.lang.Integer
int, if 32 bits not enough Long NSNumber(value: Int) java.lang.Long
double Double NSNumber(value: Double) java.lang.Double
String String String java.lang.String
Uint8List ByteArray FlutterStandardTypedData(bytes: Data) byte[]
Int32List IntArray FlutterStandardTypedData(int32: Data) int[]
Int64List LongArray FlutterStandardTypedData(int64: Data) long[]
Float32List FloatArray FlutterStandardTypedData(float32: Data) float[]
Float64List DoubleArray FlutterStandardTypedData(float64: Data) double[]
List List Array java.util.ArrayList
Map HashMap Dictionary java.util.HashMap

GitHub

View Github

Footer Example