[譯]Flutter for Android Developers - Intents

catsuo發表於2018-02-28

在Flutter中Intent等價於什麼

  • in Android

    • 在Android中Intent用來表示應用的一種意圖,常見的使用場景比如切換不同的Activity,傳送廣播,呼叫手機內的其他模組功能等等。
  • in Flutter

    • 沒有Intent的概念,但是如果需要的話Flutter也能通過呼叫Android對應的平臺介面觸發Intent。

在Flutter中要實現一個應用的多個介面並且在介面間切換跳轉需要用到兩個核心的概念:

1.Route: Route是應用中一個介面的抽象,類似Android中的Activity。

2.Navigator: Navigator是一個Widget,它用於管理應用中所有的Route。一個Navigator可以通過push或者pop操作來入棧和出棧一個Route,進而實現從一個介面進入另一個介面或者返回上一個介面的效果。

與在Android中的AndroidManifest.xml中宣告Activity類似,在Flutter中通過構造MaterialApp例項時傳入的routes引數來配置Route。如下:

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

構造MaterialApp時與之前不同的是我們傳入了一個Map作為其routes引數,該Map的key是String型別,value是一個用於構造對應Route的方法。 接下來就可以通過獲取一個Navigator物件來改變當前螢幕內顯示的Route:

Navigator.of(context).pushNamed('/b');
複製程式碼

pushNamed方法傳入的引數與之前在MaterialApp構造時配置的routes是匹配的,這個例子中將key為"/b"的Route入棧,進而介面跳轉到Page B。

另一個使用Intent主要的場景是去呼叫手機內的其他功能模組,比如啟動相機或者檔案管理器等。在這種情況下可以通過呼叫Android對應的平臺介面來實現。後面的例子有簡單用到Flutter中Android對應的平臺介面。

小結: 在Flutter中處理介面間的跳轉切換使用Navigator和Route配合完成。也可以通過Flutter提供的Android對應的平臺介面去呼叫外部的其他模組。

怎樣在Flutter中處理其他應用傳送過來的Intent

在Flutter中處理收到的Intent分為兩個步驟: 1.接收Intent 這一步還是利用Android的原理,在AndroidManifest.xml中配置我們的Activity所能夠接收的Intent。 2.處理資料 這一步通過Flutter提供的Android平臺介面在Flutter層獲取Android層接收到的Intent中所攜帶的資料內容。 通過一個例子來解釋一下。 首先在AndroidManifest.xml中配置我們的Activity可以接收的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">
       <!-- This keeps the window background of the activity showing
            until Flutter renders its first frame. It can be removed if
            there is no splash screen (such as the default splash screen
            defined in @style/LaunchTheme). -->
       <meta-data
           android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
           android:value="true" />
       <intent-filter>
           <action android:name="android.intent.action.MAIN"/>
           <category android:name="android.intent.category.LAUNCHER"/>
       </intent-filter>
       <intent-filter>
           <action android:name="android.intent.action.SEND" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:mimeType="text/plain" />
       </intent-filter>
   </activity>
複製程式碼

這是一個AndroidManifest.xml配置檔案片段,上面的配置說明我們的應用可以接收Action為android.intent.action.SEND的Intent。

接著,在MainActivity中我們實現接收Intent的邏輯,重點需要關注的是這裡從Intent中獲取的資料是如何傳遞給Flutter的:

package com.yourcompany.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.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    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 MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if (methodCall.method.contentEquals("getSharedText")) {
                    result.success(sharedText);
                    sharedText = null;
                }
            }
        });
    }


    void handleSendText(Intent intent) {
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    }
}
複製程式碼

MainActivity從FlutterActivity繼承而來。這裡主要是接收Action為android.intent.action.SEND的Intent,將該Intent中包含的資料提取出來儲存在成員變數sharedText中。關鍵在於onCreate方法中構造了一個MethodChannel例項,構造時需要傳遞一個String引數作為標識,在後面Flutter層需要構造同樣標識的MethodChannel。MethodChannel的作用主要就是為了實現Flutter層與Android層的通訊。關於MethodChannel的更多幫助資訊可以參閱官方文件

最後,在Flutter層我們可以呼叫系統提供的介面請求Android層中儲存的資料sharedText:

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

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

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

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

  @override
  _SampleAppPageState createState() => new _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 new Scaffold(body: new Center(child: new Text(dataShared)));
  }

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

這段程式碼比較簡單,展示一個我們自定義的StatefulWidget在螢幕上,我們主要關注與SampleAppPage對應的_SampleAppPageState類。首先這裡使用與前面相同的標識構造了一個MethodChannel類,相同的標識才能保證這裡的MethodChannel能與之前Android層中定義的MethodChannel通訊。另外定義了一個getSharedText非同步方法,該方法中呼叫MethodChannel例項的invokeMethod方法,最終會回撥到之前MainActivity中定義MethodChannel時傳入的MethodCallHandler類的onMethodCall方法,該方法的methodCall引數封裝Flutter層傳遞到Android層的資訊,result引數用於向Flutter層返回結果資訊。

小結: 接收Intent的配置和處理還是在Android實現,不同的是要在Flutter中處理接收到的Intent所帶的資料時,需要使用MethodChannel類來完成Flutter層與Android層之間的通訊。

在Flutter中startActivityForResult等價於什麼

在Flutter中可以使用Navigator類獲取從當前Route返回到上一個Route時附帶的資料資訊。只需要對push返回的Future物件做一個await操作。關於Future,await,async不太清楚可以參閱官方文件,他們用來在Dart中實現非同步同步功能。

比如我們需要啟動一個位置資訊介面讓使用者選擇他們所處的位置,我們可以寫下面的程式碼:

Map coordinates = await Navigator.of(context).pushNamed('/location');
複製程式碼

此處通過pushNamed方法將螢幕上的當前介面跳轉到了一個位置資訊介面(假設我們已經配置好了/location對應的Route)。同時pushNamed會返回一個Future物件,我們需要將該Future物件作為await的表示式。

然後在我們的顯示位置資訊的介面中當使用者選擇好位置後我們就通過pop當前的Route出棧來實現返回上一個介面的效果,並同時帶上需要返回給上一個介面的資料資訊:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});
複製程式碼

pop的引數就是返回給上一個介面的資料資訊,這裡是一個Map型別的資料。表示位置資訊的Route出棧後,上面的await表示式將被喚醒,並且接收到傳遞過來的Map資料。

小結: 在Flutter中使用Navigator向Route Stack中push一個Route時返回的是一個Future物件,通過await表示式可以實現等待介面返回的效果,並且Navigator從Route Stack中pop一個Route時可以帶上引數,此時帶的引數就會返回給喚醒的await表示式。進而實現類似startActivityForResult中的當前介面返回並傳遞引數給上一個介面的效果。

英文原版傳送

相關文章