Flutter产品搜索插件visenze_productsearch_sdk的使用
Flutter产品搜索插件visenze_productsearch_sdk的使用
目录
1. 概述
ViSenze发现套件通过帮助客户更容易地搜索、导航和与产品互动,为您的客户提供更好的和更直观的产品搜索和发现体验。ViSenze最新的产品搜索和推荐API包含在这个SDK中。更多信息请参阅在线文档。
2. 设置
2.1 安装
运行命令:
flutter get visenze_productsearch_sdk
2.2 启动
在开始使用SDK之前,您需要设置SDK密钥。大多数这些密钥可以在您的帐户的仪表板上找到。
首先,查看下表以了解每个密钥代表什么:
密钥 | 重要性 | 描述 |
---|---|---|
app_key | 必需 | 所有SDK功能都依赖于一个有效的app_key。app_key还限制了您可以使用的API功能。 |
placement_id | 必需 | 您的放置ID。 |
timeout | 可选 | API超时时间(毫秒)。默认值为15000。 |
uid | 可选 | 客户用户ID。如果没有提供,将自动生成。 |
创建ProductSearch实例:
const psClient = await VisenzeProductSearch.create('APP_KEY', 'PLACEMENT_ID');
也可以指定用户ID和超时时间:
const psClient = await VisenzeProductSearch.create('APP_KEY', 'PLACEMENT_ID', uid: 'CUSTOMER_UID', timeout: TIMEOUT_IN_MS);
3. API
3.1 图像搜索
POST /product/search_by_image
通过图像搜索可以有三种不同的方式 - 通过URL、ID或文件。
使用图像ID:
var params = {
im_id: 'your-image-id'
};
var response = await psClient.productSearchByImage(null, params);
使用图像URL:
var params = {
im_url: 'your-image-url'
};
var response = await psClient.productSearchByImage(null, params);
使用相册中的图像:
var image = await psClient.uploadImage();
if (image != null) {
var response = await psClient.productSearchByImage(image, params);
}
使用相机捕捉的图像:
var image = await psClient.captureImage();
if (image != null) {
var response = await psClient.productSearchByImage(image, params);
}
还可以传递格式为XFile的图像。
注意:如果您正在访问iOS设备上的相册/相机进行图像搜索,请提供NSPhotoLibraryUsageDescription
和NSCameraUsageDescription
值。
请求参数:有关搜索API的请求参数,请参见Viseze文档中心。
3.1.1 调整大小设置
默认情况下,我们将用户上传的图像大小限制为512x512像素,以平衡搜索延迟和搜索准确性。
如果您的图像包含细小细节,如纺织品图案和纹理,可以设置更大的限制。
psClient.widthLimit = 1024;
psClient.heightLimit = 1024;
为了有效利用移动设备的内存和网络带宽,最大尺寸设置为1024x1024。任何超过此限制的图像都将被调整为该限制。
3.2 推荐
GET /product/recommendations/{product_id}
根据产品的唯一标识符搜索产品数据库中的视觉相似产品。
var productId = 'your-product-id';
// 示例参数
var parameters = {
limit: 20 // 将结果限制为20个结果
};
var response = await psClient.productSearchById(productId, parameters);
请求参数:有关此API的请求参数,请参见Viseze文档中心。
4. 高级搜索
4.1 自动对象检测
搜索API能够检测查询图像中存在的对象,并建议最佳匹配的产品类型以运行搜索。
使用detection
或point
参数来建议查询图像中的特定对象。box
和point
参数不允许出现在同一个请求中。
var params = {
im_url: 'your-image-url',
detection: 'Top' // 仅检测具有产品类型`Top`的图像中的对象
detection_limit: 5 // 仅检测图像中的最多5个对象
};
var response = await psClient.productSearchByImage(null, params);
详情:有关更多详细信息,请参阅Viseze文档中心中的自动对象检测文档。
4.2 过滤器和文本过滤器
要基于产品元数据过滤搜索结果,请在filters
或text_filters
参数中提供元数据键值对。
参数 | 过滤查询行为 | 示例 |
---|---|---|
filters | 过滤查询被视为精确匹配条件。适用于字符串、整数和浮点类型字段。 | filters=brand:my_brand 表示搜索结果的品牌(字符串)值必须严格等于“my_brand”。filters=price:10,199 表示搜索结果的价格(整数)值必须严格在10到199之间(包括10和199)。 |
text_filters | 过滤查询被视为部分匹配过滤器。仅适用于字符串类型字段。 | text_filters=brand:my_brand 表示搜索结果的品牌值可以是包含“my_brand”的任何值,例如“my_brand >> sub brand”。 |
示例
var productId = 'your-product-id';
var parameters = {
filters: ['brand:my_brand', 'price:50:200'] // 过滤所有来自my_brand且价格在50-200货币单位的产品
};
var response = await psClient.productSearchById(productId, parameters);
详情:有关更多过滤器语法和规则,请参阅Viseze文档中心中的过滤器部分。
4.3 层面
层面是从结果列表中过滤的值。可以通过发送一组字段来启用层面。
var productId = 'your-product-id';
var parameters = {
facets: 'gender, brand, price', // 返回可能的过滤元数据字段,性别、品牌、价格
facets_limit: 10 // 每个层面返回10个值
};
var response = await psClient.productSearchById(productId, parameters);
详情:有关更多详细信息,请参阅Viseze文档中心中的层面部分。
4.4 属性检索
要从API调用中检索元数据,请在attrs_to_get
(字段列表)属性中提供产品元数据键列表。
var productId = 'your-product-id';
var parameters = {
attrs_to_get: 'gender, price, brand' // 在结果中返回性别、价格和品牌元数据
};
var response = await psClient.productSearchById(productId, parameters);
注意:只有已索引的属性才能被检索。
注意:只有已索引的属性可以通过此参数检索。您可以访问编辑应用页面查看哪些属性已包含在应用索引中。
5. 搜索结果
该SDK使用http库进行API请求。响应是一个http Response。
错误代码列表可以在这里找到。 对于响应属性,请参阅API文档。
var productId = 'your-product-id';
var response = await psClient.productSearchById(productId);
if (response.statusCode == 200) {
Map<String, dynamic> successResp = jsonDecode(response.body);
List<Map<String, dynamic>> results = successResp['result'];
// 处理结果
} else {
// 处理错误
}
6. 事件跟踪
为了提高搜索性能并获得有用的数据洞察力,建议发送与视觉搜索结果相关的用户交互(操作)。
目前,我们支持以下事件动作:product_click
、product_view
、add_to_cart
和 transaction
。但是,action参数可以是任意字符串,以便发送自定义事件。
某些事件(例如product_click
或product_view
)可能需要额外的参数,如pid
(产品ID)。
6.1 设置
我们将使用从您的应用密钥和放置ID生成的跟踪ID初始化事件跟踪器。
6.2 发送事件
单个事件
用户操作可以通过事件处理器发送。注册事件处理器到用户将互动的元素。
// 发送产品点击
psClient.sendEvent('product_click', {
queryId: '<search reqid>',
pid: '<your product id>',
pos: 1, // 搜索结果中的产品位置,从1开始
});
// 发送产品印象
psClient.sendEvent('product_view', {
queryId: '<search reqid>',
pid: '<your product id>',
pos: 1, // 搜索结果中的产品位置,从1开始
});
// 发送交易事件,例如订单购买300元
psClient.sendEvent('transaction', {
queryId: "<search reqid>",
transId: "<your transaction ID>",
value: 300
});
// 发送添加到购物车事件
psClient.sendEvent('add_to_cart', {
queryId: '<search reqid>',
pid: '<your product id>',
pos: 1, // 搜索结果中的产品位置,从1开始
});
// 发送自定义事件
psClient.sendEvent('favourite', {
queryId: '<search reqid>',
label: 'custom event label',
cat: 'visual_search'
});
// 处理成功或错误
try {
psClient.sendEvent('product_click', {
queryId: '<search reqid>',
pid: '<your product id>',
pos: 1, // 搜索结果中的产品位置,从1开始
});
onRequestSuccess(); // 处理成功
} catch (errResponse) {
onRequestError(errResponse); // 处理错误
}
批量事件
批量操作可以通过批处理事件处理器发送。
psClient.sendEvents('transaction',
[{
queryId: '<search request ID>',
pid: '<product ID - 1>',
value: 300,
}, {
queryId: '<search request ID>',
pid: '<product ID - 2>',
value: 400
}]
);
6.3 事件参数
有关所有事件参数及其解释的列表,请参阅此文档。
6.3.1 搜索查询ID
所有发送到Viseze分析服务器的事件都需要在搜索结果响应中找到的搜索查询ID(reqid
)作为请求参数的一部分。
var productId = 'your-product-id';
var parameters = {
...
};
var response = await psClient.productSearchById(productId, parameters);
var responseBody = jsonDecode(response.body);
var queryId = responseBody['reqid']; // <- 这是您的搜索查询ID
您也可以直接获取客户端最后一次成功搜索请求的查询ID。
var queryId = psClient.lastSuccessQueryId;
6.3.2 会话ID
var sessionId = psClient.sessionId;
6.3.3 用户ID
var userId = psClient.userId;
示例代码
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:visenze_productsearch_sdk/visenze_productsearch.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
[@override](/user/override)
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
String _recRequestResult = '<unknown>';
String _searchRequestResult = '<unknown>';
String _sid = '<unknown>';
String _uid = '<unknown>';
String _trackingRequestResult = '<unknown>';
String? _lastQueryId = '<unknown>';
final TextEditingController _pidController = TextEditingController();
final TextEditingController _imgUrlController = TextEditingController();
final TextEditingController _imgIdController = TextEditingController();
final TextEditingController _queryParamController = TextEditingController();
final TextEditingController _eventController = TextEditingController(text: 'transaction');
final TextEditingController _paramsController = TextEditingController(text: '{"pid": "PID_1", "queryId": "1234", "value": 50}');
final TextEditingController _paramsListController = TextEditingController(text: '[{"pid": "PID_1", "queryId": "1234", "value": 50}, {"pid": "PID_2", "queryId": "1234", "value": 100}]');
late VisenzeProductSearch psSearchClient;
late VisenzeProductSearch psRecClient;
String? _fileName;
[@override](/user/override)
void initState() {
super.initState();
initPS();
}
// 工厂方法是异步的,所以我们将其放在异步方法中。
void initPS() async {
psSearchClient = await VisenzeProductSearch.create('APP_KEY', 'PLACEMENT_ID');
psRecClient = await VisenzeProductSearch.create('APP_KEY', 'PLACEMENT_ID');
setState(() {
_sid = psSearchClient.sessionId;
_uid = psSearchClient.userId;
// 调整大小限制 --------
// psSearchClient.widthLimit = 2048;
// psSearchClient.heightLimit = 2048;
});
}
// ------------- 公共工具函数 ---------- 开始 --------------
void _resetSession() async {
psSearchClient.resetSession();
setState(() {
_sid = psSearchClient.sessionId;
});
}
void _onRequestSuccess() {
setState(() {
_trackingRequestResult = 'Request success';
});
}
void _onRequestError(dynamic err) {
setState(() {
_trackingRequestResult = 'Request fail: $err';
});
}
void _resetMsg() {
_searchRequestResult = "";
_trackingRequestResult = "";
_fileName = "";
_recRequestResult = "";
}
Map<String, dynamic> _getSearchQueryParam() {
Map<String, dynamic> params = {};
if(_queryParamController.text.isNotEmpty) {
params = jsonDecode(_queryParamController.text);
}
return params;
}
// ------------- 推荐 ---------- 开始 --------------
void _searchById() async {
_resetMsg();
try {
Map<String, dynamic> params = _getSearchQueryParam();
var response = await psRecClient.productSearchById(_pidController.text, params=params);
setState(() {
_recRequestResult = response.body;
_lastQueryId = psRecClient.lastSuccessQueryId;
});
} catch (err) {
_onRequestError(err);
}
}
// ------------- 搜索 ---------- 开始 --------------
void _searchByImgUrl() async {
_resetMsg();
try {
Map<String, dynamic> params = {'im_url': _imgUrlController.text};
params.addAll(_getSearchQueryParam());
var response = await psSearchClient.productSearchByImage(null, params);
setState(() {
_searchRequestResult = response.body;
_lastQueryId = psSearchClient.lastSuccessQueryId;
});
} catch(err) {
_onRequestError(err);
}
}
void _searchByImgId() async {
_resetMsg();
try {
Map<String, dynamic> params = {'im_id': _imgIdController.text};
params.addAll(_getSearchQueryParam());
var response = await psSearchClient.productSearchByImage(null, params);
setState(() {
_searchRequestResult = response.body;
_lastQueryId = psSearchClient.lastSuccessQueryId;
});
} catch(err) {
_onRequestError(err);
}
}
void _searchByCamera() async {
_resetMsg();
try {
var file = await psSearchClient.captureImage();
setState(() {
_fileName = file?.name;
});
if (file != null) {
_searchByImg(file);
}
} catch(err) {
_onRequestError(err);
}
}
void _searchByImageUpload() async {
_resetMsg();
var file;
try {
file = await psSearchClient.uploadImage();
} catch(err) {
_onRequestError(err);
return;
}
setState(() {
_fileName = file?.name;
});
if (file != null) {
_searchByImg(file);
}
}
void _searchByImg(file) async {
_resetMsg();
try {
if (_fileName != null) {
var response = await psSearchClient.productSearchByImage(file, _getSearchQueryParam());
setState(() {
_searchRequestResult = response.body;
_lastQueryId = psSearchClient.lastSuccessQueryId;
});
}
} catch(err) {
_onRequestError(err);
}
}
// ------------- 跟踪 ---------- 开始 --------------
Future<void> _sendEvent() async {
try {
Map<String, dynamic> params = jsonDecode(_paramsController.text);
params["queryId"] = _lastQueryId;
await psSearchClient.sendEvent(
_eventController.text, params);
_onRequestSuccess();
} catch (err) {
_onRequestError(err);
}
}
Future<void> _sendBatchEvent() async {
try {
List<Map<String, dynamic>> params = jsonDecode(_paramsListController.text)
.map((element) => element as Map<String, dynamic>)
.toList();
await psSearchClient.sendEvents(_eventController.text, params);
_onRequestSuccess();
} catch (err) {
_onRequestError(err);
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text('ProductSearch SDK Demo'),
bottom: const TabBar(
tabs: [
Tab(text: 'Rec'),
Tab(text: 'Search'),
Tab(text: 'Tracking'),
],
),
),
body: TabBarView(children: [
SingleChildScrollView(
child: Container(
height: 600,
padding: const EdgeInsets.all(15),
child: ListView(
children: <Widget>[
Text(
'last query id: $_lastQueryId',
),
const Padding(padding: EdgeInsets.all(20)),
TextField(
decoration: const InputDecoration(
labelText: "Query params",
),
controller: _queryParamController,
),
TextField(
decoration: const InputDecoration(
labelText: "Product id",
),
controller: _pidController,
),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _searchById,
child: const Text('Search by product id')),
const Divider(thickness: 2, color: Colors.grey, height: 30),
Text(
'Response: $_recRequestResult',
),
const Padding(padding: EdgeInsets.all(4)),
Text("Error: $_trackingRequestResult"),
],
),
),
),
SingleChildScrollView(
child: Container(
height: 900,
padding: const EdgeInsets.all(15),
child: ListView(
children: <Widget>[
Text(
'last query id: $_lastQueryId',
),
const Padding(padding: EdgeInsets.all(20)),
TextField(
decoration: const InputDecoration(
labelText: "Query params",
),
controller: _queryParamController,
),
const Padding(padding: EdgeInsets.all(20)),
TextField(
decoration: const InputDecoration(
labelText: "Image url",
),
controller: _imgUrlController,
),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _searchByImgUrl,
child: const Text('Search by image url')),
const Divider(thickness: 2, color: Colors.grey, height: 30),
TextField(
decoration: const InputDecoration(
labelText: "Image id",
),
controller: _imgIdController,
),
ElevatedButton(
onPressed: _searchByImgId,
child: const Text('Search by image id')),
const Divider(thickness: 2, color: Colors.grey, height: 30),
ElevatedButton(
onPressed: _searchByImageUpload,
child: const Text('Upload search')),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _searchByCamera,
child: const Text('Camera search')),
const Padding(padding: EdgeInsets.all(4)),
Text(
'Image name: $_fileName',
),
const Divider(thickness: 2, color: Colors.grey, height: 30),
Text(
'Response: $_searchRequestResult',
),
const Divider(thickness: 2, color: Colors.grey, height: 30),
const Padding(padding: EdgeInsets.all(4)),
Text("Error: $_trackingRequestResult"),
],
),
),
),
Container(
height: 600,
padding: const EdgeInsets.all(15),
child: ListView(
children: <Widget>[
Text(
'last query id: $_lastQueryId',
),
const Padding(padding: EdgeInsets.all(4)),
Text(
'Session id: $_sid',
),
Text(
'User id: $_uid',
),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _resetSession,
child: const Text('Reset session')),
const Divider(thickness: 2, color: Colors.grey, height: 30),
TextField(
decoration: const InputDecoration(
labelText: "Event name",
),
controller: _eventController,
),
TextField(
decoration: const InputDecoration(
labelText: "Event params",
),
controller: _paramsController,
),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _sendEvent, child: const Text('Send event')),
TextField(
decoration: const InputDecoration(
labelText: "Event list params",
),
controller: _paramsListController,
),
const Padding(padding: EdgeInsets.all(4)),
ElevatedButton(
onPressed: _sendBatchEvent,
child: const Text('Send batch event')),
const Padding(padding: EdgeInsets.all(4)),
Text(_trackingRequestResult),
],
),
),
])),
));
}
}
更多关于Flutter产品搜索插件visenze_productsearch_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter产品搜索插件visenze_productsearch_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
visenze_productsearch_sdk
是 ViSenze 提供的一个 Flutter 插件,用于在 Flutter 应用中集成视觉搜索功能。通过该插件,你可以在应用中实现基于图像的搜索,类似于通过拍照或上传图片来搜索相似商品的功能。
以下是使用 visenze_productsearch_sdk
插件的步骤:
1. 添加依赖
首先,在你的 pubspec.yaml
文件中添加 visenze_productsearch_sdk
依赖:
dependencies:
flutter:
sdk: flutter
visenze_productsearch_sdk: ^1.0.0 # 请确保使用最新版本
然后,运行 flutter pub get
来安装依赖。
2. 初始化 SDK
在你的 Flutter 应用中,首先需要初始化 ViSenze 的 SDK。你需要在应用启动时调用 ViSenze.initialize()
方法,并传入你的 API key。
import 'package:visenze_productsearch_sdk/visenze_productsearch_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await ViSenze.initialize(apiKey: 'YOUR_API_KEY');
runApp(MyApp());
}
3. 使用视觉搜索功能
在应用中使用视觉搜索功能时,你可以通过 ViSenze.searchByImage
方法来搜索相似商品。你可以传入本地图片文件路径或网络图片的 URL。
import 'package:visenze_productsearch_sdk/visenze_productsearch_sdk.dart';
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
[@override](/user/override)
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
List<SearchResult> _searchResults = [];
Future<void> _searchByImage(String imagePath) async {
try {
final results = await ViSenze.searchByImage(imagePath: imagePath);
setState(() {
_searchResults = results;
});
} catch (e) {
print('Error searching by image: $e');
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Visual Search'),
),
body: Column(
children: [
ElevatedButton(
onPressed: () {
_searchByImage('assets/sample_image.jpg'); // 使用本地图片
},
child: Text('Search by Image'),
),
Expanded(
child: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final result = _searchResults[index];
return ListTile(
title: Text(result.productName),
subtitle: Text(result.productDescription),
leading: Image.network(result.imageUrl),
);
},
),
),
],
),
);
}
}