How to enable Flutter applications to change the state of other applications across applications? See how native implementations of Flutter are implemented, and then use MethodChannel to dock with Flutter. This article is more about Android native development. There are two things that meet this need: ContentObserver and Broadcast

ContentOberver

ContentObserver is called the ContentObserver of android. The purpose of ContentObserver is to observe changes in data caused by a particular Uri, such as listening for changes in the value of a key(String). If the value changes, all the places in the system that are listening for the key will know that it has changed. To refresh the state of the related Widget based on the new value corresponding to this key, These values are stored in /data/system/users/0/ settings_global.xml,settings_system. XML, and settings_secure.

Global: All applications can access and change
System: Only system-level applications can listen and make changes, either with su permission, or lower the compiled version of the app and import android.permission.WRITE_SETTINGS
Secure: The highest security level, used to save some security Settings for the entire Android system. Change the Settings in the same way as above, but requires android.permission.WRITE_SECURE_SETTINGS

So what do we need to implement content observer listening?

A ContentObserver and a Handler(which can be omitted), for example, we listen for “Nightmare_Test_Key”

Start by customizing a ContentObserver

class MyObserver extends ContentObserver {
    final Handler mHandler;
    final Context mContext;
	public MyObserver(Context context,Handler handler) {
		super(handler);
        this.mHandler=handler;
        this.mContext=context;
	}
	@Override// Rewrite the ContentObserver onChange method
	public void onChange(boolean z) {
		// This method is triggered when the listening value changes and sends the message to a Handler
        Message obtainMessage=mHandler.obainMessage();//
        obtainMessage.obj=System.getString(mContext.getContentResolver(),"Nightmare_Test_Key"));
        // Get the new value of Nightmare_Text_Key and send it to HandlermHandler.sedMessage(obtainMessage); }}Copy the code

And I’ll create a custom Handler

class MyHandler extends Handler {
	final TextView mTextView;
	MyHandler(TextView view) {
		this.mTextView = view;
	}
	@Override
	public void handleMessage(Message message) {
		String str = (String) message.obj;// The value from ContentObserver
		this.mTextView.setText(TextUtils.isEmpty(str) ? "No data obtained": str); }}Copy the code

The following code needs to be dropped from the Activty lifecycle. If this is implemented in the lifecycle of a View, change all this to this.getContext(), or get the Android Context by other means.

TextView mTextView=new TextView(this);
Handler mHandler = new MyHandler(mTextView);
ContentObserver mContextObserser=new MyObserver(this,mHandler);
this.getContentResolver().registerContentObserver(System.getUriFor("Nightmare_Test_Key"),false,mContextObserser); // The second argumentfalseIndicates exact matching, that is, the value matches the UriCopy the code

So we have a complete listener, we just need to call it in any App.

System.putString(this.getContentReslover(),"Nightmare_Test_Key"."I want to change the Text to this.");
Copy the code

As long as the App that registered the listener is still running, the contents of that TextView will be changed. #Broadcast As one of the four components of Android, Broadcast is also quite powerful. I won’t go into details. However, Android Broadcast can be used across all running Android apps, so how can we use Broadcast to update status across applications?

Customize a Broadcast

class MyBroadcastReceiver extends BroadcastReceiver{
	final TextView mView;
	public MyBroadcastReceiver(TextView v){
		this.mView=v;
	}
    @Override
    public void onReceive(Context context, Intent intent) {
        String result = intent.getStringExtra("Test_Key");this.mView.setText(result); }}Copy the code

We register here “test. Android. Intent. Action. The test” the custom radio, the radio registration method for dynamic registration, does not involve the use of the XML file

TextView mTextView=new TextView(this);
BroadcastReceiver mBroadcastReceiver = new MyBroadcastReceiver(mTextView);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("test.android.intent.action.TEST");
this.registerReceiver(broadcastReceiver,intentFilter);
Copy the code

The whole broadcast registration is complete. Next we send a broadcast. The following code can be executed in another App

Intent intent = new Intent();
intent.putExtra("Test_Key"."Messages from other applications");
intent.setAction("test.android.intent.action.TEST");
// Use bundle to pass parameters
sendBroadcast(intent);
Copy the code

What happened to Flutter?

The following Example does not need ContentObserver, use Broadcast and go to the code, hey hey hey 😜

Moving on to the code, let’s start with the Android part of the sender

class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
    MethodChannel(flutterView, "Nightmare").setMethodCallHandler { call, _ ->
      val intent = Intent()
      intent.putExtra("Test_Key",call.method)
      intent.action = "test.android.intent.action.TEST"
      sendBroadcast(intent)
    }
  }
}

Copy the code

Why Kotlin again?


class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  MethodChannel _channel=MethodChannel("Nightmare");
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text("Add a button"),
              onPressed: (){
                _channel.invokeMethod("Button");
              },
            ),
            RaisedButton(
              child: Text("Add a Card"),
              onPressed: (){
                _channel.invokeMethod("Card"); }, ), TextField( onSubmitted: (str){ _channel.invokeMethod(str); },)],),),); }}Copy the code

The Android part of the receiver

class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
    val methodChannel = MethodChannel(flutterView, "Nightmare")
    class MyBroadcastReceiver : BroadcastReceiver() {
      override fun onReceive(context: Context, intent: Intent) {
        val result = intent.getStringExtra("Test_Key")
        methodChannel.invokeMethod(result,"")}}val mBroadcastReceiver = MyBroadcastReceiver()
    val intentFilter = IntentFilter()
    intentFilter.addAction("test.android.intent.action.TEST")
    this.registerReceiver(mBroadcastReceiver, intentFilter)
  }
}
Copy the code

The Dart part


class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> _list = [];

  MethodChannel platform = MethodChannel("Nightmare");

  @override
  void initState() {
    super.initState();
    platform.setMethodCallHandler(platformCallHandler);
  }

  Future<dynamic> platformCallHandler(MethodCall call) async {
    print(call.method);
    switch (call.method) {
      case "Button":
        _list.add(
          RaisedButton(
            onPressed: () {},
            child: Text("Button"),),); setState(() {});break;
      case "Card":
        _list.add(
            Card(
              child: Text("Card"))); setState(() {});break;
      default:
        _list.add(
            Text(call.method)
        );
        setState(() {});
        break; }}@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        centerTitle: true, ), body: Center( child: Column( children: _list, ), ), ); }}Copy the code

Take a look at the preview

This MethodChannel implements a two-way call to Flutter to Android native

Who the hell needs that?

Actual needs of individual projects

The picture above shows the status of the Flutter App(MToolkit) that controls the native App(SystemUI). Of course, this native App was decompiled and implanted into the layout. I will talk about this in detail in my next post
The lastMtoolkit, is also my own Hybrid project of Flutter and native