Add Flutter to existing Android/iOS app

Nirav Tukadiya
Flutter Community
Published in
11 min readJun 28, 2019
Add Flutter to Existing Android/iOS app

Last year, on 4th Dec 2018, Google announced Flutter 1.0. Since then Flutter has gained a lot of momentum. If you have not explored flutter yet, I recommend you to try it out. Since there are many tutorials available on how to get started with Flutter, we will not go deep into it. Here are a few links to help you get started.

I want to learn Flutter. How to start? by Pooja Bhaumik

Flutter Tutorials for Beginners by Smartherd

https://fluttercrashcourse.com/

Do you have an existing Android/iOS app and want to start with Flutter?

If yes, then this article is for you. You can continue with your existing Android/iOS code base and start building new features of your app in Flutter which can be integrated into your existing apps.

There is already wiki provided for Add Flutter to existing apps. We will create a small Android and iOS app to understand and go through all the steps.

Android/iOS Module

The app is simple. It Adds/Multiplies two numbers. The numbers are entered in the Android/iOS app which will be sent to Flutter module for Addition or Multiplication, receive the results and updates the UI. We will call this app AFE (Add Flutter to Existing) app.

Flutter Module

Flutter module will receive two numbers from Android/iOS module. It will then give the option to perform Addition or Multiplication. Based on the operation, it will deliver results back to Android/iOS app.

Add Flutter to existing Android/iOS app Series is divided into three parts.

Part 1: Add Flutter to an existing Android app

Part 2: Add Flutter to an existing iOS app

Part 3: Pass Data between Flutter to Android/iOS app

Part 1: Add Flutter to an existing Android app

Create an Android Studio Project AFEAndroid.

I selected LoginActivity (and renamed it to InputNumbersActivity) template from the list to save some time. :P

Create an Android Project

Note: I suggest you DO NOT select “user androidx.*” artifacts. I did and ran into a few issues where I was not able to compile build the project. I already created an issue in Flutter’s GitHub repository. https://github.com/flutter/flutter/issues/34582

I made a few changes in UI and this is how the Android app looks like.

AFE Android app UI
InputNumbersActivity.kt

Once we integrate Flutter Module, we will use sendDataToFlutterModule method to open flutter module and pass the data to it. As of now, we are done with AFEAndroid part. You can get the source code from Github Repository.

Create a Flutter Module AFE_Flutter

AndroidStudio -> Start New Flutter Project -> Select Flutter Module. Click Next.

Create a Flutter Module

Configure Flutter Module. Enter Project Name as afe_flutter. Provide Flutter SDK Path and Project location. Click Next.

Configure Flutter Module

Set the package name. Click Finish.

Set the package name.

It will create Flutter Module with default “Increment Counter” example. We will change the app UI which will show two number and an option to Add or Multiply those numbers.

I made changes in main.dart to show two numbers received from Android/iOS module and an option to Send results back. This is how afe_flutter module looks like:

afe_flutter Android(left), iOS(right)
main.dart of AFE_Flutter module.

Integrating AFE_Flutter in AFE_Android

Disclaimer: As of today, “Add-to-App” functionality is still in preview, the associated APIs and tooling are not stable and are subject to change. This is mentioned on flutter’s wiki page. You can track the progress here.

To import AFE_Flutter module, right click on Project Name -> New -> Module. Scroll to the bottom and select “Import Flutter Module”. Click Next.

Import_flutter_module

On the next screen, give AFE_Flutter module’s path and Click Finish.

Import AFE_Flutter module

After finishing this, you will find below changes in gradle files.

settings.gradle will have a path to AFE_Flutter directory.

include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'afe_flutter/.android/include_flutter.groovy'
))

app’s build.gradle has a dependency added for flutter module.

implementation project(':flutter')

And this is how project pane looks like

AFE_Android project pane after importing AFE_Flutter

Also, To Add Flutter to existing Android project, Java 8 compatibility is required. Please add below lines under the android section of app’s build.gradle file.

compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}

Now, we can just write a code to open flutter module in our AFE_Android app. You can either create a View or a Fragment and add it to your layout.

//Import as a viewval flutterView = Flutter.createView(
this@FlutterViewActivity,
lifecycle, null
)

addContentView(
flutterView,
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
)
//Import as a fragmentval tx = supportFragmentManager.beginTransaction()
tx.replace(R.id.someContainer, Flutter.createFragment(null))
tx.commit()

First, we will create an activity (FlutterViewActivity)and open it from InputNumbersActivity. FlutterViewActivity will render the flutter view.

From InputNumbersActivity

private fun sendDataToFlutterModule(first: Int, second: Int) {
FlutterViewActivity.startActivity(this)
}

we have successfully added AFE_Flutter to the Android app. You can get the source code of AFE_Flutter app from this Github Repository.

Part 2: Add Flutter to an existing iOS app

Create an XCode project AFEiOS

Create an XCode project

After creating the project, I made changes in UI and added validation for input fields. This is how the iOS app looks like

