Let’s explore Android Components as a Flutter Developer

A Flutter Dev’s Guide to Android

Pawan Acharya
codingmountain
8 min readSep 29, 2023

--

Working on a Flutter project is fun until you need to open build.gradle or AndroidManifest.xml etc. for some change that you saw on the internet to make something work.

With the availability of tons of packages on pub.dev for very simple to complicated tasks, it has made life easy for a flutter developer but at the same time for a new flutter developer, these package prevents them from understanding the inner details of the Native platform.

So today let's understand the Android folder present in our Flutter project so that next time you go through these folders you will be confident in what you are doing.

To enhance the productivity of this article, we’ll explore some manual tasks that you can accomplish without relying on any packages:

  1. Flavorize android app
  2. Change application id
  3. Change app icon
  4. Splash screen

android/app/src

android/
└── app/
└── src/
├── main/
│ ├── java/ (Common Java code)
│ └── res/ (Common resources)
├── debug/
├── profile/
├── free/
│ ├── java/ (Free flavor-specific Java/kotlin code)
│ └── res/ (Free flavor-specific resources)
├── paid/
└── build.gradle (Project-level build.gradle)

In the android/app/src we can see many folders like debug, main, profile, etc. These are the default folders created by Flutter for their obvious use case. Let’s understand why they are there and how it works from an android perspective.

But before that, we need some background knowledge:

build.gradle

Gradle

Gradle is the build tool that helps a developer to basically build the project, it handles all the compilation and all the inner details needed to build the project. It is majorly used in Android and Java/C++ ecosystems. It provides build caching, parallel execution, and custom build logic in high-level, expressive build language.

In simple words, it helps to build our Android project, manage dependency, etc.

productFlavors


defaultConfig {
applicationId "default_app_id"
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}

flavorDimensions "default"
productFlavors {
free {
dimension "default"
applicationIdSuffix ".free"
manifestPlaceholders = [appName: "Free-Name"]
}
paid {
dimension "default"
applicationId "my_app_id"
manifestPlaceholders = [appName: "Name"]
}

}

Let's say you created an app and want to have two different versions of the same app, one with more features (paid) and one with fewer features and ads(free).

Maintaining two different project repositories is not a solution for this. We can utilize productFlavors and create as much as white labels/variants as we need. Each variant can have different API URLs, app logos, app names, etc.

It supports having different application IDs per flavor. For eg: in each variant block you can add the value to applicationId so that a specific variant will have that particular application ID. With applicationIdSuffix you can append the suffix like .free .paid on the default app ID defined on defaultConfig.

There is one section called defaultConfig it is the same as productFlavors but here you define the properties that will default to all flavors so you don’t have to repeat them again.

There are many such useful properties that deserve a separate article but for now, let's summarize that, with productFlavors we can create customized white-labeled apps with different names, IDs, API URLs, logos, etc.

buildTypes

In the app/build.gradle we have a section called buildTypes.

    buildTypes {
release {
signingConfig signingConfigs.debug
}

free {}

paid {
signingConfig signingConfigs.debug
}
}

Build types are somehow similar to product flavor but it has its separate use case. It helps to define a configuration for our app for eg: we need our app to support debugging like adding breakpoints etc we have debug buildType (It will be there by default even if we don’t declare it there, we can add it to customize it), we might want to shrink, obfuscate code, etc. So for that case, we have release.

Also, we need to sign our apk/bundle before release we can do it here via signingConfig in the specific buildTypes block.

Apart from that, we can have any other types, like Flutter introduced profile build type which will be configured internally by Flutter. It supports additional features to analyze the performance of the Flutter app.

Apart from release, debug, or profile we can have our custom buldType like shown above free and paid. They are the flavor and if we want to have individual signingConfig and other build setups we can include them here otherwise it is optional.

This was some background information about buildTypes and productFlavors.

Now we have covered the concept of buildType and productFlavors it will be easy to understand why there are different folders like debug, main, profile, free, etc.

Can you guess it?

In app/build.gradle buildType and productFlavors we can tweak small information like applicationId, applicationName, signingConfig etc. But if we want some more control and flexibility that’s where these folders come into play.

They allow us to provide different assets, native codes, etc. based on flavor and buildTypes. For eg: we might want to have different google-services.json based on the flavor. This is where we can do it.

Also if you notice there is AndroidManifest.xml one in each directory so yes, we can have individuals AndroidManifest.xml for different flavors and buildType.

This is why we can access internet in debug build without explicitly providing internet premission as there is different AndroidManifest.xml file in debug folder having interent permission provided.

