Flutter集成TRUE SDK插件tru_sdk_flutter的使用
Flutter集成TRUE SDK插件tru_sdk_flutter的使用
tru.ID SDK For Flutter
tru.ID SDK 的主要目的是在调用公共 URL 之前强制使用数据蜂窝连接,并将返回以下 JSON 响应。
成功响应
当数据连接已建立并且从 URL 端点接收到响应时:
{
"http_status": string, // 与 URL 相关的 HTTP 状态
"response_body" : { // 根据 HTTP 状态可选
... // 打开的 URL 的响应体
... // 查看 /device_ip 和 /redirect API 文档
},
"debug" : {
"device_info": string,
"url_trace" : string
}
}
错误响应
当数据连接不可用和/或发生内部 SDK 错误时:
{
"error" : string,
"error_description": string
"debug": {
"device_info": string,
"url_trace" : string
}
}
可能的错误代码包括 sdk_no_data_connectivity
、sdk_connection_error
、sdk_redirect_error
和 sdk_error
。
安装
要将 tru_sdk_flutter
包添加到您的项目中:
-
添加依赖项。打开位于应用程序文件夹内的
pubspec.yaml
文件,并在依赖项下添加以下内容:tru_sdk_flutter: ^x.y.z
-
安装依赖项:
-
从终端运行:
flutter pub get
-
在 Android Studio/IntelliJ 中,点击顶部 pubspec.yaml 文件操作栏中的 Packages get。
-
在 VS Code 中,点击顶部 pubspec.yaml 文件操作栏右侧的 Get Packages。
-
兼容性
使用
检查设备是否符合tru.ID静默认证条件
import 'package:tru_sdk_flutter/tru_sdk_flutter.dart';
// ...
// 从后端获取带有覆盖范围范围的访问令牌
var token = ...
// 打开 device_ip 公共 API 端点
TruSdkFlutter sdk = TruSdkFlutter();
try {
Map reach = await sdk.openWithDataCellularAndAccessToken(
"https://eu.api.tru.id/coverage/v0.1/device_ip", token, true);
if (reach.containsKey("error")) {
// 网络连接错误
} else if (reach.containsKey("http_status")) {
if (reach["http_status"] == 200 && reach["response_body"] != null) {
// 设备符合tru.ID条件
Map<dynamic, dynamic> body = reach["response_body"];
Coverage cv = Coverage.fromJson(body);
} else if (reach["status"] == 400 && reach["response_body"] != null) {
// 不支持的移动网络运营商,请查看 ${body.detail}
} else if (reach["status"] == 412 && reach["response_body"] != null) {
// 非移动IP,请查看 ${body.detail}
} else if (reach["response_body"] != null) {
// 其他错误,请查看 ${body.detail}
}
}
}
如何打开由 PhoneCheck API 或 SubscriberCheck API 返回的检查 URL
import 'package:tru_sdk_flutter/tru_sdk_flutter.dart';
// ...
TruSdkFlutter sdk = TruSdkFlutter();
Map result = await sdk.openWithDataCellular(checkUrl, false);
if (result.containsKey("error")) {
// 错误
} else if (result["http_status"] == 200 && result["response_body"] != null) {
if (result["response_body"].containsKey("error")) {
CheckErrorBody errorBody = CheckErrorBody.fromJson(body);
// 错误,请查看 ${body.error_description}
} else {
CheckSuccessBody successBody = CheckSuccessBody.fromJson(body);
// 将 code、check_id 和 reference_id 发送到后端以触发 PATCH /checks/{check_id}
}
} else {
// 其他错误,请查看 ${body.detail}
}
示例Demo
有一个嵌入式的示例演示位于 example
目录中,详见 README。
Meta
本项目根据MIT许可证分发。更多详情请参阅 LICENSE
文件。
示例代码
以下是 example
目录下的 lib/main.dart
文件内容:
/*
* MIT License
* Copyright (C) 2020 4Auth Limited. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import 'dart:convert';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:tru_sdk_flutter/tru_sdk_flutter.dart';
import 'src/http/mock_client.dart';
// 设置本地隧道基础 URL。
final String baseURL = "YOUR_LOCAL_TUNNEL_URL";
void main() {
runApp(PhoneCheckApp());
}
class PhoneCheckApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'tru.ID Phone Check Sample',
theme: ThemeData(),
home: PhoneCheckHome(title: 'tru.ID Flutter Sample App'),
);
}
}
class PhoneCheckHome extends StatefulWidget {
PhoneCheckHome({Key? key, required this.title}) : super(key: key);
final String title;
[@override](/user/override)
_PhoneCheckAppState createState() => _PhoneCheckAppState();
}
class _PhoneCheckAppState extends State<PhoneCheckHome> {
Future<CheckStatus>? _futurePhoneCheck;
String? _result = null;
final _formKey = GlobalKey<FormState>();
String? phoneNumber;
bool agreedToTerms = false;
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'tru.ID Sample App',
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('tru.ID Flutter Sample App'),
),
body: bodyContainer(),
),
);
}
Container bodyContainer() {
return Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: (_futurePhoneCheck == null) ? bodyForm() : buildFutureBuilder(),
);
}
Form bodyForm() {
return Form(
key: _formKey,
child: Scrollbar(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
logo(),
const SizedBox(height: 24),
validatingTextField(),
const SizedBox(height: 24),
validatingFormField(),
const SizedBox(height: 24),
verifyButton(),
Text((_result == null) ? "" : "Results $_result")
],
),
),
),
);
}
FutureBuilder<CheckStatus> buildFutureBuilder() {
return FutureBuilder<CheckStatus>(
future: _futurePhoneCheck,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
_result = 'Match status: ${snapshot.data!.match}';
} else if (snapshot.hasError) {
_result = 'Error:${snapshot.error}';
}
return bodyForm();
} else if (snapshot.connectionState == ConnectionState.active ||
snapshot.connectionState == ConnectionState.waiting ||
snapshot.connectionState == ConnectionState.none) {
print("-->");
}
return CircularProgressIndicator();
},
);
}
Widget logo() {
return Center(
child: Image.asset('assets/images/1024.png',
width: 175.0, height: 100.0, fit: BoxFit.cover),
);
}
// 一个验证电话号码的文本字段。
TextFormField validatingTextField() {
return TextFormField(
// autofocus: true,
initialValue: (phoneNumber == null) ? null : phoneNumber,
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
validator: (value) {
if (value!.isEmpty) {
return '请输入电话号码。';
}
RegExp exp = RegExp(r"^(?:[+0][1-9])?[0-9]{10,12}$");
if (exp.hasMatch(value)) {
return null;
}
return '无效的电话号码';
},
decoration: const InputDecoration(
filled: true,
hintText: '例如:+447830305594',
labelText: '输入电话号码',
),
onChanged: (value) {
phoneNumber = value;
},
);
}
// 一个自定义表单字段,要求用户勾选复选框。
FormField<bool> validatingFormField() {
return FormField<bool>(
initialValue: agreedToTerms,
validator: (value) {
if (value == false) {
return '必须同意服务条款。';
}
return null;
},
builder: (formFieldState) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Checkbox(
value: agreedToTerms,
onChanged: (value) {
// 当复选框值发生变化时,
// 更新 FormFieldState 以便重新验证表单。
formFieldState.didChange(value);
setState(() {
agreedToTerms = value!;
});
},
),
Text(
'我同意服务条款。',
style: Theme.of(context).textTheme.subtitle1,
),
],
),
if (!formFieldState.isValid)
Text(
formFieldState.errorText ?? "",
style: Theme.of(context)
.textTheme
.caption!
.copyWith(color: Theme.of(context).errorColor),
),
],
);
},
);
}
Widget verifyButton() {
return TextButton(
child: const Text('验证我的电话号码'),
style: TextButton.styleFrom(
primary: Colors.white,
backgroundColor: Colors.blue,
),
onPressed: () {
// 通过从 GlobalKey 获取 FormState 并调用 validate() 来验证表单。
var valid = _formKey.currentState!.validate();
if (!valid) {
return;
}
if (phoneNumber != null) {
FocusScope.of(context).unfocus();
setState(() {
_futurePhoneCheck = executeFlow(phoneNumber!);
});
}
},
);
}
// 获取覆盖范围访问令牌
Future<TokenResponse> getCoverageAccessToken() async {
final response = await http.get(
Uri.parse('$baseURL/coverage-access-token'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
return TokenResponse.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to get coverage access token: No access token');
}
}
// 平台消息是异步的,因此我们初始化在一个异步方法中。
Future<CheckStatus> executeFlow(String phoneNumber) async {
print("[Reachability] - Start");
var canMoveToNextStep = false;
var tokenResponse = await getCoverageAccessToken();
var token = tokenResponse.token;
TruSdkFlutter sdk = TruSdkFlutter();
try {
Map reach = await sdk.openWithDataCellularAndAccessToken(
"https://eu.api.tru.id/coverage/v0.1/device_ip", token, true);
print("isReachable = $reach");
if (reach.containsKey("error")) {
throw Exception(
'Status = ${reach["error"]} - ${reach["error_description"]}');
} else if (reach.containsKey("http_status")) {
if (reach["http_status"] == 200 && reach["response_body"] != null) {
Map<dynamic, dynamic> body = reach["response_body"];
Coverage cv = Coverage.fromJson(body);
print("body ${cv.networkName}");
if (cv.products != null) print("product ${cv.products![0]}");
//一切正常
canMoveToNextStep = true;
} else if (reach["http_status"] == 400 && reach["response_body"] != null) {
ApiError body = ApiError.fromJson(reach["response_body"]);
print("[Is Reachable 400] ${body.detail}");
} else if (reach["http_status"] == 412 && reach["response_body"] != null) {
ApiError body = ApiError.fromJson(reach["response_body"]);
print("[Is Reachable 412] ${body.detail}");
} else if (reach["response_body"] != null) {
ApiError body = ApiError.fromJson(reach["response_body"]);
print("[Is Reachable ] ${body.detail}");
}
}
if (canMoveToNextStep) {
print("Moving on with Creating PhoneCheck...");
final response = await http.post(
Uri.parse('$baseURL/v0.2/phone-check'),
headers: <String, String>{
'content-type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'phone_number': phoneNumber,
}),
);
print("[PhoneCheck] - Received response");
if (response.statusCode == 200) {
PhoneCheck checkDetails =
PhoneCheck.fromJson(jsonDecode(response.body));
Map result = await sdk.openWithDataCellular(checkDetails.url, false);
print("openWithDataCellular Results -> $result");
if (result.containsKey("error")) {
print(result);
throw Exception('Error in openWithDataCellular: $result');
} else if (result["http_status"] == 200 && result["response_body"] != null) {
if (result["response_body"].containsKey("error")) {
CheckErrorBody body = CheckErrorBody.fromJson(result["response_body"]);
print("CheckErrorBody: ${body.description}");
throw Exception('openWithDataCellular error ${body.description}');
} else {
CheckSuccessBody body = CheckSuccessBody.fromJson(result["response_body"]);
print('CheckSuccessBody $body');
try {
return exchangeCode(body.checkId, body.code, body.referenceId);
} catch (error) {
print("Error retrieving check result $error");
throw Exception("Error retrieving check result");
}
}
} else {
ApiError body = ApiError.fromJson(result["response_body"]);
print("ApiError ${body.detail}");
throw Exception("Error: ${body.detail}");
}
} else {
throw Exception('Failed to create phone check');
}
} else {
print("isReachable parsing error");
throw Exception('reachability failed');
}
} on PlatformException catch (e) {
print("isReachable Error: ${e.toString()}");
throw Exception('reachability failed');
}
}
}
Future<CheckStatus> exchangeCode(
String checkID, String code, String? referenceID) async {
var body = jsonEncode(<String, String>{
'code': code,
'check_id': checkID,
'reference_id': (referenceID != null) ? referenceID : ""
});
final response = await http.post(
Uri.parse('$baseURL/v0.2/phone-check/exchange-code'),
body: body,
headers: <String, String>{
'content-type': 'application/json; charset=UTF-8',
},
);
print("response request ${response.request}");
if (response.statusCode == 200) {
CheckStatus exchangeCheckRes =
CheckStatus.fromJson(jsonDecode(response.body));
print("Exchange Check Result $exchangeCheckRes");
if (exchangeCheckRes.match) {
print("✅ successful PhoneCheck match");
} else {
print("❌ failed PhoneCheck match");
}
return exchangeCheckRes;
} else {
throw Exception('Failed to exchange Code');
}
}
class TokenResponse {
final String token;
final String url;
TokenResponse({required this.token, required this.url});
factory TokenResponse.fromJson(Map<dynamic, dynamic> json) {
return TokenResponse(
token: json['token'],
url: json['url']
);
}
}
class PhoneCheck {
final String id;
final String url;
PhoneCheck({required this.id, required this.url});
factory PhoneCheck.fromJson(Map<dynamic, dynamic> json) {
return PhoneCheck(
id: json['check_id'],
url: json['check_url'],
);
}
}
class CheckStatus {
final String id;
bool match = false;
CheckStatus({required this.id, required this.match});
factory CheckStatus.fromJson(Map<dynamic, dynamic> json) {
return CheckStatus(
id: json['check_id'],
match: json['match'] == null ? false : json['match'],
);
}
}
// 设置一个模拟 HTTP 客户端。
final http.Client httpClient = MockClient();
Future<String> getMockData() {
return Future.delayed(Duration(seconds: 2), () {
return "PhoneCheck Mock data";
// throw Exception("Custom Error");
});
}
Future<String> asyncMockData() async {
await Future.delayed(Duration(seconds: 10));
return Future.value("PhoneCheck Mock data");
}
更多关于Flutter集成TRUE SDK插件tru_sdk_flutter的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter集成TRUE SDK插件tru_sdk_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中集成并使用TRUE SDK插件tru_sdk_flutter
的示例代码。假设你已经具备基本的Flutter开发环境配置,并且已经有一个正在开发的Flutter项目。
1. 添加依赖
首先,在你的pubspec.yaml
文件中添加tru_sdk_flutter
依赖:
dependencies:
flutter:
sdk: flutter
tru_sdk_flutter: ^最新版本号 # 请替换为实际的最新版本号
然后运行flutter pub get
来安装依赖。
2. 配置iOS和Android
iOS
对于iOS,你可能需要在Info.plist
中添加一些必要的配置,比如权限请求等,具体取决于TRUE SDK的要求。此外,确保你的Podfile
配置正确,并且运行pod install
。
Android
对于Android,你可能需要在AndroidManifest.xml
中添加一些权限声明。同样,具体权限取决于TRUE SDK的要求。
3. 初始化TRUE SDK
在你的Flutter应用的入口文件(通常是main.dart
)中,初始化TRUE SDK。以下是一个示例代码:
import 'package:flutter/material.dart';
import 'package:tru_sdk_flutter/tru_sdk_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// 初始化TRUE SDK
_initializeTRUESDK();
}
Future<void> _initializeTRUESDK() async {
try {
// 假设TRUE SDK需要一些初始化参数,这里以示例参数为准
final String apiKey = "你的API_KEY";
final String environment = "production"; // 或者 "sandbox"
// 初始化TRUE SDK
await TrueSDK.initialize(apiKey: apiKey, environment: environment);
// 初始化成功后,可以进行其他操作,比如用户登录等
print("TRUE SDK initialized successfully.");
} catch (e) {
print("Error initializing TRUE SDK: $e");
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('TRUE SDK Flutter Example'),
),
body: Center(
child: Text('TRUE SDK is initializing...'),
),
),
);
}
}
4. 使用TRUE SDK功能
一旦TRUE SDK初始化成功,你就可以使用它提供的功能了。以下是一个假设的用户登录示例:
Future<void> _userLogin() async {
try {
// 假设TRUE SDK的登录方法需要一些参数,这里以示例参数为准
final String phoneNumber = "+1234567890";
final String password = "userpassword";
// 执行用户登录
final loginResult = await TrueSDK.userLogin(phoneNumber: phoneNumber, password: password);
if (loginResult.isSuccess) {
print("User logged in successfully.");
// 登录成功后,可以获取用户信息,进行后续操作等
} else {
print("User login failed: ${loginResult.errorMessage}");
}
} catch (e) {
print("Error during user login: $e");
}
}
你可以在按钮点击事件或其他适当的时机调用_userLogin
方法。
注意事项
- 请确保你使用的TRUE SDK版本与Flutter SDK版本兼容。
- 仔细阅读TRUE SDK的官方文档,了解所有可用方法和参数。
- 根据TRUE SDK的要求,处理用户隐私和数据安全问题。
这个示例代码提供了一个基本的框架,你可能需要根据TRUE SDK的实际API和文档进行调整和扩展。