文章目录
  1. 1. 1. Create a Flutter project
  2. 2. 2. Android app
    1. 2.1. 2.1 Add denpendency on Flutter module
    2. 2.2. 2.2 Use Flutter module
      1. 2.2.1. Optional: AndroidX headache
    3. 2.3. 2.3 Hot reload
  3. 3. 3. iOS app
    1. 3.1. 3.1 Add denpendency on Flutter module
      1. 3.1.1. Optional: post_install hook headache
    2. 3.2. 3.2 Use Flutter module
  4. 4. Reference

TL;DR: this guide gives a real example (which is a minimized version that comes from our real project) when you follow the Official wiki: Add Flutter to existing apps, covering some common issues you might have. Check out the full FlutterHybridExample in Github to see what it looks like when done.

Consider this situation, you have an app that works for both Android and iOS platforms, you developed them in a native way to get the best performance and user experience possible. Then one day you need to add a new feature to make it more attractive, which means the same thing has to be done twice, one for each platform. You heard of fancy and shinny stuff - Flutter that promises to work for both platforms and claims to bring native performance. But you can’t afford the time in totally rebuild the app, you still need to keep things running for existing users, just like doing an open heart surgery. So my answer is:

  • Develop the new feature using Flutter
  • Add the new Flutter module into existing native Android and iOS apps
  • Write platform specific code in Flutter, and/or add bridging code to Flutter and/or native apps as required

For this approach to be productive:

  • The new module should not rely too much on platform specific features
  • The new module should not rely too much on communication with existing apps, e.g. calling methods from native app

Before starting, here are a few prerequsites:

  1. You have set up Flutter tool chain 1.0
  2. You have set up Android Studio IDE 3.3

Let’s get started.

1. Create a Flutter project

First we create a new Flutter project FlutterHybridExample/flutter_common_module as a sub-module in order to develop a new feature:

1
2
cd FlutterHybridExample
flutter create -t module flutter_common_module

2. Android app

There’s an existing Android minimal app at FlutterHybridExample/android_native_app which represents our existing Android app. It has a TextView saying “This is Android native fragment” and a button labeled “GO TO FLUTTER” that does nothing at this stage.

2.1 Add denpendency on Flutter module

Edit file android_native_app/settings.gradle to include Flutter project as a sub-project, which will look like:

1
2
3
4
5
6
7
include ':app'

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

Then edit file android_native_app/app/build.gradle to add denpendency on Flutter module:

1
implementation project(':flutter')

After a Gradle sync, you can see a new sub-project flutter in parallel with your existing main app module in Android Studio’s project panel (as picture shown below). When you add more dependencies to flutter project, you will see more sub-projects in your Android app.

After adding flutter sub-module

2.2 Use Flutter module

You can use it in two ways, one is to create a FlutterView and display it the same way as any Android View, another is to use it as an Android Fragment. Because we need to register a MessageChannel later to enable calling Android methods at Flutter side, so it’s a bit easier to just use FlutterView.

Suppose we have an existing AndroidFragment that represents the existing Android app, now we are loading a new FlutterHostFragment which creates a FlutterView by calling Flutter.createView():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FlutterHostFragment: Fragment() {

companion object {
fun newInstance(): FlutterHostFragment {
return FlutterHostFragment()
}
}

private var flutterView: FlutterView? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
flutterView = Flutter.createView(activity as Activity, lifecycle, null)
return flutterView
}
}

Then you can build and run the app in the same way building a normal Android one.

Screen shots

Optional: AndroidX headache

As of I’m writing at Feburary 2019, Flutter doesn’t support com.android.support.androidx packages yet. If your app has migrated to AndroidX, Android Studio will give you errors like when you try to run the app:

1
2
3
4
5
6
flutter_common_module/.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java
error: package android.support.annotation does not exist

flutter_common_module/.android/Flutter/src/main/java/io/flutter/facade/Flutter.java
error: package android.support.annotation does not exist
error: package android.arch.lifecycle does not exist

