Integrate Flutter module into Senyumku & Tunaiku App (Android)

Stevanus Wijaya
Tunaiku Tech
Published in
6 min readMar 31, 2022

Sometimes there is a needs to re-use our module in multiple different applications (native). Is this possible using the Flutter module? The answer is Yes!

It is really possible to integrate Flutter module into multiple applications using Add to app integration. Flutter app can be imported as a library or module in your base application project (native).

In Senyumku, some of the main features such as transactions are using Flutter module that is integrated into native applications both for Senyumku application itself and also Tunaiku. Also, every feature in the Flutter module app is separated using modularization so every feature can be called independently.

Creating Flutter Module

To create add to app Flutter project, the project can be created with a module template like this:

$ flutter create -t module module_name

The difference with the common Flutter project is now this Flutter app can be imported as a module. Also in this template now both folder .android and .ios is generated folders.

Add the Flutter module as a dependency

To add the flutter module to the native app (Senyumku and Tunaiku), Senyumku uses Android Archive (AAR) method to import Flutter module as a dependency. To build it, just use the build AAR command like this:

flutter build aar

This command creates (by default all debug/profile/release modes) a local repository, with the following files:

build/host/outputs/repo
└── com
└── example
└── my_flutter
├── flutter_release
│ ├── 1.0
│ │ ├── flutter_release-1.0.aar
│ │ ├── flutter_release-1.0.aar.md5
│ │ ├── flutter_release-1.0.aar.sha1
│ │ ├── flutter_release-1.0.pom
│ │ ├── flutter_release-1.0.pom.md5
│ │ └── flutter_release-1.0.pom.sha1
│ ├── maven-metadata.xml
│ ├── maven-metadata.xml.md5
│ └── maven-metadata.xml.sha1
├── flutter_profile
│ ├── ...
└── flutter_debug
└── ...

Then in the native app, this repo can be imported via maven repositories like this:

android {
// ...
}

repositories {
maven {
url 'some/path/my_flutter/build/host/outputs/repo'
// This is relative to the location of the build.gradle file
// if using a relative path.
}
maven {
url 'https://storage.googleapis.com/download.flutter.io'
}
}

dependencies {
// ...
debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}

You can set host or CI/CD for this Flutter AAR repository so your native app can get the Flutter library as a dependency.

Run Flutter Module

To run the Flutter module from the native app, the main Flutter activity (FlutterActivity) needs to be registered first in the AndroidManifest.xml like this:

<activity
android:name=”io.flutter.embedding.android.FlutterActivity”
android:theme=”@style/LaunchTheme
android:configChanges=”orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode”
android:hardwareAccelerated=”true”
android:windowSoftInputMode=”adjustResize”
/>

Then to call it to the specific module (using route) call the FlutterActivity with the initial route method:

myButton.setOnClickListener {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(this)
)
}

Now, any feature from the Flutter module can be called from the native application. However, now the Flutter module will depend on the native platform to run and test it. Because of that, there is a special feature in Senyumku (module in Flutter) to test any feature in the Flutter app.

This feature can’t be called from a native application and is only used in the Flutter module its own APK build. Also to make sure the config android settings are configured automatically in CI/CD process, there are a script to copy the configuration of the android into .android files.

# Clear original gradle project file> .android/build.gradle# Update content gradle project file with exisiting configurationcat config/android/build.gradle >> .android/build.gradle# Clear original manifest flutter module> .android/Flutter/src/main/AndroidManifest.xml# Update content manifest flutter module file with exisiting configurationcat config/android/flutter/manifest/AndroidManifest.xml >> .android/Flutter/src/main/AndroidManifest.xml

Now when the QA tester needs to test our product features that use Flutter module, just use the Flutter special APK that called feature testing module.

Sharing module between application

Now after integrating the Flutter module into the native application. However, there are different user interface designs between Senyumku and Tunaiku like this:

Senyumku & Tunaiku transfer screen design

Senyumku App Widget Theme

For this, there is a special theme class to handle the different designs for the Senyumku and Tunaiku apps. Senyumku application theme contains the theme itself (Senyumku, Tunaiku), text styles, and colors.

/// Base styles based on the application theme
class SenyumkuAppTheme {
final SenyumkuThemes senyumkuThemes;
final SenyumkuTextStyles senyumkuTextStyles;
final SenyumkuColors senyumkuColors;
SenyumkuAppTheme({
required this.senyumkuThemes,
required this.senyumkuTextStyles,
required this.senyumkuColors,
});
}

The Senyumku app theme contains the enum of the list of the different themes designed for the Senyumku widget in the application and also the specific value for the theme (example: name, font family).

