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_connectivitysdk_connection_errorsdk_redirect_errorsdk_error

安装

要将 tru_sdk_flutter 包添加到您的项目中:

  1. 添加依赖项。打开位于应用程序文件夹内的 pubspec.yaml 文件,并在依赖项下添加以下内容:

    tru_sdk_flutter: ^x.y.z
    
  2. 安装依赖项:

    • 从终端运行:

      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

1 回复

更多关于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和文档进行调整和扩展。

回到顶部