盒子
盒子
文章目录
  1. 往期回顾
  2. OAuth App
  3. url_launcher
  4. Scheme
    1. Android
    2. IOS
  5. MethodChannel
    1. Android
    2. IOS
  6. 推荐项目

从零开始的Flutter之旅: MethodChannel

往期回顾

从零开始的Flutter之旅: StatelessWidget
从零开始的Flutter之旅: StatefulWidget
从零开始的Flutter之旅: InheritedWidget
从零开始的Flutter之旅: Provider
从零开始的Flutter之旅: Navigator

flutter_github有这么一个场景:通过authorization认证方式进行登录。而authorization的具体登录形式是,通过跳转一个网页链接进行github授权登录,成功之后会携带对应的code到指定客户端中,然后客户端可以通过这个code来进行oauth授权登录,成功之后客户端可以拿到该账户的token,所以之后的github操作都可以通过该token来进行请求。由于token是有时效性,同时也可以手动解除授权,所以相对于在客户端进行账户密码登录来说更加安全。

那么要实现上面这个场景,Flutter就需要与原生客户端进行通信,拿到返回的code,然后再到Flutter中进行oauth授权登录请求。

通信方式可以使用MethodChannel,这个就是今天的主题。

OAuth App

authorization认证的原理已经知道了,下面直接来看实现方案。

首先我们需要一个OAuth App用来提供用户通过github授权的应用。

这个在github上可以直接注册的

在注册的OAuth App时会有一个Authorization callback URL必填项。这个callback url的作用就是当你通过该链接认证通过后会以App Link的方式使用该url跳转到对应的App应用,同时返回认证成功的code。这里将其定义为REDIRECT_URI

注册成功之后,我们拿到它的Client ID、Client Secret与Authorization callback URL,拼接成下面的连接

1
2
const String URL_AUTHORIZATION =
'https://github.com/login/oauth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=user%20repo%20notifications%20';

有了跳转到外部的认证链接之后,下面就是在应用中实现这个跳转认证流程。

url_launcher

首先需要跳转外部浏览器访问上面的authorization链接。这一步的实现需要借助url_launcher,它能够帮助我们检查链接是否有效,同时启动外部浏览器进行跳转。

在使用之前需要在pubspec.yaml中添加依赖

1
2
3
4
5
6
7
8
dependencies:
flutter:
sdk: flutter
http: 0.12.0+4
dio: 3.0.7
shared_preferences: 0.5.6+1
url_launcher: 5.4.1
...

依赖成功之后,使用canLaunch()来检查链接的有效性;launch()来启动跳转

1
2
3
4
5
6
7
8
9
10
11
12
authorization() {
return () async {
FocusScope.of(context).requestFocus(FocusNode());
if (await canLaunch(URL_AUTHORIZATION)) {
// 为设置forceSafariVC,IOS 默认会打开APP内部WebView
// 而APP内部WebView不支持重定向跳转到APP
await launch(URL_AUTHORIZATION, forceSafariVC: false);
} else {
throw 'Can not launch $URL_AUTHORIZATION)';
}
};
}

Scheme

通过authorization()方法可以成功跳转到外部浏览器进行登录授认证。授权成功之后会返回到之前的app,具体页面路径与链接中配置的REDIRECT_URI有关。

1
const String REDIRECT_URI = 'github://login';

这里定义了一个Scheme,为了能够成功返回到客户端指定的页面,我们需要为Android与IOS配置对应的Scheme。

Android

找到AndroidManifest文件,在activity便签下添加intent-filter属性

1
2
3
4
5
6
7
8
9
10
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="login"
android:scheme="github" />
</intent-filter>

前面的action与category配置是固定的,如果需要支持不同的scheme,主要修改的是data中的配置。

将scheme与host分别对应到REDIRECT_URI中的数值。

IOS

找到info.plist文件,添加URL types便签,在它的item下配置对应的URL identifier与URL Schemes

配置完scheme之后,就能够正常返回到对应的客户端页面。

接下来需要考虑的是,如何拿到返回的code值

MethodChannel

这个时候今天的主角就该上场了。

MethodChannel简单的说就是Flutter提供与客户端通信的渠道,使用时互相约定一个渠道name与对应的调用客户端指定方法的method。

所以我们先来约定好这两个值

1
2
const String METHOD_CHANNEL_NAME = 'app.channel.shared.data';
const String CALL_LOGIN_CODE = 'getLoginCode';

