I recently got started developing a cross-platform app in Flutter, a framework developed by Google that uses the Dart programming language. One major benefit of the fact that Flutter is created by Google is the mostly easy integration with Google’s other services. I have come across one minor exception to this rule in my attempt to integrate Firebase with my Flutter project, an error regarding .dex
file generation.
I followed along with Google’s guide on modifying my app’s Gradle configuration files to integrate Firebase services into the app. My Gradle config files after implementing the Google guide’s changes were as follows:
./android/
:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:4.3.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
./android/app/
:
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'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.jonathanbayless.autodo"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin
After I was done following the Google guide I was met with a build error that was not addressed in the Google guide. The addition of the google-services
package inflated the size of the built project to the point that it exceeded the number of methods allowed in a single .dex
file.
A .dex
file is similar to a .jar
file from typical Java development but is unique to the Android ecosystem. The .dex
file is used as an intermediary file in developing a .apk
file that will be loaded onto the Android device. I discovered through this error message that the default Gradle configuration generated with Flutter only uses one .dex
file to generate the .apk
file.
The solution I found to this error consisted of two changes: enabling a build flag regarding the .dex
generation and enabling resource shrinking. The default configuration that only allows for one .dex
file can be changed by setting the multiDexEnabled
to true
in the defaultConfig
section of the android
rules.
defaultConfig {
applicationId "com.jonathanbayless.autodo"
multiDexEnabled true
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
The first change alone will solve the build errors, but I was frustrated with the massive increase in size of my application when I was only using a small portion of the Firebase feature set. There are two additional flags available to reduce the size of the .dex
files and resulting .apk
file. The shrinkResources
flag removes unnecessary code that comes by default with the google-services
package and the minifyEnabled
just removes any dead code.
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
shrinkResources true
minifyEnabled true
}
}
The mix of these three flags reduced the size of my application and got it back to being able to compile.
The ./android/
Gradle config file was not changed at all, it was the same as in the original example. Here is the modified ./android/app/
Gradle file:
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'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.jonathanbayless.autodo"
minSdkVersion 16
targetSdkVersion 28
multiDexEnabled true
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
shrinkResources true
minifyEnabled true
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin