文章目录
  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
    3. 2.3. 2.3 Hot reload
  3. 3. 3. iOS app
    1. 3.1. 3.1 Add denpendency on Flutter module
    2. 3.2. 3.2 Use Flutter module
    3. 3.3. Build error
  4. 4. Reference

UPDATED: Updated for Flutter v1.9.1.

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.9.1
  2. You have set up Android Studio IDE 3.5

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 --androidx 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

Here we use the option “Depend on the module’s source code” described in the official wiki. 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

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 your Podfile which calls a script to setup Flutter stuff:

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

Then for each Xcode target that needs to embed Flutter, call install_all_flutter_pods(flutter_application_path) in the Podfile.

1
2
3
target 'ios_native_app' do
install_all_flutter_pods(flutter_application_path)
end

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

1
2
3
4
5
6
7
8
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_common_module (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.

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.

Build error

With Flutter 1.9.1 you might run into this build error in the final phase [CP] Embed Pods Frameworks, saying permission denied when signing file Flutter.framework:

1
2
3
4
5
PhaseScriptExecution [CP] Embed Pods Frameworks

/Users/myuser/Library/Developer/Xcode/DerivedData/ios_native_app/Build/Products/Debug-iphonesimulator/ios_native_app.app/Frameworks/Flutter.framework: replacing existing signature
/Users/myuser/Library/Developer/Xcode/DerivedData/ios_native_app/Build/Products/Debug-iphonesimulator/ios_native_app.app/Frameworks/Flutter.framework: Permission denied
Command PhaseScriptExecution failed with a nonzero exit code

According to a user replied in Flutter issue tracker, it can be fixed by adding a line chmod -R +w $1 to script ios_native_app/Pods/Target Support Files/Pods-ios_native_app/Pods-ios_native_app-frameworks.sh.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identity
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
chmod -R +w $1 # ADD THIS NEW LINE
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"

if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
fi
echo "$code_sign_cmd"
eval "$code_sign_cmd"
fi
}

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
    3. 2.3. 2.3 Hot reload
  3. 3. 3. iOS app
    1. 3.1. 3.1 Add denpendency on Flutter module
    2. 3.2. 3.2 Use Flutter module
    3. 3.3. Build error
  4. 4. Reference