AFE iOS App UI
ViewController.swift of AFE_iOS

We will use the same sendDataToFlutterModule method to pass data to the flutter module. You can find the source code of AFEiOS project on this Github Repository.

Integrating AFE_Flutter in AFE_iOS

Integrating the Flutter framework requires the use of the CocoaPods dependency manager. This is because the Flutter framework needs to be available also to any Flutter plugins that you might include in afe_flutter.

If your app already uses CocoaPods then add below lines to Podfile.

flutter_application_path = '/Users/nirav/AndroidStudioProjects/FlutterModuleDemo/afe_flutter/'eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

Now, close the pod file and Xcode and run pod install inside your project directory. Once done, reopen the project from .xcworkspace file.

Note: Make sure you don’t open .xcodeproject file otherwise you will get compilation errors regarding Flutter Framework not found.

Disable BITCODE for the target

Since Flutter doesn’t support as of now, you need to disable ENABLE_BITCODE flag located in your target's Build Settings->Build Options->Enable Bitcode part. eseidelGoogle already created an issue on Flutter’s GitHub repository. You can track the status here.

Add a build phase for building the Dart code

Select the top-level AFEiOs project in the Project Navigator. Select TARGET AFEiOS in the left part of the main view, and then select the Build Phases tab. Add a new build phase by clicking the + towards the top left of the main view. Select New Run Script Phase. Expand the new Run Script, just appended to the list of phases.

Paste the following into the text area just below the Shell field:

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

Finally, drag the new build phase to just after the Target Dependencies phase.

You should now be able to build the project using ⌘B. To understand what is going on Under the hood, go to this guide.

Write code to use FlutterViewController from AFE_iOS

First, declare your app delegate to be a subclass of FlutterAppDelegate. Then define a FlutterEngine property, which helps you to register a plugin without a FlutterViewController instance.

Now, we can just write a code to open flutter module in our AFE_iOS app. Add below lines in sendDataToFlutterModule method inside ViewController.swift file.

let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine;let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)!;self.present(flutterViewController, animated: false, completion: nil)

Now Clicking on Send to Flutter Module button will open AFE_Flutter module inside our AFE_iOS app.

Part 3: Pass Data between Flutter to Android/iOS app

Flutter allows you to call platform-specific APIs whether available in Java or Kotlin code on Android, or in Objective-C or Swift code on iOS.

In Flutter documents, Flutter part is the client app and native part i.e. Android/iOS part is the host app. Flutter’s platform-specific API relies on a flexible message passing style. Client app (Flutter part of the app)sends the message to the host over the platform channel. The host app (Android/iOS part of the app) listens on the platform channel and receives the message.

Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram:

Message passing between host and client app

More information about platform channels can be found here.

Setting up AFE_Flutter to receive data from AFE_Android and AFE_iOS

To pass the data between the client and the host app, we must create a platform channel on which we will send/receive data.

In AFE_Flutter Module create a channel with the name ‘in.androidgeek.afe/data’

class _MyHomePageState extends State<MyHomePage> {
.....
static const platform = const MethodChannel('in.androidgeek.afe/data');......}

Create a method _receiveFromHostto handle data received from the host. We will process the data and show it in the UI later on.

Initialize the platform in the _MyHomePageState constructor and set the _receiveFromHost method as Method Call Handler.

_MyHomePageState() {
platform.setMethodCallHandler(_receiveFromHost);
}

Now AFE_Flutter is listening to the channel ‘in.androidgeek.afe/data’. Any message sent to this channel will be received in _receiveFromHost method.

Setting up AFE_Android to send and receive data from AFE_Flutter

Since we rendered Flutter View inside FlutterViewActivity, We will set it up to send and receive data from AFE_Flutter.

To Send entered numbers to AFE_Flutter, add below code after addContentView method in FlutterViewActivity.Kt

val first = intent?.extras?.getInt("first")
val second = intent?.extras?.getInt("second")

val json = JSONObject()
json.put("first", first)
json.put("second", second)

Handler().postDelayed({
MethodChannel(flutterView, CHANNEL).invokeMethod("fromHostToClient", json.toString())
}, 500)

We are getting first and second numbers from previous activity in extras and creating the JSON object to send to AFE_Flutter. MethodChannel is being used for that. fromHostToClient is the method name we are invoking and another is arguments in which JSON string is being passed.

Note: I have added the delay of 500ms because it takes a little time to render FluterView and initialize the channel. I am using emulator, you may not face this issue in the real device. Alternatively, from AFE_Flutter you can notify host app to send data to AFE_Flutter.

Now, to receive data from AFE_Flutter, we need to set up MethodCallHandler as we did in AFE_Flutter.

MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
// manage method calls here
if (call.method == "FromClientToHost") {
val resultStr = call.arguments.toString()
//TODO parse result string and send results back to previous activity
} else {
result.notImplemented()
}
}

Here, FromClientToHost method name is being used to receive data from AFE_Flutter.

