Flutter软件开发工具包快速入门

云眼About 15 min

Flutter软件开发工具包快速入门

欢迎来到 云眼灰度发布(特性标帜) 的 Flutter SDK 的快速入门指南。

按照本指南中的步骤创建灰度发布(特性标帜)、推出标帜传递,并使用简单的命令行应用程序运行 A/B 测试。

第一部分:创建示例应用

1. 访问或创建帐户

需要一个 云眼 帐户才能遵循本指南。如果没有帐户,可以注册一个免费帐户open in new window。如果已有帐户,请导航到已启用标帜的项目。

2. 获取开发工具包密钥

要在云眼项目中查找开发工具包密钥,请执行以下操作:

  1. 转到“设置”>“主环境”。
  2. 复制并保存主环境的 SDK 密钥

📘 注意

每个环境都有自己的开发工具包密钥。

点击图片放大
sdk_key.png

3. 复制示例代码

要使用 SDK,请执行以下操作:

  1. Android Studioopen in new window 中安装 Flutter 插件。
  2. 在 Android Studio 中,创建一个名为 的新 Flutter 项目(File -> New -> New Flutter Project...)。eyeofcloud_flutter_quickstart
  3. 选择 Java for Android 语言。选择 Swift for iOS 语言。
  4. 为平台切换 AndroidiOS
  1. 单击完成
  2. 安装云眼灰度发布(特性标帜)AB实验Flutter SDK。打开 pubspec.yaml 文件并添加:

Pubspec.yaml

dependencies: eyeofcloud_flutter_sdk: ^1.0.0-beta

📘 注意

Flutter SDK目前处于公测阶段。

它已经过全面测试,但如果发现任何错误,请使用本文档右上角的**“建议编辑”**或联系客户成功经理。

完整的源代码可在GitHubopen in new window上找到。

  1. 将以下代码示例复制到应用的 main.dart 文件(位于 lib 目录中)。替换为在上一步中找到的 SDK 密钥。 <Your_SDK_Key>

Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:eyeofcloud_flutter_sdk/eyeofcloud_flutter_sdk.dart';
import 'dart:async';

// NOTE: You need to change this SDK key to your project's SDK Key
const String sdkKey = "Your_SDK_Key";
const String logTag = "EYEOFCLOUD_QUICK_START";

void main() {
  runApp(const MyApp());
  var productSorter = ProductSorter();
  productSorter.initializeQuickStart();
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String uiResponse = 'Unknown';

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Quick start example app'),
        ),
        body: Center(
          child: Text(uiResponse),
        ),
      ),
    );
  }
}

class ProductSorter {
  String _datafileHost = "https://cdn.eyeofcloud.com";
  String _datafileSuffixAndroid = "/datafiles/%s.json";
  String _datafileSuffixIOS = "/datafiles/%@.json";

  Future<void> initializeQuickStart() async {
    // Setting up custom datafile URL format
    Map<ClientPlatform, DatafileHostOptions> datafileHostOptions = {};

    datafileHostOptions[ClientPlatform.android] =
        DatafileHostOptions(_datafileHost, _datafileSuffixAndroid);

    datafileHostOptions[ClientPlatform.iOS] =
        DatafileHostOptions(_datafileHost, _datafileSuffixIOS);

    // Initializing EyeofcloudClient
    var flutterSDK = EyeofcloudFlutterSdk(
        sdkKey, datafileHostOptions: datafileHostOptions);
    var response = await flutterSDK.initializeClient();
    if (response.success) {
      flutterSDK.addConfigUpdateNotificationListener((msg) {
        runQuickStart(flutterSDK);
      });
      await runQuickStart(flutterSDK);
    }
  }

