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:
- How we have done the integration — Flutter with submodules
- What are alternative ways to include it — Flutter with “attach”
- Challenges we’ve faced with Flutter integration (coming soon)
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:
- Rename your applications folder name
AndroidAwesomeApp [1]
toandroid
- Rename your main Android module to
app
- Update
build.gradle [3]
in the app folder - Update
build.gradle [4]
in the root folder - Update
settings.gradle [5]
- Create pubspec.yaml and
lib
- 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 connectedflutter 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 withFlutterApplication
- create simple
FlutterActivity
class MyFlutterActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
- register the
Activity
in theAndroidManifest.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.