In conclusion, in an Android project, you can create different directories within the ‘src’ folder to meet the unique needs of different app variants. When building a specific variant, such as ‘free,’ the process begins by gathering essential information from the ‘main’ directory, which represents the default version. However, any additional details specified in the ‘free’ directory, will override the default details provided. It provides a powerful way to tailor each app variant according to your specific requirements.

(main) ---> (Basic Info)

(free) ---> (Extra Info, Overrides or Adds)

AndroidManifest.xml

AndroidManifest.xml is one of the main files in the Android project. It allows us to declare necessary permissions, application names, activities, and intents, etc.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.dreamz_mobile">

/// Permissions
<uses-permission android:name="android.permission.INTERNET" />

/// Activity and intent filter
<application
android:name="${applicationName}"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">

<activity android:name="com.facebook.FacebookActivity"
android:exported="true"
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />

<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>
</manifest>

The permission part is pretty self-explanatory.

Activity represents different screens of our app. If we are working on Native Android we need to create different activities like LoginActivity, DashboardActivity etc. and we have to register them in AndroidManifest.xml.

But in Flutter the whole app is hosted on MainActivity which extends FlutterActivity.

If you want to learn in-depth about AndroidManifest.xml let me know in a comment. For now, let's move ahead with this information.

app/src/*/res

res/
├── drawable/
│ ├── drawable-mdpi/
│ ├── drawable-hdpi/
│ ├── drawable-xhdpi/
│ ├── drawable-xxhdpi/
│ └── drawable-xxxhdpi/

├── layout/

├── mipmap/
│ ├── mipmap-mdpi/
│ ├── mipmap-hdpi/
│ ├── mipmap-xhdpi/
│ ├── mipmap-xxhdpi/
│ └── mipmap-xxxhdpi/

├── values/
├── colors.xml
└── strings.xml

Every flavor-specific directory like main, free, paid, etc can have a folder named res . It stands for resources so it's clear from the name that this folder is a hub for all the resources in the Android Project.

You can see a folder with the suffix mdpi, hdpi etc. they are basically for different screen densities. For old devices with lower resolution, it will take resources from mdpi, or let's say based on pixel density the device takes resources from a specific directory. (To be specific when you download app from playstore, playstore optimize the apk you download by removing the directory your device don’t need).

drawable

Here you will place all the image resources that you will use in the app. As a Flutter developer, we don’t have to touch this directory most of the time but if you want to change the splash screen image you might consider adding the image in the drawable directory. (To generate the image for all dpi directory you can use online tools, android studio resource manager or figma plugins.)

You might want to take a look at the Flutter document on how to change the splash screen image.

mipmap

Drawable is used to store images used inside the app and the mipmap directory is used to store launcher icons/app icons. It is recommended to place app icons in this directory as it will help Launchers and OS pick the best icon to not compromise the sharpness of the app icon.

Also, Android Studio provides a very easy way to replace the launcher icon. For this, you need to open your Android project on AndroidStudio and use the resource manager tool. Right-click on res → New → Image Asset and follow the dialog box by selecting asset, background, etc.

app/build.gradle & project/build.gradle

We already touched on some attributes of the build.gradle file, but in the Android project we can find two build.gradle, let's find out why.

Why multiple build.gradle?

In Android, there is the concept of creating multiple modules. Each module can be treated as a separate entity that has its own build.gradle , source code, resources, etc. Multimodule has its own Pros and Cons, let’s keep it aside for now.

Android project has a default module named app and we can have many modules based on our needs for eg: if we want to isolate the authentication part then we can create a module named auth.

As a Flutter developer working with Dart only we might not need to create new modules but if we are creating native plugins etc. we can do so based on requirements.

So this should make it pretty much clear why we have multiple build.gradle files. Let's summarize,

  1. project/build.gradle → It’s located in the root of the android directory and contains configuration that applies to the entire project, including all modules.
  2. app/build.gradle → It’s located on each module directory like android/app/build.gradle or android/auth/build.gradle. It contains configuration specific to that module.

gradle.wrapper

Under the Gradle folder, we can find gradle-wrapper.properties it contains the gradle version information used to build the project. It helps to ensure that the correct version of gradle is used in the project for everyone working.

Based on this version information it downloads gradle-wrapper.jar which helps to download the required gradle files in a project.

So yes this was some information on some Important files and directories of the Android part. I hope with this read you can manually perform the tasks I mentioned initially.

Until next time 👋 👋

--

--