  Future<void> runQuickStart(EyeofcloudFlutterSdk flutterSDK) async {
    /* --------------------------------
     * to get rapid demo results, generate random users. Each user always sees the same variation unless you reconfigure the flag rule.
     * --------------------------------
     */
    // simulate 50 users
    var rangeMax = 9999;
    var rangeMin = 1000;
    var range = Random();
    var hasOnFlags = false;

    for (int i = 0; i < 10; i++) {
      var userId = "${range.nextInt(rangeMax - rangeMin) + rangeMin}";
      /* --------------------------------
       Create hardcoded user & bucket user into a flag variation
       --------------------------------
      */
      var user = await flutterSDK.createUserContext(userId);
      // "product_sort" corresponds to a flag key in your Eyeofcloud project
      var decisionResponse = await user!.decide("product_sort");
      var decision = decisionResponse.decision;
      // did decision fail with a critical error?
      if (decision?.variationKey == null) {
        print("\n\n$logTag decision error: ${decisionResponse.reason}");
      }
      // get a dynamic configuration variable
      // "sort_method" corresponds to a variable key in your Eyeofcloud project
      String sortMethod = decision?.variables["sort_method"] as String;

      if (decision!.enabled) {
        // Keep count how many visitors had the flag enabled
        hasOnFlags = true;
      }
      /* --------------------------------
         Mock what the users sees with print statements (in production, use flag variables to implement feature configuration)
         --------------------------------
      */
      var userIdResponse = await user.getUserId();
      // always returns false until you enable a flag rule in your Eyeofcloud project
      print("\n\n$logTag Flag ${(decision.enabled
          ? "on"
          : "off")}. User number ${userIdResponse.userId} saw flag variation: ${decision
          .variationKey} and got products sorted by: ${sortMethod} config variable as part of flag rule: ${decision
          .ruleKey}");
    }
    if (!hasOnFlags) {
      print(
          "\n\n$logTag Flag was off for everyone. Some reasons could include:" +
              "\n1. Your sample size of visitors was too small. Rerun, or increase the iterations in the FOR loop" +
              "\n2. By default you have 2 keys for 2 project environments (dev/prod). Verify in Settings>Environments that you used the right key for the environment where your flag is toggled to ON." +
              "\nCheck your key at  https://app.eyeofcloud.com/v2/projects/YOUR_PROJECT_ID/settings/implementation");
    }
  }
}

📘 注意

暂时不要运行你的应用。仍然需要在 云眼 应用程序中设置标帜和标帜传递规则。

第二部分:运行应用

此时,你的应用不执行任何操作。您首先需要在 云眼 应用程序中创建标帜和标帜传递规则以启用该应用程序。

1. 创建灰度发布(特性标帜)

通过灰度发布(特性标帜),可以控制要在应用中向哪些用户公开新功能代码。对于本快速入门,假设你正在推出重新设计的排序功能来显示产品。

在 云眼 中创建一个名为 product_sort 的标帜,并为其提供一个名为 sort_method 的变量:

  1. 转到标帜>创建标帜
  2. 将标帜键命名为_product_sort_,然后单击**“创建标帜**”,该标帜对应于示例应用中的标帜键。
  3. 转到默认变量,然后单击新建 (+)。
  4. 将变量类型设置为字符串
  5. 将变量命名_为 sort_method_,该名称对应于示例应用中的变量键。
  6. 将变量默认值设置为_字母顺序_。这表示旧排序方法。
创建变量
variable_sort_method.png
  1. 单击右下角的保存以保存变量。
  2. 转到变体**,**然后点击默认的“开启”变体。变体是变量值集合的包装器。
  3. sort_method变量值设置为 popular_first。这表示新排序方法。
创建变体
variation_popular.png
  1. 单击保存

2. 创建标帜传递规则

示例应用_仍_不执行任何操作。接下来,需要在应用程序中制定并启用标帜传递规则。

为_product_sort_标帜的“开启”变体制定有针对性的投放规则。有针对性的交付允许逐步向用户发布灰度发布(特性标帜),但可以灵活地在遇到错误时将其回滚。

  1. 验证您是否在主环境中,因为您使用的是主环境 SDK 密钥:
验证制定规则的环境
verify_env.png
  1. 点击添加规则,然后选择定向投放
  2. 将流量滑块设置为 50%。这会将标帜传递给在此环境中触发标帜的 50% 的用户。可以随时将product_sort标帜推出或回滚到流量的百分比。
  3. 从“传递”下拉列表中,选择“打开”。
  4. 单击保存
配置目标投放
Screenshot docs.png
  1. 将标帜切换为**“开**”,为标帜传递规则启用它:
启用标帜
enable_flag.png

3. 运行示例应用

在 Android Studio 中,启动模拟器。单击“运行”以执行之前创建的示例应用代码。输出类似于以下内容:

PowerShell

Flag on. User number 6998 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 1177 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 9714 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 4140 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 4994 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag off. User number 8700 saw flag variation: off and got products sorted by: alphabetical config variable as part of flag rule: default-rollout-208-19963693913

Flag off. User number 9912 saw flag variation: off and got products sorted by: alphabetical config variable as part of flag rule: default-rollout-208-19963693913

Flag on. User number 6560 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 9252 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

Flag on. User number 6582 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: targeted_delivery

📘 注意

