Intents

Intents

Intent 在 Flutter 中的对应概念是什么?

在 Android 中,Intent 主要有两个使用场景:在 Activity 之前进行导航,以及组件间通信。Flutter 却没有 intent 这样的概念,但是你依然可以通过原生集成 (插件) 来启动 intent。

Flutter 实际上并没有 Activity 和 Fragment 的对应概念。在 Flutter 中你需要使用 Navigator 和 Route 在同一个 Activity 内的不同界面间进行跳转。

Route 是应用内屏幕和页面的抽象,Navigator 是管理路径 route 的工具。一个 route 对象大致对应于一个 Activity,但是它的含义是不一样的。Navigator 可以通过对 route 进行压栈和弹栈操作实现页面的跳转。Navigator 的工作原理和栈相似,你可以将想要跳转到的 route 压栈 (push()),想要返回的时候将 route 弹栈 (pop())。

在 Android 中,在应用的 AndroidManifest.xml 文件中声明 Activity。

在 Flutter 中,你有多种不同的方式在页面间导航:

  • 定义一个 route 名字的 Map。(MaterialApp)

  • 直接导航到一个 route。(WidgetApp)

下面的例子创建了一个 Map。

void main() {
  runApp(MaterialApp(
    home: MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => MyPage(title: 'page A'),
      '/b': (BuildContext context) => MyPage(title: 'page B'),
      '/c': (BuildContext context) => MyPage(title: 'page C'),
    },
  ));
}

通过将 route 名压栈 (push) 到 Navigator 中来跳转到这个 route。

Navigator.of(context).pushNamed('/b');

Intent 的另一种常见的使用场景是调用外部的组件,例如相机或文件选择器。对于这种情况,你需要创建一个原生平台集成(或者使用 已有的插件)。

想要学习如何创建一个原生平台集成,请查看 开发包和插件。

在 Flutter 中应该如何处理从外部应用接收到的 intent?

Flutter 可以通过直接和 Android 层通信并请求分享的数据来处理接收到的 Android intent。

下面的例子中,运行 Flutter 代码的原生 Activity 注册了一个文本分享的 intent 过滤器,这样其它应用就可以和 Flutter 应用分享文本了。

从以上流程可以得知,我们首先在 Android 原生层面(在我们的 Activity 中)处理分享的文本数据,然后 Flutter 再通过使用 MethodChannel 获取这个数据。

首先,在 AndroidManifest.xml 中注册 intent 过滤器:

<activity
  android:name=".MainActivity"
  android:launchMode="singleTop"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize">
  <!-- ... -->
  <intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
  </intent-filter>
</activity>

接着在 MainActivity 中处理 intent,提取出其它 intent 分享的文本并保存。当 Flutter 准备好处理的时候,它会使用一个平台通道请求数据,数据便会从原生端发送过来:

package com.example.shared;

import android.content.Intent;
import android.os.Bundle;

import java.nio.ByteBuffer;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

  private String sharedText;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
      if ("text/plain".equals(type)) {
        handleSendText(intent); // Handle text being sent
      }
    }

    new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(
      new MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall call, MethodChannel.Result result) {
          if (call.method.contentEquals("getSharedText")) {
            result.success(sharedText);
            sharedText = null;
          }
        }
      });
  }

  void handleSendText(Intent intent) {
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
  }
}

最后,当 Widget 渲染的时候,从 Flutter 这端请求数据:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }

  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

startActivityForResult() 的对应方法是什么?

Navigator 类负责 Flutter 的导航,并用来接收被压栈的 route 的返回值。这是通过在 push() 后返回的 Future 上 await 来实现的。

例如,要打开一个让用户选择位置的 route,你可以这样做:

Map coordinates = await Navigator.of(context).pushNamed('/location');

然后,在你的位置 route 内,一旦用户选择了位置,你就可以弹栈 (pop) 并返回结果:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});