This is because Flutter still uses com.android.support in their Java wrapper files, if your app is depending on com.android.support.androidx, it will cause conflict. So you have to modify some wrapper Java file generated by Flutter. The fix is simple, open flutter_common_module/.android/Flutter/build.gradle, replace the dependencies at the bottom from:

1
2
3
4
5
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'com.android.support:support-v13:27.1.1'
implementation 'com.android.support:support-annotations:27.1.1'
}

to this:

1
2
3
4
5
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.annotation:annotation:1.0.0'
}

Errors should go away after a Gradle sync. Then open FlutterFragment.java and Flutter.java, remove all imports that the compiler marks red saying can’t find the packages.

2.3 Hot reload

This is the best part of a dynamic language like Javascript where you can do hot reload that shows the changes to UI instantly. For the compiled languages like Java/Kotlin , even with partial compile and JVM hot bainary swap, it’s still a headache especially there is a long way to get to the screen that is changed.

Connect a device or launch an emulator. Then attach Flutter to the app by typing commands in terminal:

1
2
cd FlutterHybridExample/flutter_common_module
flutter attach # Use flutter attach -d DEVICE_ID if there are multiple devices connected

Then run the app in debug mode from Android Studio. Go to the Flutter screen by hitting button GO TO FLUTTER in the app. You should be albe to see output in the terminal:

1
2
3
4
5
6
Done.
Syncing files to device Nexus 5X... 5.1s

🔥 To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on Nexus 5X is available at: http://127.0.0.1:59556/
For a more detailed help message, press "h". To quit, press "q".

After you make some changes to Dart code in flutter_common_module, type r key in the terminal, then you can see the changes instanly. You can also paste the URL above into your browser to use the Dart Observatory for setting breakpoints, analyzing memory retention and other debugging tasks.

3. iOS app

There’s another minimal app at FlutterHybridExample/ios_native_app which represents our existing iOS counterpart. It uses cocoapods for dependencies management.

3.1 Add denpendency on Flutter module

First add these two lines to the end of your Podfile which calls a script to setup Flutter stuff:

1
2
flutter_application_path = '../flutter_common_module'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

Then run pod install, you should be able to see pod output like this without any error:

1
2
3
4
5
6
Analyzing dependencies
Fetching podspec for `Flutter` from `../flutter_common_module/.ios/Flutter/engine`
Fetching podspec for `FlutterPluginRegistrant` from `../flutter_common_module/.ios/Flutter/FlutterPluginRegistrant`
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)

Then go to project navigator, select ios_native_app project and then TARGETS ios_native_app. Select Build Phases tab, click the + button to add a New Run Script Phase. Add the following lines to the text field:

1
2
"$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. It should look like this:

Flutter Run Script Phase

Optional: post_install hook headache

If you have a post_install hook in your Podfile, then you need to move the content into flutter_common_module/.ios/Flutter/podhelper.rb. Flutter uses that hook to disable bitcode, which will cause conflict with yours.

Then you should be able to build and run the app like before when there’s no Flutter module.

3.2 Use Flutter module

The way described in the official wiki (see reference 1 at the end of article) requires modifying AppDelegate to register additoinal Flutter libraries, which I don’t really like. Actually the FlutterViewController class wraps everything up pretty good, so the easiest way is to add a new ViewController in storyboard and set is as subclass FlutterViewController. No code is needed.

Here we add one more host FlutterHostViewController to embed the FlutterViewController to give a little bit more flexibility.

iOS storyboard

And we are all set, just run the app. Hot reload is the same as described in a previous chapter.

Reference

  1. Official wiki: Add Flutter to existing apps
文章目录
  1. 1. 1. Create a Flutter project
  2. 2. 2. Android app
    1. 2.1. 2.1 Add denpendency on Flutter module
    2. 2.2. 2.2 Use Flutter module
      1. 2.2.1. Optional: AndroidX headache
    3. 2.3. 2.3 Hot reload
  3. 3. 3. iOS app
    1. 3.1. 3.1 Add denpendency on Flutter module
      1. 3.1.1. Optional: post_install hook headache
    2. 3.2. 3.2 Use Flutter module
  4. 4. Reference