在“开启”变体中,您不会获得恰好 50% 的用户流量,因为您正在与如此少的访问者合作。此外,获得“off”标帜的用户将不会进入您设置的 50% 流量,因此他们落入默认的“关闭”规则(前面的打印语句中的_默认推出_):

4. 工作原理

到目前为止,您:

  • 在 云眼 应用程序中创建了一个标帜、标帜变量和一个标帜变体(变量的包装器)
  • 使用 Decide 方法在应用中实现了标帜

示例应用中发生了什么?

工作原理:决定向用户显示标帜

Flutter SDK 的 Decide 方法确定是显示还是隐藏特定用户的灰度发布(特性标帜)。

📘 注意

可以将此方法重用于不同的标帜规则,例如,投放更多流量或运行实验以仅向部分用户展示不同的排序方法。

了解哪种排序方法最能提高销售额后,请将产品排序标帜推广到所有流量,并将该方法设置为最佳值。

在示例应用中:

Dart

var user = await eyeofcloudClient.createUserContext(userId);
// "product_sort" corresponds to the flag key you create in the Eyeofcloud app
var decideResponse = await user!.decide("product_sort"); 

📘 注意

(可选)在创建用户时包括属性(未显示在示例应用中),以便可以定位特定的受众群体。例如:

Dart

var attributes = <String, dynamic>{};
attributes["logged_in"] = true;
var user = await eyeofcloudClient.createUserContext("user123", attributes);

工作原理:配置标帜变体

可以使用标帜变量动态配置标帜变体。在示例应用中:

Dart

// always returns false until you enable a flag rule in the Eyeofcloud app
if (decideResponse.decision!.enabled) {
  // "sort_method" corresponds to variable key you define in Eyeofcloud app
  var sortMethod = decideResponse.decision!.variables["sort_method"] as String;
  print("sort_method: $sortMethod");
}

对于product_sort标帜,可以配置具有不同sort_method值的变体,按热门产品、相关产品、促销产品等排序。随时在 云眼 应用程序中为排序方法设置不同的值。

第三部分:运行 A/B 测试

本教程将指导您完成目标投放,这是最简单的标帜规则。但是,在推出灰度发布(特性标帜)传递之前,你通常需要对用户_对_灰度发布(特性标帜)变体的反应进行 A/B 测试。

下表显示了标帜传递和 A/B 测试之间的差异:

目标投放规则

A/B 测试规则

可以将标帜推广到一定比例的一般用户群(或特定受众),或者在发现错误时将其回滚。

在投资交付之前,通过 A/B 测试标帜进行实验,这样您就知道要构建什么。跟踪用户在标帜变体中的行为,然后使用 云眼 统计引擎解释实验结果。

请务必对product_sort标帜的“on”变体进行A / B测试!

1. 添加事件跟踪

需要将跟踪事件方法添加到示例应用,以便可以模拟用户事件,然后查看指标。

  1. 删除旧的示例代码,然后粘贴以下代码,记住:
  • 再次替换开发工具包密钥
  • 暂时不要运行你的应用。您首先需要在云眼应用程序中设置A / B测试

Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:eyeofcloud_flutter_sdk/eyeofcloud_flutter_sdk.dart';
import 'dart:async';
import 'package:eyeofcloud_flutter_sdk/src/user_context/eyeofcloud_user_context.dart';

// NOTE: You need to change this SDK key to your project's SDK Key
const String sdkKey = "Your_SDK_Key";
const String logTag = "EYEOFCLOUD_QUICK_START";

void main() {
  runApp(const MyApp());
  var productSorter = ProductSorter();
  productSorter.initializeQuickStart();
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String uiResponse = 'Unknown';

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Quick start example app'),
        ),
        body: Center(
          child: Text(uiResponse),
        ),
      ),
    );
  }
}

class ProductSorter {
  String _datafileHost = "https://cdn.eyeofcloud.com";
  String _datafileSuffixAndroid = "/datafiles/%s.json";
  String _datafileSuffixIOS = "/datafiles/%@.json";

  Future<void> initializeQuickStart() async {
    // Setting up custom datafile URL format
    Map<ClientPlatform, DatafileHostOptions> datafileHostOptions = {};

    datafileHostOptions[ClientPlatform.android] =
        DatafileHostOptions(_datafileHost, _datafileSuffixAndroid);

    datafileHostOptions[ClientPlatform.iOS] =
        DatafileHostOptions(_datafileHost, _datafileSuffixIOS);

    // Initializing EyeofcloudClient
    var flutterSDK = EyeofcloudFlutterSdk(
        sdkKey, datafileHostOptions: datafileHostOptions);
    var response = await flutterSDK.initializeClient();
    if (response.success) {
      flutterSDK.addConfigUpdateNotificationListener((msg) {
        runQuickStart(flutterSDK);
      });
      await runQuickStart(flutterSDK);
    }
  }

