从零开始的Flutter之旅: MethodChannel

2020_07_09_18_50.jpeg

往期回顾

从零开始的Flutter之旅: StatelessWidgetandroid

从零开始的Flutter之旅: StatefulWidgetgit

从零开始的Flutter之旅: InheritedWidgetgithub

从零开始的Flutter之旅: Providerswift

从零开始的Flutter之旅: Navigatorsegmentfault

flutter_github有这么一个场景:经过authorization认证方式进行登陆。而authorization的具体登陆形式是,经过跳转一个网页连接进行github受权登陆,成功以后会携带对应的code到指定客户端中,而后客户端能够经过这个code来进行oauth受权登陆,成功以后客户端能够拿到该帐户的token,因此以后的github操做均可以经过该token来进行请求。因为token是有时效性,同时也能够手动解除受权,因此相对于在客户端进行帐户密码登陆来讲更加安全。浏览器

method_channel.gif

那么要实现上面这个场景,Flutter就须要与原生客户端进行通讯,拿到返回的code,而后再到Flutter中进行oauth受权登陆请求。安全

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

OAuth App

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

首先咱们须要一个OAuth App用来提供用户经过github受权的应用。less

这个在github上能够直接注册的

method_channel_1.png

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

注册成功以后,咱们拿到它的Client IDClient SecretAuthorization 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>

前面的actioncategory配置是固定的,若是须要支持不一样的scheme,主要修改的是data中的配置。

schemehost分别对应到REDIRECT_URI中的数值。

IOS

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

method_channel_2.png

配置完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值,只需经过resultsuccess方法,将获取到的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,感兴趣的能够加入一块儿来学习,若是有帮助的话也请不要吝啬你的关注。

flutter_github_preview.png

固然若是你想了解Android原生,AwesomeGithub是一个不错的选择。它是flutter_github的纯Android版本。

若是你喜欢个人文章模式,或者对我接下来的文章感兴趣,你能够关注个人微信公众号:【Android补给站】

或者扫描下方二维码,与我创建有效的沟通,同时可以更方便的收到相关的技术推送。

Android补给站.jpg

相关文章
相关标签/搜索