Review past

The Journey of Flutter from scratch: StatelessWidget

The Journey of Flutter from Scratch: StatefulWidget

A Journey to Flutter from scratch: InheritedWidget

The Journey to Flutter from scratch: Provider

A journey to Flutter from scratch: Navigator

In Flutter_Github, there is a scenario for logging in through authorization authentication. The specific form of authorization login is to jump to a webpage link for Github authorization login. After successful login, the corresponding code will be carried to the specified client, and then the client can use this code for OAUth authorization login. After successful login, the client can get the token of the account. All subsequent Github operations can be requested using this token. The token is time-sensitive and can be manually disauthorized. Therefore, it is more secure than the password login from the client.

To implement this scenario, a Flutter needs to communicate with the native client, get the returned code, and then go to Flutter to make an Oauth authorized login request.

You can use method Channels for communication, and that’s the topic for today.

OAuth App

The principle of authorization authentication has been known, and the implementation scheme is shown below.

First we need an OAuth App to provide users with github authorized applications.

You can sign up directly on Github

There will be a mandatory Authorization Callback URL for the registered OAuth App. The function of this callback URL is that after you pass the authentication through this Link, you will use this URL to jump to the corresponding App in the form of App Link and return the code with successful authentication. It is defined here as REDIRECT_URI

After successful registration, we get its Client ID, Client Secret, and Authorization Callback URL, and splice them into the following connection

const String URL_AUTHORIZATION =
    'https://github.com/login/oauth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=user%20repo%20notificati ons%20';
Copy the code

With a jump to external authentication link, here’s how to implement the jump to external authentication in your application.

url_launcher

The first step is to redirect the external browser to access the authorization link above. We do this with the url_launcher, which helps us check whether the link is working and launch an external browser to jump to it.

You need to add the dependency in pubspec.yaml before using it

Dependencies: flutter HTTP: 0.12.0+4 dio: 3.0.7 shared_preferences: 0.5.6+1 URl_launcher: 5.4.1...Copy the code

After the dependency succeeds, use canLaunch() to check the validity of the link; Launch () to launch the jump

  authorization() {
    return () async {
      FocusScope.of(context).requestFocus(FocusNode());
      if(await canLaunch(URL_AUTHORIZATION)) {// Set forceSafariVC, By default, IOS will open internal APP WebView // and the internal APP WebView does not support redirection to APP await launch(URL_AUTHORIZATION, forceSafariVC:false);
      } else {
        throw 'Can not launch $URL_AUTHORIZATION)'; }}; }Copy the code

Scheme

The authorization() method can be used to successfully switch to the external browser for login authentication. After successful authorization, it will return to the previous app. The specific page path depends on the REDIRECT_URI configured in the link.

const String REDIRECT_URI = 'github://login';
Copy the code

Here we define a Scheme, and in order to successfully return to the page specified by the client, we need to configure the corresponding Scheme for Android and IOS.

Android

Go to the AndroidManifest file and add the intent-filter attribute to the Activity TAB

<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>
Copy the code

The previous action and category configurations are fixed. If you need to support different schemes, you need to change the configuration in data.

Map Scheme and host to the value in REDIRECT_URI, respectively.

IOS

Go to the info.plist file and add the URL Types TAB and configure the corresponding URL identifier and URL Schemes under its item

After scheme is configured, you can return to the corresponding client page.

The next thing you need to think about is, how do you get the code that’s returned

MethodChannel

That’s when the hero of the day comes on.

MethodChannel simply means that a Flutter provides a channel to communicate with a client. When used, they agree on a channel name and the corresponding method that calls the client-specified method.

So let’s just settle on these two values

const String METHOD_CHANNEL_NAME = 'app.channel.shared.data';
const String CALL_LOGIN_CODE = 'getLoginCode';
Copy the code

We then use MethodChannel to get the corresponding channel

  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); }}}Copy the code

Use invokeMethod to call the corresponding method on the client. In this case, the code is used to get the authorization and return to the client.

This is how Flutter calls client methods. Let’s look at the client implementation

Android

First we define the agreed channel name and callback method name as constants

object Constants {
    const val AUTHORIZATION_CODE = "code"
    const val METHOD_CHANNEL_NAME = "app.channel.shared.data"
    const val CALL_LOGIN_CODE = "getLoginCode"
}
Copy the code

We have already defined scheme in androidmanifest.xml before, so after successful authentication, we return to the MainActivity page of the client and call back to the onNewIntent method.

So getting the code returned can be done in onNewIntent, and you need to set up the corresponding MethodChannel and the method that provides the callback. The concrete implementation is as follows:

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 funsetupMethodChannel() { 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 } } } }Copy the code

Methodchannels set up channels and setMethodCallHandler to respond to methods that need to be called in a Flutter. Determine the method name of the callback, that is, the CALL_LOGIN_CODE previously agreed on Flutter. To execute the corresponding logic

Because we need to return the code value, we just need to pass the obtained code through the success method of result. This value can then be picked up by Flutter.

IOS

Define a methodChannel in Appdelegate. swift, using the convention name.

MethodChannel create IOS is through FlutterMethodChannel. The init to generate. The subsequent callbacks are basically similar to Android’s

@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)
     
    letcontroller: FlutterViewController = window? .rootViewController as! FlutterViewControllerlet 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)
        letqueryItems = urlComponents? .queryItemsfor item in queryItems! {
            paramsMap[item.name] = item.value
        }
        return true}}Copy the code

In setMethodCallHandler, determine if the callback method matches the convention method name. If so, pass code to Flutter via the Result method.

Both Android and IOS now communicate with Flutter. The bridge between them is built through the MethodChannel.

Finally, after the code is transmitted back to Flutter, we request the code to obtain the corresponding token.

At this point, the entire authorization and authentication is completed, and then we can request the user-related interface through the token to obtain the corresponding data.

To obtain the token and call the related interface, check the source code of Flutter_github

flutter_github

Flutter_github is a Github client for Flutter based on the Github Open Api. This project is mainly used to practice Flutter. If you are interested in Flutter, please join in and learn it. If it is helpful, please do not hesitate to pay attention to Flutter.

Of course, if you want to learn about Android native, AwesomeGithub is a good choice. It is a pure Android version of Flutter_Github.

If you like my article mode, or are interested in my next article, you can follow my wechat official account: [Android supply station]

Or scan the qr code below to establish effective communication with me and receive relevant technical push more conveniently.