/// Theme of the application (tunaiku or senyumku)
/// This theme means what is the application that is using right now for this flutter module
/// If the theme is tunaiku then this flutter module is called from tunaiku application, otherwise is called from senyumku
enum SenyumkuThemes {
tunaiku,
senyumku,
}
/// Get the value based on the application theme
extension SenyumkuThemeData on SenyumkuThemes {
String get name {
switch (this) {
case SenyumkuThemes.senyumku:
return ThemeConstants.senyumku;
case SenyumkuThemes.tunaiku:
return ThemeConstants.tunaiku;
}
}
String get fontFamily {
switch (this) {
case SenyumkuThemes.senyumku:
return "Muli";
case SenyumkuThemes.tunaiku:
return "Roboto";
}
}
}

SenyumkuTextStyles containing all the TextStyle that is used in the Senyumku widget. The text styles will automatically adapt based on the themes that used in the application

/// Senyumku text styles base class
class SenyumkuTextStyles {
final SenyumkuThemes senyumkuThemes;
final SenyumkuColors senyumkuColors;
TextStyle get h20 {
switch (senyumkuThemes) {
case SenyumkuThemes.senyumku:
return TextStyles.h20.apply(
color: senyumkuColors.accentColor,
);
case SenyumkuThemes.tunaiku:
return TextStyles.h20.apply(
fontFamily: senyumkuThemes.fontFamily,
color: senyumkuColors.accentColor,
);
}
}

//...
}

SenyumkuColors contain all the Colors that is used in the Senyumku widget. The colors will automatically adapt based on the themes that used in the application

/// Senyumku colors base class
class SenyumkuColors {
final SenyumkuThemes senyumkuThemes;
Color get primaryColor {
switch (senyumkuThemes) {
case SenyumkuThemes.senyumku:
return AppColors.tunaikuColors01PrimaryGreen;
case SenyumkuThemes.tunaiku:
return AppColors.primarySolidOceanBlueTunaiku;
}
}

// ...
}

Method Channel

To get the current theme in the application, the native will have a method channel to listen to the call from the Flutter app like this:

MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
MethodChannelConstant.CHANNEL_NAME
).setMethodCallHandler { call, result ->
when (call.method) {
"getAppTheme" -> {
// Senyumku App will return "senyumku", Tunaiku App will return "tunaiku"
val appTheme = "tunaiku"
result.success(appTheme)
}
}
}

Then in Flutter just call the method getAppTheme to know which app called the Senyumku Widget:

/// Default value of the app theme is "senyumku"
Future<String> getAppTheme() async {
try {
final result = await platform.invokeMethod(
"getAppTheme",
);
if (result != null) {
return result;
} else {
return ThemeConstants.senyumku;
}
} catch (e) {
return ThemeConstants.senyumku;
}
}

Call theme in screen

Finally to use a theme similar like ThemeData This can be done by creating the InheritedWidget class to give listenable class for the entire application

/// Widget config for the app environment (flavor)
class ThemeConfig extends InheritedWidget {

/// Theme of the application (senyumku or tunaiku)
final SenyumkuAppTheme appTheme;
const ThemeConfig({
Key? key,
required this.appTheme,
}) : super(key: key, child: childWidget);
static FlavorConfig of(BuildContext context) {
final FlavorConfig? result =
context.dependOnInheritedWidgetOfExactType<FlavorConfig>();
assert(result != null, 'No ThemeConfig found in context');
return result!;
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}

Then in the main application wrap the root of the widget with the theme config so all the widgets can access the current context theme and value

void main() async {
String theme = await SenyumkuMethodChannelImpl(
senyumkuSharedPreferences: _senyumkuSharedPreferences)
.getAppTheme();
SenyumkuThemes appTheme = AppThemes.getSenyumkuThemes(theme);
SenyumkuColors senyumkuColors = SenyumkuColors(senyumkuThemes: appTheme);
SenyumkuTextStyles senyumkuTextStyles = SenyumkuTextStyles(
senyumkuThemes: appTheme, senyumkuColors: senyumkuColors);
ThemeConfig(
appTheme: SenyumkuAppTheme(
senyumkuThemes: appTheme,
senyumkuTextStyles: senyumkuTextStyles,
senyumkuColors: senyumkuColors,
),
childWidget: MaterialApp(
// ...
),
);
}

With SenyumkuAppTheme, now the application can have different text styles, colors, even widgets based on a theme. Senyumku design theme can be called from context like this:

/// Get the app theme from the context
SenyumkuAppTheme appTheme = ThemeConfig.of(context).appTheme;
/// Get one of the text style from the theme
TextStyle textStyleH20 = appTheme.senyumkuTextStyles.h20;
/// Get one of the colors from the theme
Color primaryColor = appTheme.senyumkuColors.primaryColor;
/// Example use in the widget
Text("FAQ", style: appTheme.senyumkuTextStyles.h20,);

That’s all! By using the Flutter module (Add to app) native applications can now have reusable modules that can be used in multiple applications. With the reusable module, this can help the development process faster, more efficient, and has the same consistency between applications.

--

--