Passing data between AFE_Android and AFE_Flutter

Receiving the data in AFE_Flutter and showing it in UI.

Future<void> _receiveFromHost(MethodCall call) async {
int f = 0;
int s = 0;

try {
print(call.method);

if (call.method == "fromHostToClient") {
final String data = call.arguments;
print(call.arguments);
final jData = jsonDecode(data);

f = jData['first'];
s = jData['second'];
}
} on PlatformException catch (e) {
//platform may not able to send proper data.
}

setState(() {
_first = f;
_second = s;
});
}

Also, remove hardcoded numbers in AFE_Flutter and set _first and _second as a text in both the text view.

Performing selected operation and sending results back to the host app.

void _sendResultsToAndroidiOS() {
if (dropdownValue == 'Add') {
_result = _addNumbers(_first, _second);
} else {
_result = _multiplyNumbers(_first, _second);
}

Map<String, dynamic> resultMap = Map();
resultMap['operation'] = dropdownValue;
resultMap['result'] = _result;

setState(() {
resultStr = resultMap.toString();
});

platform.invokeMethod("FromClientToHost", resultMap));
}

Now, we need to process the data received from the client app in FlutterViewActivity and send it back to InputNumbersActivity to show it in the UI.

MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
// manage method calls here
if (call.method == "FromClientToHost") {
val resultStr = call.arguments.toString()
val resultJson = JSONObject(resultStr)
val res = resultJson.getInt("result")
val operation = resultJson.getString("operation")

val intent = Intent()
intent.putExtra("result", res)
intent.putExtra("operation", operation)
setResult(Activity.RESULT_OK, intent)
finish()
} else {
result.notImplemented()
setResult(Activity.RESULT_CANCELED)
finish()
}
}

and in InputNumbersActivity.kt’s onActivityResult method,

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (requestCode == 100) {
if (resultCode == Activity.RESULT_OK) {
val result = data?.extras?.getInt("result")
val operation = data?.extras?.getString("operation")

tvResult.text = "${when (operation) {
"Add" -> "Addition"
"Multiply" -> "Multiplication"
else -> "NA"
}} of the entered numbers is $result"
} else {
tvResult.text = "Could not perform the operation"
}
}
}

We are done!! AFE_Android should show the result of the operation being performed on input numbers. You can find the full source code on these Github Repositories, AFE_Android and AFE_Flutter.

Passing data between AFE_iOS and AFE_Flutter

Creating a data channel

//Set up data channel to send/receive data from AFE_Flutterlet afeDataChannel = FlutterMethodChannel(name: "in.androidgeek.afe/data",binaryMessenger: flutterViewController)

Sending input numbers from AFE_iOS to AFE_Flutter

//Sending data to AFE_Flutterlet jsonObject: NSMutableDictionary = NSMutableDictionary()jsonObject.setValue(first, forKey: "first")jsonObject.setValue(second, forKey: "second")
var convertedString: String? = nildo{let data1 = try JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions.prettyPrinted)convertedString = String(data: data1, encoding: String.Encoding.utf8)} catch let myJSONError {print(myJSONError)}afeDataChannel.invokeMethod("fromHostToClient", arguments: convertedString)

Receiving results from AFE_Flutter after the operation is being performed.

//setting up method call handler to receive data from AFE_FlutterafeDataChannel.setMethodCallHandler({[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in//receiving data from AFE_Flutter and showing results in UIguard call.method == "FromClientToHost" else {result(FlutterMethodNotImplemented)return}flutterViewController.dismiss(animated: true, completion: nil)var dictonary:NSDictionary? = call.arguments as? NSDictionaryif(call.arguments != nil){self?.lResult.text = "\((dictonary!["operation"] as! String)=="Add" ? "Addition" : "Multiplication"): \(dictonary!["result"]!)"}else{self?.lResult.text = "Could not perform the operation"}})

We have successfully integrated AFE_Flutter into AFE_iOS app.

Conclusion

Even though Flutter’s Add Flutter to existing apps is still in preview, you can start using it in the production app. Rather than developing your existing app completely in Flutter, you can start developing new features in Flutter and reuse your existing Android/iOS codebase.

The complete project source can be found on below Github Repositories:

AFE_Android: https://github.com/nirav-tukadiya/AFEAndroid
AFE_iOS: https://github.com/nirav-tukadiya/AFEiOS
AFE_Flutter: https://github.com/nirav-tukadiya/AFE_flutter

Feel Free to post your comments if you face any issues. You can 👏 if you liked the article. Do you know that you can 👏 50 times in a single post? try it.

Nirav Tukadiya
Android | Flutter | Machine Learning

You can follow me on Twitter or GitHub, or Add me on LinkedIn or Just drop me an email to nirav.tukadiya@gmail.com

--

--

Nirav Tukadiya
Flutter Community

Android Architect 🔥 | Kotlin & Flutter enthusiast 💻 | Sharing tips & tutorials 📝 | Let's build awesome apps together! 🚀