background

Due to the requirements of the project, the team developed Flutter to realize an app for ordering food in stores (android dual-screen device). Staff use the main screen to order food, while the secondary screen displays the menu and price information to customers.

Technical solution:
  1. The overall project is in the form of flutter-app. We package the secondary screen capability as plugin and provide it to the main program.
  2. The secondary screen display scheme is Presentation;
  3. The secondary screen maintains a flutterEngine, and the main screen maintains a flutterEngine. The two engines use channels for associated communication.

Record the implementation steps:

1. Create a flutterPlugin project using androidStudio: flutter_subscreen_plugin

2. Step two, encapsulate native capabilities, providing the ability to evoke a second screen

Create a class FlutterSubScreenPresentation inherited from the Presentation, as deputy screen UI carrier:

@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) class FlutterSubScreenPresentation(outerContext: Context? , display: Display?) : Presentation(outerContext, display) { lateinit var flutterEngine: FlutterEngine override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) val engine = FlutterEngine(context) FlutterEngine = engine // Specify the initial route flutterEngine.navigationChannel.setInitialRoute("subMain"); flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint( FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main")) setContentView(R.layout.flutter_presentation_view) val flutterView: FlutterView = the findViewById (R.i d.f lutter_presentation_view) FlutterView. AttachToFlutterEngine (flutterEngine) / / must be invoked Otherwise the page will update card death flutterEngine. LifecycleChannel. AppIsResumed ()} override fun dismiss () { flutterEngine.lifecycleChannel.appIsDetached() super.dismiss() } }Copy the code

The flutter_presentation_view file looks like this:

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <io.flutter.embedding.android.FlutterView android:id="@+id/flutter_presentation_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>Copy the code
Technical points:
  1. Inherit presentation, rewrite onCreate, create a flutterEngine for associating flutterView, take flutterView as the input parameter of setContentView, and realize the use of flutter layer to draw sub-screen pages.
  2. SetInitialRoute (“subMain”) is used to specify the initial route in main.dart
  3. DartExecutor. ExecuteDartEntrypoin is used to specify the corresponding rendering engine page path: lib/main. The dart
  4. FlutterView. AttachToFlutterEngine (flutterEngine) to render the UI at this time
  5. FlutterEngine. LifecycleChannel. AppIsResumed () life events
3. Interactive communication between primary and secondary screens:

When we create the Plugin project, a class FlutterSubscreenPlugin is automatically generated, and the main and secondary screens interact with this middleware. We divide the structure into three colors to mark it :(below)

  1. Define two channels, one for primary and native interactions and one for secondary and native interactions (blue)
  2. When FlutterSubscreenPlugin binds to the main project (home screen), onAttachedToEngine is triggered. In this case, mainChannel is used for binding listening, event listening is handled in onMethodCall, Pass events received by mainChannel to subChannel for distribution (red)
  3. Provides methods for external initializations, and provides the ability to pass events received by subChannel to mainChannel for data transfer between secondary and primary screens (green)

4. Create a utility class FlutterSubScreenProvider to initialize the secondary screen
Class FlutterSubScreenProvider {companion object {init fun configSecondDisplay(plugin: FlutterSubscreenPlugin, context: Context) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { val manager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val displays = manager.displays if (displays.size > 1) { val display = displays[1] val handler = FlutterSubScreenPresentation(context, display) handler.show() plugin.onCreateViceChannel(handler.flutterEngine.dartExecutor) } } } catch (e: Throwable) { println(e.message) e.printStackTrace() } } }Copy the code
5. Define the secondary screen initialization time, let FlutterSubscreenPlugin implement ActivityAware interface, rewrite onAttachedToActivity method, call the secondary screen initialization:
class FlutterSubscreenPlugin: FlutterPlugin, ActivityAware, MethodCallHandler{ ... override fun onAttachedToActivity(binding: ActivityPluginBinding) {// Your plugin is now attached to an Activity // Initialize the secondary screen FlutterSubScreenProvider.configSecondDisplay(this, binding.activity) } ... }Copy the code
Next, paste what the UI layer needs to do in the DART file: main.dart
void main() { var defaultRouteName = window.defaultRouteName; if ("subMain" == defaultRouteName) { viceScreenMain(); } else { defaultMain(); } // UI void defaultMain() {runApp(MainApp()); } void viceScreenMain() {runApp(SubApp()); }Copy the code

Get the initRoute in the main method to differentiate and bind the corresponding widget

###### Create a new tool class for channel primary and secondary screen interaction:

Class SubScreenPlugin {static const _mainChannelName = 'screen_plugin_main_channel'; static const _subChannelName = 'screen_plugin_sub_channel'; // ignore: close_sinks static StreamController<MethodCall> _subStreamController; // ignore: close_sinks static StreamController<MethodCall> _mainStreamController; static MethodChannel _mainChannel = MethodChannel(_mainChannelName) .. setMethodCallHandler(_onMainChannelMethodHandler); static MethodChannel _subChannel; static Stream<MethodCall> get viceStream { if (_subChannel == null) { _subChannel = MethodChannel(_subChannelName) .. setMethodCallHandler(_onSubChannelMethodHandler); } if (_subStreamController == null) { _subStreamController = StreamController<MethodCall>.broadcast(); } return _subStreamController.stream; } static Stream<MethodCall> get mainStream { if (_mainStreamController == null) { _mainStreamController = StreamController<MethodCall>.broadcast(); } return _mainStreamController.stream; } the static Future < dynamic > _onSubChannelMethodHandler async (MethodCall call) {/ / vice screen channel every receives an event and let it flow, Listening externally _subStreamController? .sink? .add(call); return "success"; } the static Future < dynamic > _onMainChannelMethodHandler async (MethodCall call) {/ / home screen channel every receives an event and let it flow, External listening _mainStreamController? .sink? .add(call); return "success"; } // Call to the home screen, Static Future<void> sendMsgToViceScreen(String method, {Map<String, dynamic> params, }) async { await _mainChannel.invokeMethod(method, params); } // Call the sub-screen, Static Future<void> sendMsgToMainScreen(String method, {Map<String, dynamic> params, }) async { await _subChannel.invokeMethod(method, params); }}Copy the code

You can get all the event data from the main screen to the secondary screen by using the following method:

/ / send data SubScreenPlugin. SendMsgToViceScreen (" test ", params: {" content ":" test "}); / / get data SubScreenPlugin. ViceStream. Listen ((event) {val name = event. The method; //test val params = event.arguments; // {"content": "test"} });Copy the code

###### Note: To use android dual-screen, add the following two permissions in the list configuration file:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
Copy the code
The structure of the entire call relationship is as follows:

###### Git has been uploaded. Project path:Github.com/liyufengrex…###### Plugin has been uploaded.Flutter uses plug-ins to interact with two screens