— Flutter, Android, iOS — 2 min read
Being cross-platform, Flutter abstracts away Platform APIs, such as iOS, Android, Web, macOS, Windows and Linux. However, there are platform specific differences which will affect how you implement features or develop package plugins.
For example, on iOS, you need to implement
didReceive(_:completionHandler:) in your app's main entrypoint (AppDelegate), where as on Android, you need to declare a Service or Broadcast Receiver in the AndroidManifest.xml file, and override FirebaseMessagingService onMessageReceived method.
On iOS, your Flutter application always runs if a push notification (or background process) is running. This is because the FlutterViewController is initialized when the application launches, and this creates a FlutterEngine.
The FlutterViewController is declared in the Main.storyboard file, so technically Flutter apps are Storyboard apps (very basic ones) 🤓. A Flutter Engine is created in the Objective-C++ file, FlutterViewController.mm:
1auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]2 initWithName:@"io.flutter"3 project:project4 allowHeadlessExecution:self.engineAllowHeadlessExecution5 restorationEnabled:[self restorationIdentifier] != nil]};Note: this isn't Objective-C, it's Objective-C++. It has both verbose allocation/initiazation syntax and also uses C++ style class: ``.
1template <typename NST>2class scoped_nsobject : public scoped_nsprotocol<NST*> {3 public:4 explicit scoped_nsobject(NST* object = nil) : scoped_nsprotocol<NST*>(object) {}56 scoped_nsobject(const scoped_nsobject<NST>& that) : scoped_nsprotocol<NST*>(that) {}78 template <typename NSU>9 scoped_nsobject(const scoped_nsobject<NSU>& that) : scoped_nsprotocol<NST*>(that) {}1011 scoped_nsobject& operator=(const scoped_nsobject<NST>& that) {12 scoped_nsprotocol<NST*>::operator=(that);13 return *this;14 }15};On Android, only the component (e.g. Activity, Broadcast Receiver, Service) you declared runs, and the Flutter application doesn't necessarily run. The FlutterEngine is only automatically run if a FlutterActivity or FlutterFragment is used. For example, in FlutterActivity, it creates a FlutterEngine in the onCreate method. This means on Android, we need to launch some kind of dart/ flutter code in the component if the FlutterEngine doesn't yet exist. There are 2 options:
1flutterEngine = new FlutterEngine(context, null);2final MethodChannel methodChannel = new MethodChannel(executor.getBinaryMessenger(), methodChannelName, new StandardMethodCodec(new AblyMessageCodec()));3methodChannel.setMethodCallHandler(this);4// Get and launch the users app isolate manually:5flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());6// Even though lifecycle parameter is @NonNull, the implementation `FlutterEngineConnectionRegistry`7// does not use it, because it was only meant to be exposed for testing framework. See https://github.com/flutter/flutter/issues/903168flutterEngine.getBroadcastReceiverControlSurface().attachToBroadcastReceiver(receiver, null);Once you do this, you'll need the Flutter application to inform the Android side that the dart side is ready to receive data being sent the Android side. This is because there is no way for the Android side to determine when the FlutterEngine/Isolate/Flutter app is ready.
I'll stop there. Try to understand the following things by reading code, preferably cloning the repo and navigating the code using an IDE (Android Studio, Xcode and AppCode, rather than VSCode for this kind of code):
FlutterFirebaseMessagingBackgroundExecutor.java. Then you could take a look at my simpler implementation in ably_flutter.FlutterViewController, FlutterJNI, etc. You should clone flutter/engine.FlutterActivity and then FlutterActivityAndFragmentDelegate.FlutterViewController.mm.Feel free to ask questions in the comments below :)