往期回顧
從零開始的Flutter之旅: StatelessWidget
從零開始的Flutter之旅: StatefulWidget
從零開始的Flutter之旅: InheritedWidget
在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
,拼接成下面的連線
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
中新增依賴
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()
來啟動跳轉
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
有關。
const String REDIRECT_URI = 'github://login';
複製程式碼
這裡定義了一個Scheme
,為了能夠成功返回到客戶端指定的頁面,我們需要為Android與IOS配置對應的Scheme。
Android
找到AndroidManifest
檔案,在activity
便籤下新增intent-filter
屬性
<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
。
所以我們先來約定好這兩個值
const String METHOD_CHANNEL_NAME = 'app.channel.shared.data';
const String CALL_LOGIN_CODE = 'getLoginCode';
複製程式碼
然後通過MethodChannel
來獲取對應的渠道
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
首先我們將約定好的渠道名稱與回撥方法名定義為常量
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
與提供回撥的方法。具體實現如下:
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的基本類似
@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_github
flutter_github,這是一個基於Github Open Api開發的Flutter版本的Github客戶端。該專案主要是用來練習Flutter,感興趣的可以加入一起來學習,如果有幫助的話也請不要吝嗇你的關注。
當然如果你想了解Android原生,AwesomeGithub是一個不錯的選擇。它是flutter_github的純Android版本。
如果你喜歡我的文章模式,或者對我接下來的文章感興趣,你可以關注我的微信公眾號:【Android補給站】
或者掃描下方二維碼,與我建立有效的溝通,同時能夠更方便的收到相關的技術推送。