  Future<void> runQuickStart(EyeofcloudFlutterSdk flutterSDK) async {
    /* --------------------------------
     * to get rapid demo results, generate random users. Each user always sees the same variation unless you reconfigure the flag rule.
     * --------------------------------
     */

    /* --------------------------------
       OPTIONAL: Add a notification listener so you can integrate with third-party analytics platforms
       --------------------------------
    */
    // var notificationId = await flutterSDK.addDecisionNotificationListener((decisionNotification) {
    //   if (decisionNotification.type == "flag") {
    //
    //     var serializedInfo = decisionNotification.decisionInfo.toString();
    //     print("$logTag Feature flag access related information: ${serializedInfo}");
    //     // Send data to analytics provider here
    //   }
    // });


    // simulate 50 users
    var rangeMax = 9999;
    var rangeMin = 1000;
    var range = Random();
    var hasOnFlags = false;

    for (int i = 0; i < 10; i++) {
      var userId = "${range.nextInt(rangeMax - rangeMin) + rangeMin}";
      /* --------------------------------
       Create hardcoded user & bucket user into a flag variation
       --------------------------------
      */
      var user = await flutterSDK.createUserContext(userId);
      // "product_sort" corresponds to a flag key in your Eyeofcloud project
      var decisionResponse = await user!.decide("product_sort");
      var decision = decisionResponse.decision;
      // did decision fail with a critical error?
      if (decision?.variationKey == null) {
        print("\n\n$logTag decision error: ${decisionResponse.reason}");
      }
      // get a dynamic configuration variable
      // "sort_method" corresponds to a variable key in your Eyeofcloud project
      String sortMethod = decision?.variables["sort_method"] as String;

      if (decision!.enabled) {
        // Keep count how many visitors had the flag enabled
        hasOnFlags = true;
      }
      /* --------------------------------
         Mock what the users sees with print statements (in production, use flag variables to implement feature configuration)
         --------------------------------
      */
      // always returns false until you enable a flag rule in your Eyeofcloud project
      var userIdResponse = await user.getUserId();
      print("\n\n$logTag Flag ${(decision.enabled
          ? "on"
          : "off")}. User number ${userIdResponse.userId} saw flag variation: ${decision
          .variationKey} and got products sorted by: ${sortMethod} config variable as part of flag rule: ${decision
          .ruleKey}");
      await mockPurchase(user);
    }
    if (!hasOnFlags) {
      print(
          "\n\n$logTag Flag was off for everyone. Some reasons could include:\n1. Your sample size of visitors was too small. Rerun, or increase the iterations in the FOR loop\n2. By default you have 2 keys for 2 project environments (dev/prod). Verify in Settings>Environments that you used the right key for the environment where your flag is toggled to ON.\nCheck your key at  https://app.eyeofcloud.com/v2/projects/YOUR_PROJECT_ID/settings/implementation");
    } else {

    }
  }

  // mock tracking a user event so you can see some experiment reports
  Future<void> mockPurchase(EyeofcloudUserContext user) async {
    print("\n\n$logTag Pretend that user made a purchase? y/n ");
    Random rnd = new Random();
    int yesOrNo = rnd.nextInt(2);
    // Assigning random yes and no
    String answer = yesOrNo == 1? "y" : "n";
    print("\n\n$logTag $answer");
    var userIdResponse = await user.getUserId();
    if (answer == "y") {
      // track a user event you defined in the Eyeofcloud app
      await user.trackEvent("purchase");
      print("\n\n$logTag Eyeofcloud recorded a purchase in experiment results for user ${userIdResponse.userId}");
    } else {
      print("\n\n$logTag Eyeofcloud didn't record a purchase in experiment results  for user ${userIdResponse.userId}");
    }
  }
}

2. 暂停免费帐户中的其他规则

如果你有免费帐户,则需要在创建 A/B 测试之前暂停之前在本快速入门中创建的目标投放:

  1. 选择包含之前在快速入门中创建的目标交付的特定标帜
选择之前在本快速入门指南中创建的标帜
选择之前在本快速入门指南中创建的标帜
  1. 选择要暂停的环境(生产)和目标交付
  2. 单击右上角的禁用规则
暂停目标投放
暂停目标投放

3. 创建 A/B 测试

