Integrating Flutter into an Existing App — Part One: Flutter with Submodules

You might be one of the people interested in using Flutter in your daily job, but unfortunately, you cannot use it as you already have a native application that you cannot just rewrite now with Flutter.

Luckily, Flutter can be integrated into an already existing application — at Groupon, we have been using it in Merchant application since May.

In the next three articles I would like to tell you:

Flutter with submodules

The approach that I will be describing here is called with submodules as it’s done with help of git submodules.
Flutter project structure is heavily opinionated, it should look like this:

└── FlutterApplication
├── android
├── ios
└── lib

When having already both Android and iOS repositories for your existing native project, you do not want to put everything into one mega-repository.

The solution to this is to have separate Android, iOS and Flutter repository. Your native Android repo would be submoduled into android folder and iOS repo into ios folder.

Project structure

First, let’s assume this is your project structure and AwesomeApp is your existing Android application:

└── MyProjects
├── AwesomeApp
│ └── AndroidAwesomeApp [1]
│ ├── mainModule [2]
│ │ └──
build.gradle [3]
│ ├──
build.gradle [4]
│ └──
settings.gradle [5]
└── Flutter

Required steps to add include Flutter into your apk:

  1. Rename your applications folder name AndroidAwesomeApp [1] to android
  2. Rename your main Android module to app
  3. Update build.gradle [3] in the app folder
  4. Update build.gradle [4] in the root folder
  5. Update settings.gradle [5]
  6. Create pubspec.yaml and lib
  7. Run flutter run

1. Rename your applications folder name

Flutter expects your Android code to be located in android folder:

└── MyProjects
├── AwesomeApp
│ └── android <=============== [1]
│ ├── mainModule [2]
│ │ └──
build.gradle [3]
│ ├──
build.gradle [4]
│ └──
settings.gradle [5]
└── Flutter

2. Rename your main Android module to app

Flutter build tool is really opinionated and has to have iOS code in ios folder and Android in android. Furthermore, the main module of the Android project has to be named app otherwise it won’t be found.

Make sure your project structure looks now like this:

└── MyProjects
├── AwesomeApp
│ └── android [1]
│ ├── app <=============== [2]
│ │ └──
build.gradle [3]
│ ├──
build.gradle [4]
│ └──
settings.gradle [5]
└── Flutter

3. Update build.gradle [3] in the root folder

// build.gradle [3]
// Where should output folder for binaries be
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
// All subprojects need to be dependent of the main module
subprojects {
project.evaluationDependsOn(':app')
}
// Flutter cleanup task
task clean(type: Delete) {
delete rootProject.buildDir
}

4. Configuring app build.gradle

Make sure you put this code at the beginning of your app/build.gradle [4]

// build.gradle [4]
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
// Applying flutter.gradle has to be after com.android.application pluggin
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

// Path to flutter folder
flutter {
source '../..'
}

5. Configure settings.gradle [5] —adding plugin support

Flutter android plugins are normal Android modules — because those will be added in pubspec.yaml we need to add dynamic loading:

// settings.gradle[5]
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

6. Create pubspec.yaml and lib

To successfully run flutter build commandpubspec.yaml file is required. To create a basic .yaml file, just run flutter create <project name> in a temporary folder and copy both pubspec.yaml and lib. into AwesomeApp folder:

└── MyProjects
├── AwesomeApp
│ ├── android [1]
│ │ ├── app [2]
│ │ │ └──
build.gradle [3]
│ │ ├──
build.gradle [4]
│ │ └──
settings.gradle [5]
│ ├── lib <================== [6]
│ │ └── main.dart <======== [6]
│ └── pubspec.yaml <========= [6]
└── Flutter

7. Run flutter run

Now you can run the app with:

  • flutter run -release if you have an actual device connected
  • flutter run -debug if you have emulator connected

Know issue for debug build:

When trying to create a debug build for the emulator you might encounter this issue:

> Could not resolve all artifacts for configuration :app:debugCompileClasspath’.
> Failed to transform file ‘flutter-x86.jar’ to match attributes {artifactType=android-classes} using transform JarTransform
> Transform output file .\build\app\intermediates\flutter\flutter-x86.jar does not exist.

flutter-x86.jar file is needed to have hot reload during the development. To fix this issue, run gradlew flutterBuildX86Jar when you are in the android folder — this should copy the jar into the build folder.

Until the fix is for this issue is merged into beta channel, this solution should be enough.

Showing Flutter screen

At this point, we have the application with Flutter embedded! One minor issue is that we cannot see Flutter view anywhere.

To fix it, we need to:

  • extend Application class with FlutterApplication
  • create simple FlutterActivity
class MyFlutterActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
  • register the Activity in the AndroidManifest.xml file
<activity
android:name=".MyFlutterActivity"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
</activity>
  • launch the Activity
startActivity(Intent(this, MyFlutterActivity::class.java))

Congratulations! You have embedded Flutter into your existing application!

If you want to see the required changes in a real project, here is an unofficial client for Freesound service. It’s a normal Android project where I’ve added support for Flutter in the last three commits.


In the next part I discuss Flutter attach approach to include Flutter in a project.