然后通过MethodChannel来获取对应的渠道

1
2
3
4
5
6
7
8
9
callLoginCode(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) {
final platform = const MethodChannel(METHOD_CHANNEL_NAME);
final code = await platform.invokeMethod(CALL_LOGIN_CODE);
if (code != null) {
_getAccessTokenFromCode(code);
}
}
}

使用invokeMethod来调用客户端对应的方法,这里是用来获取授权成功后返回客户端的code。

这是Flutter调用客户端方法的步骤,下面再看客户端的实现

Android

首先我们将约定好的渠道名称与回调方法名定义为常量

1
2
3
4
5
object Constants {
const val AUTHORIZATION_CODE = "code"
const val METHOD_CHANNEL_NAME = "app.channel.shared.data"
const val CALL_LOGIN_CODE = "getLoginCode"
}

在之前我们已经在AndroidManifest.xml中定义的scheme,所以认证成功后回返回客户端的MainActivity页面,同时回调onNewIntent方法。

所以获取返回code的方式可以在onNewIntent中进行,同时还需要建立对应的MethodChannel与提供回调的方法。具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MainActivity : FlutterActivity() {

private var mAuthorizationCode: String? = null

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
setupMethodChannel()
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
getExtra(intent)
}

private fun getExtra(intent: Intent?) {
// from author login
mAuthorizationCode = intent?.data?.getQueryParameter(Constants.AUTHORIZATION_CODE)
}

private fun setupMethodChannel() {
MethodChannel(flutterEngine?.dartExecutor, Constants.METHOD_CHANNEL_NAME).setMethodCallHandler { call, result ->
if (call.method == Constants.CALL_LOGIN_CODE && !TextUtils.isEmpty(mAuthorizationCode)) {
result.success(mAuthorizationCode)
mAuthorizationCode = null
}
}
}
}

MethodChannel建立渠道,setMethodCallHandler来响应Flutter中需要调用的方法。通过判断回调的方法名称,即之前在Flutter中约定的CALL_LOGIN_CODE。来执行对应的逻辑

因为我们需要返回的code值,只需通过result的success方法,将获取到的code传递过去即可。之后Flutter就能够获取到该值。

IOS

在AppDelegate.swift中定义一个methodChannel,使用约定好的name。

methodChannel的创建IOS是通过FlutterMethodChannel.init来生成。之后的回调与Android的基本类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var paramsMap: Dictionary<String, String> = [:]
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel.init(name: "app.channel.shared.data", binaryMessenger: controller.binaryMessenger)

methodChannel.setMethodCallHandler { (call, result) in
if "getLoginCode" == call.method && !self.paramsMap.isEmpty {
result(self.paramsMap["code"])
self.paramsMap.removeAll()
}
}

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let absoluteString = url.absoluteURL.absoluteString
let urlComponents = NSURLComponents(string: absoluteString)
let queryItems = urlComponents?.queryItems
for item in queryItems! {
paramsMap[item.name] = item.value
}
return true
}
}

在setMethodCallHandler中判断回调的方法是否与约定的方法名一致,如果一致再通过result方法将code传递给Flutter。

至此Android与IOS都与Flutter建立了通信,它们之间的桥梁就是通过MethodChannel来搭建的。

最后code传回到Flutter之后,我们再将code进行请求获取到对应的token。

到这里整个授权认证就完成了,之后我们就可以通过token来请求用户相关的接口,获取对应的数据。

token的获取与相关接口的调用可以通过查看flutter_github源码获取

推荐项目

下面介绍一个完整的Flutter项目,对于新手来说是个不错的入门。

flutter_github,这是一个基于Flutter的Github客户端同时支持Android与IOS,支持账户密码与认证登陆。使用dart语言进行开发,项目架构是基于Model/State/ViewModel的MSVM;使用Navigator进行页面的跳转;网络框架使用了dio。项目正在持续更新中,感兴趣的可以关注一下。

当然如果你想了解Android原生,相信flutter_github的纯Android版本AwesomeGithub是一个不错的选择。

如果你喜欢我的文章模式,或者对我接下来的文章感兴趣,建议您关注我的微信公众号:【Android补给站】

或者扫描下方二维码,与我建立有效的沟通,同时更快更准的收到我的更新推送。

支持一下
赞赏是一门艺术