要在云眼项目中创建和启动实验,请执行以下操作:

  1. 转到标帜的规则。
  2. 单击“> A/B 测试添加规则”。
创建新的 A/B 测试
创建新的 A/B 测试

4. 添加事件

在实验中,可以跟踪用户的相关操作,以衡量他们对灰度发布(特性标帜)变体的反应。需要定义要跟踪的操作:

  1. 单击**“指标**”字段。
  2. 点击创建新活动
点击图片放大
create_new_event.png
  1. _输入_购买作为事件密钥,然后单击创建事件。(若要了解新的排序标帜是否有助于客户确定要购买的内容,请跟踪用户在新订单中查看产品后是否进行了购买。
点击图片放大
2021-03-24 12_27_01-product_sort_ Flags_ Rules_ Development_ Experiment - 云眼.png
  1. 保留默认值(衡量_唯一身份转化_次数_的增加_)。
点击图片放大
2020-11-19_10_13_31-Window.png
  1. 点击添加指标
  2. 将默认的“关闭”变体保留为控件。选择您在上一步中配置的 打开变体:
选择“开”变体
save_ab_test.png

📘 注意

此示例中的两个变体不是限制。还可以创建具有多个变体的 A/B 测试。

  1. 单击保存

仔细检查标帜以确保其处于开启状态,以便实验可以运行:

点击图片放大
flag_on.png

5. 运行 A/B 测试

验证模拟器是否仍在运行。单击在 Android Studio 中运行并回答命令行提示。输出应类似于以下内容:

PowerShell

Flag on. User number 1496 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: experiment_1
Pretend that user made a purchase? y/n
n
Eyeofcloud didn't record a purchase in experiment results for user 1496


Flag off. User number 1194 saw flag variation: off and got products sorted by: alphabetical config variable as part of flag rule: experiment_1
Pretend that user made a purchase? y/n
y
Eyeofcloud recorded a purchase in experiment results for user 1194


Flag off. User number 5815 saw flag variation: off and got products sorted by: alphabetical config variable as part of flag rule: experiment_1
Pretend that user made a purchase? y/n
y
Eyeofcloud recorded a purchase in experiment results for user 5815


Flag on. User number 1248 saw flag variation: on and got products sorted by: popular_first config variable as part of flag rule: experiment_1
Pretend that user made a purchase? y/n
y
Eyeofcloud recorded a purchase in experiment results for user 1248


Flag off. User number 9580 saw flag variation: off and got products sorted by: alphabetical config variable as part of flag rule: experiment_1
Pretend that user made a purchase? y/n
n
Eyeofcloud didn't record a purchase in experiment results for user 9580


Done with your mocked A/B test.
Check out your report at  https://app.eyeofcloud.com/v2/projects/19957465438/reports
Be sure to select the environment that corresponds to your SDK key

6. 查看 A/B 测试结果

转到“报告”标签以查看实验结果。

点击图片放大
choose_report.png

结果应如下所示:

点击图片放大
experiment overview.png

📘 注意

  • 在拥有更多用户之前,开发者可能不会看到为标帜变体配置的确切用户流量百分比。
  • 开发者可能不会立即看到用户流量。刷新浏览器以刷新流量。
  • 在访问者数量较多(大约 100,000 或更多)之前,实验结果不会告诉您获胜变体。

7. 工作原理

对于 A/B 测试,需要一种方法来告诉 云眼 用户何时在应用中进行购买,并将应用代码中的此事件映射到您在 云眼 中创建的特定事件。SDK有一个方法!使用 Track Event 方法并传入所创建事件的密钥 ()。在示例应用中:purchase

Dart

// Track how users behave when they see a flag variation
// e.g., after your app processed a purchase, let Eyeofcloud know what happened:
user.trackEvent("purchased"); 

📘 注意

(可选)向事件添加标签以丰富事件(未显示在示例应用中)。还可以使用收入备用标签键来跟踪定量结果。

目前仅实验规则支持事件跟踪,不支持投放规则。未来变体将支持跟踪交货。

无论哪种方式,在实现标帜时都应包含事件跟踪,因为它可以帮助与第三方分析平台集成。它还提供了创建 A/B 测试时的灵活性。

结论

祝贺!您已成功设置并启动了第一个云眼灰度发布(特性标帜)AB实验。虽然此示例侧重于优化销售,但 云眼 的实验平台可以支持一组开放式的实验用例。

请参阅我们完整的 Flutter SDK 文档,了解更多使用实验优化软件的方法。

Last update:
Contributors: “zhangweixue”,zhangweixue