Flutter 记录应用授权登录踩坑之旅
最近刚刚入门
Flutter,不写点代码实在记不住新知识,就用Flutter构建了一个原Objective-C项目,来实现它的第三方授权登录。
引入插件
我本次使用的是
ShareSDK,因为OC项目一直用的是ShareSDK,所以就继续维持原平台。具体使用请下载sdk,参考sdk中的example工程。
在pubspec.yaml文件中加入下面依赖
yaml
dependencies:
# MobTech分享
mobcommonlib: ^1.1.7
sharesdk_plugin: ^1.3.17
然后执行:flutter packages get 导入 package
或者
bash
flutter pub add mobcommonlib sharesdk_plugin
在你的 dart工程文件中,导入下面头文件,就可以开始使用了。
dart
import 'package:sharesdk_plugin/sharesdk_plugin.dart';
Tips:不要着急运行项目,
iOS和Android还有配置要添加,如不需要则可以直接跳过。
安装后,iOS端这里是无法运行到模拟器中的

错误表示,微信的SDK无法链接上。ShareSDK使用的xcframework,里面包含了arm64和x86_64指令集编译后的库。

解决办法,找到下图所示设置

可以看到Dart项目中只有i386指令集,而i386指令集早已不适用了,向其中添加arm64指令集。

iOS 端集成配置
iOS 端自定义需要导入的分享平台
在ios目录下,使用Xcode中打开Runner.xcworkspace工程,找到Pods/Development Pods/sharesdk_plugin/Pod的sharesdk_plugin.podspec文件,添加或者删除你需要的dependency。
ruby
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'sharesdk_plugin'
s.version = '1.1.7'
s.summary = 'Flutter plugin for ShareSDK.'
s.description = <<-DESC
ShareSDK is the most comprehensive Social SDK in the world,which share easily with 40+ platforms.
DESC
s.homepage = 'http://www.mob.com/mobService/sharesdk'
s.license = { :file => '../LICENSE' }
s.author = { 'Mob' => 'mobproduct@mob.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.dependency 'mob_sharesdk'
s.dependency 'mob_sharesdk/ShareSDKExtension'
s.dependency 'mob_sharesdk/ShareSDKUI'
s.dependency 'mob_sharesdk/ShareSDKPlatforms/QQ'
s.dependency 'mob_sharesdk/ShareSDKPlatforms/SinaWeibo'
s.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChat'
# s.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChatFull'#微信sdk带支付的命令
s.dependency 'mob_sharesdk/ShareSDKPlatforms/Apple'#苹果登录
s.static_framework = true
s.ios.deployment_target = '8.0'
end
打开命令行工具,cd到ios目录下,通过pod命令来安装iOS依赖包。
bash
cd ios
pod install
配置初始化我们 SDK 的 AppKey
在项目工程的Info.plist中如图增加MOBAppKey 和 MOBAppSecret两个字段

配置对应平台的 URL Scheme
打开项目的 Info 选项,然后选择 URL Types,添加对应平台的 URL Scheme 配置,如下图:

配置白名单
1.在项目的info.plist中添加LSApplicationQueriesSchemes,类型为Array
2.然后给它添加一个需要支持的项目,类型为String类型:

配置 ATS
微博平台还需要加上 ATS 配置:
-
在项目的
info.plist中添加LSApplicationQueriesSchemes,类型为Array。 -
给它添加一个需要支持的项目,类型为
String类型:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

网络强制走 https
如果需要强制,在Info.plist配置MOBForceHttps为YES。优先级高于ATS的配置

配置 Universal Link
新浪微博,微信,QQ,line 等平台在 iOS13 上需要校验 Universal Link,之前在微信,QQ,新浪微博,line 上注册应用需要在配置上 Universal Link,另外项目里也要配置上,可以根据 MOB 后台生成的 Universal Link 去配置:

注意: Team id,Bundle id 这些必须要填写的和自己项目里使用的证书的 Team id 和 Bundle id 一致,QQ AppID 是填写 qq 初始化的 appid,如果需要 QQ 平台,那么需要填写上,把信息都填写之后保存了才可以使用我们生成的 Univesal link
在项目里配置,如下图:

点击 Capability,选择 Associated Domains,并双击添加,如下图:

填写上 Universal Link 配置,填写的格式是 applinks:xxxx

注意 :这个 Universal Link 也可以自己生成,可以参考这个 苹果官方文档,但是为了方便用户,节省用户的时间和精力,建议直接拷贝 MOB 生成的配置。
微信、QQ 通用链接最后的/一定不要移除。
微信、QQ 通用链接最后的/一定不要移除。
微信、QQ 通用链接最后的/一定不要移除。
重要的事情说三遍。
苹果登录(Sign in with Apple)配置
根据 苹果审核 指南:如果 app 专门使用第三方或社交登录服务(例如微信登录,QQ 登录,Facebook 登录,Google 登录,Twitter 登录等)来对其进行设置或验证这个 app 的用户主账户,则该 app 必须同时提供"通过 Apple 登录"作为等效选项,用户的主账户时指在 app 中建立的,用于标识身份,登录和访问功能和相关服务的账户。

与通用链接Associated Domains的添加流程一致。
添加完成后,Runner.entitlements中也会出现相关的配置信息。

Android 端集成配置
Android 端的集成配置全凭 文档 和 demo 结合来实现的,由于没有设备调试,所以正确性暂未验证;如有错误请大家指正。
kts 构建脚本配置
配置 SDK 地址
在项目setting.gradle.kts中添加配置
kotlin
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
//Maven仓地址
maven { url = uri("https://mvn.mob.com/android")}
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
google()
mavenCentral()
//Maven仓地址
maven { url = uri("https://mvn.mob.com/android")}
}
}
配置 Maven
在android项目根目录下的 build.gradle 中添加以下代码:
java
buildscript {
repositories {
// 配置Mob Maven库
maven {
url "https://mvn.mob.com/android"
}
// 配置HMS Core SDK的Maven仓地址。(集成华为厂商需要添加)
// maven {
// url 'https://developer.huawei.com/repo/'}
// }
}
dependencies {
// 集成MobPush
// classpath "cn.fly.sdk:FlySDK:+"
}
}
或(注意文件名后缀)
在android项目根目录下build.gradle.kts中添加配置
kotlin
buildscript {
dependencies {
// 增加MobSDK插件配置
classpath ("com.mob.sdk:MobSDK2:+")
}
}
plugins {
xxxxxx
}
gradle.properties 中添加代码
ini
MobSDK.spEdition = FP
如果您的应用需要上架 Google 商店,请务必使用 Google Play 版本。 在gradle.properties中添加代码,如已添加 MobSDK.spEdition相关配置,则修改FP为 GPP即可
ini
MobSDK.spEdition = GPP
第三方平台添加
在 android根目录下创建MobSDK.gradle文件, 你可以参考 官方 demo中的平台配置信息。
bash
apply plugin: 'com.mob.sdk'
MobSDK {
appKey "moba0b0c0d0"
appSecret "5713f0d88511f9f4cf100cade0610a34"
ShareSDK {
//平台配置信息
devInfo {
SinaWeibo {
id 1
sortId 1
appKey "500068354"
appSecret "0a5f391f938dc371fb44d47a4fa0822e"
callbackUri "http://www.sharesdk.cn"
shareByAppClient true
enable true
}
/* Wechat微信和WechatMoments微信朋友圈的appid是一样的;
注意:开发者不能用我们这两个平台的appid,否则分享不了
微信测试的时候,微信测试需要先签名打包出apk,
sample测试微信,要先签名打包,keystore在sample项目中,密码123456
BypassApproval是绕过审核的标记,设置为true后AppId将被忽略,故不经过
审核的应用也可以执行分享,但是仅限于分享文字和图片,不能分享其他类型,
默认值为false。此外,微信收藏不支持此字段。wx4868b35061f87885
<!--要分享微信小应用程序时用userName,path-->*/
Wechat {
id 2
sortId 2
appId "wx4868b35061f87885"
appSecret "64020361b8ec4c99936c0e3999a9f249"
userName "gh_afb25ac019c9"
path "pages/index/index.html?id=1"
withShareTicket true
miniprogramType 0
bypassApproval false
enable true
}
QQ {
id 3
sortId 3
appId "100371282"
appKey "aed9b0303e3ed1e27bae87c33761161d"
shareByAppClient true
bypassApproval false
enable true
}
}
}
}
配置引入
在 /android/app/build.gradle 中添加以下代码:
java
// 导入MobSDK
apply from: '../MobSDK.gradle'
或
在/android/app/build.gradle.kts添加以下代码:
kotlin
// kts
apply(from = "../MobSDK.gradle")
配置 AndroidManifest
AndroidManifest 中需要加入配置,将其加到application中,避免mobsdk与flutter的application冲突。
xml
tools:replace="android:name"
Flutter 中 API 接口调用
流程图
流程图大致如下:
初始化 MobSDK 注册器
如果需要分享内容到三方平台的话,建议放到main.dart中runApp()之前初始化。
我这里只用到了授权登录,所以就放到了login_controller.dart控制器中onInit()初始化。
dart
@override
void onInit() {
// 初始化MOBShareSDK注册器
_initMobShareSDKRegister();
super.onInit();
}
/// 初始化Mob分享SDK注册器
void _initMobShareSDKRegister() {
ShareSDKRegister register = ShareSDKRegister();
// 微信
register.setupWechat(
AppThirdPlatformKey.wechatAppId,
AppThirdPlatformKey.wechatAppSecret,
AppThirdPlatformKey.wechatUniversalLink,
);
// QQ
register.setupQQ(AppThirdPlatformKey.qqAppId, AppThirdPlatformKey.qqAppKey);
// 新浪微博
register.setupSinaWeibo(
AppThirdPlatformKey.sinaWeiBoAppKey,
AppThirdPlatformKey.sinaWeiBoAppSecret,
AppThirdPlatformKey.sinaWeiBoRedirectUrl,
AppThirdPlatformKey.sinaWeiBoUniversalLink,
);
SharesdkPlugin.regist(register);
//SharesdkPlugin.uploadPrivacyPermissionStatus(0, getPrivacyPolicy);
SharesdkPlugin.addRestoreReceiver(_onMobShareSDKEvent, _onMobShareSDKError);
// 回传用户隐私授权结果
Mobcommonlib.submitPolicyGrantResult(true, null);
}
监听的回调
dart
/// Mob分享SDK监听回调
void _onMobShareSDKEvent(dynamic event) {
debugPrint('>>>>>>>>>>>>>>>>>>>>>>>>>>>');
Map resMapT = event;
Map<String, dynamic> resMap = Map<String, dynamic>.from(resMapT);
// Map<String, dynamic> params = Map<String, dynamic>.from(resMap['params']);
debugPrint('>>>>>>>>>>>>>>>>>>>>>>>>>>>onSuccess:$resMap');
}
/// Mob分享SDK监听错误回调
void _onMobShareSDKError(dynamic event) {
debugPrint('>>>>>>>>>>>>>>>>>>>>>>>>>>>onError:$event');
}
向第三方请求授权
dart
/// MOB 第三方授权登录
void mobThidPartAuthLogin(
/// 平台
ShareSDKPlatform platform,
/// 成功回调
Function(dynamic user) onSuccess,
) {
// 是否安装了客户端
SharesdkPlugin.isClientInstalled(platform).then((dynamic hasClient) {
bool hasInstalled = hasClient as bool;
if (hasInstalled) {
// 安装了客户端
// 请求授权
mobThirdPartApplyAuthRequest(platform, onSuccess);
} else {
// 没有安装客户端
Fluttertoast.showToast(msg: '请先安装客户端');
}
});
}
/// MOB 申请三方授权登录请求
void mobThirdPartApplyAuthRequest(
/// 平台
ShareSDKPlatform platform,
/// 成功回调
Function(dynamic user) onSuccess,
) {
// 请求授权
SharesdkPlugin.auth(platform, {}, (
SSDKResponseState state,
dynamic user,
SSDKError error,
) {
switch (state) {
case SSDKResponseState.Success:
// 登录成功
debugPrint('授权登录成功');
debugPrint('$user');
onSuccess(user);
break;
case SSDKResponseState.Cancel:
// 取消登录
debugPrint('授权登录取消');
Fluttertoast.showToast(msg: '已取消');
break;
default:
// 登录失败
debugPrint('授权登录失败');
Fluttertoast.showToast(msg: '授权登录失败');
debugPrint('${error.rawData}');
break;
}
});
}
获取授权信息
这里是封装了三方平台登录按钮点击,最后通过onSuccess回调的user授权信息,最后与后端沟通保存授权登录信息,一般是credential中的token和uid字段。
dart
/// 三方登录按钮点击事件
void thirdPartyButtonClick(ThirdPartLoginType loginType) {
if (!isAgreement.value) {
Get.snackbar('提示', '请先同意协议');
return;
}
ShareSDKPlatform platform;
switch (loginType) {
case ThirdPartLoginType.Wechat:
platform = ShareSDKPlatforms.wechatSession;
break;
case ThirdPartLoginType.QQ:
platform = ShareSDKPlatforms.qq;
break;
case ThirdPartLoginType.Sina:
platform = ShareSDKPlatforms.sina;
break;
case ThirdPartLoginType.Apple:
platform = ShareSDKPlatforms.apple;
break;
}
// 申请授权
mobThidPartAuthLogin(platform, (user) {
// 授权成功
saveMobThirdPartyAuthLoginInfo(platform, user);
});
}
/// 保存三方授权信息
///
/// [platform] 平台
/// [user] 授权成功的用户相关信息
void saveMobThirdPartyAuthLoginInfo(
/// 平台
ShareSDKPlatform platform,
/// 授权成功的用户相关信息
dynamic user,
) {
Map<String, dynamic> params = {};
if (platform == ShareSDKPlatforms.wechatSession) {
params['accessToken'] = user['credential']['token'];
params['wxOpenId'] = user['credential']['uid'];
params['loginType'] = ThirdPartLoginType.Wechat.value;
} else if (platform == ShareSDKPlatforms.qq) {
params['accessToken'] = user['credential']['token'];
params['loginType'] = ThirdPartLoginType.QQ.value;
} else if (platform == ShareSDKPlatforms.apple) {
params['accessToken'] = user['credential']['token'];
params['loginType'] = ThirdPartLoginType.Apple.value;
} else if (platform == ShareSDKPlatforms.sina) {
params['accessToken'] = user['credential']['token'];
params['loginType'] = ThirdPartLoginType.Sina.value;
}
// 发起请求
LoginDioApis.thirdPartyAuthLogin(
params: params,
onSuccess: (loginResultModel) => _handleLoginSuccess(loginResultModel),
onError: (apiServerException) => _handleLoginError(apiServerException),
);
}
/// 登录成功回调
void _handleLoginSuccess(LoginResultModel loginResultModel) {
// 登录成功
Get.snackbar('登录成功', '欢迎您,${loginResultModel.userInfo?.nickName}');
Get.offAllNamed('/home');
}
/// 登录失败回调
void _handleLoginError(ApiServerException apiServerException) {
Fluttertoast.showToast(msg: '登录失败!');
}