設定値配信 新テンプレート (β)を利用する
設定値配信 新テンプレート (β)は、設定値配信でのバナーやカルーセル等のユースケースをテンプレート化し、専用のエディタを用意したものです。
設定値配信 新テンプレート (β)の利用ついて設定値配信 新テンプレート (β)の利用には、KARTE for Appのご契約およびプラグイン解放手続きが必要です。
詳細はサポートサイトをご参照ください。
1. 設定値配信 新テンプレート (β)で共通の実装
設定値配信をすでに使っている場合は、「1-1. 導入手順」「1-2. 変数参照の実装」の対応は不要です。
1-1. 導入手順
iOS
設定値配信 新テンプレート (β)は、KarteVariablesモジュールを導入することで利用可能です。
Variablesのリファレンスも合わせてご確認ください。
- Podfile の編集
プロジェクトディレクトリにある
Podfileを任意のエディタで開き、KarteVariablesの Pod を追加します。
pod 'KarteVariables'- Pod のインストール プロジェクトディレクトリで下記コマンドを実行し、Pod をインストールします。
pod installAndroid
設定値配信 新テンプレート (β)は、variablesモジュールを導入することで利用可能です。
Variablesのリファレンスも合わせてご確認ください。
アプリの build.gradle (app) を任意のエディタで開き、dependencies ブロックに variables モジュールを追加します。
dependencies {
implementation 'io.karte.android:variables:2.+'
}1-2. 変数参照の実装
管理画面上で設定した変数は、SDKを初期化しただけでは取得されません。
変数を利用する前に、事前にKARTEから取得する必要があります。
1-2-1. SDKのインポート宣言を追加
import KarteVariablesimport io.karte.android.variables.Variables
import io.karte.android.variables.Variable1-2-2. 変数の取得
変数を取得するには Variables クラスの fetch() メソッドを呼び出します。
fetch()は要素の表示や、セグメント更新の直前の任意のタイミングで実行することが一般的です。
実行のタイミングについての注意事項等は、設定値配信のベストプラクティスも合わせてご確認ください。
※前セッションで配信済みのキャッシュをリセットしたい場合は、アプリケーション起動時(SDKの初期化直後)にも呼び出すことを推奨しております。参考: キャッシュの削除(iOS, Android)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
KarteApp.setup(appKey: "アプリケーションキー")
Variables.fetch { isSuccessful in
if isSuccessful {
// 成功時の処理
}
}
...
}override fun onCreate() {
super.onCreate()
KarteApp.setup(this, "アプリケーションキー")
Variables.fetch { isSuccessful ->
if (isSuccessful) {
// 成功時の処理
}
}
}fetch() メソッドの呼び出し時に、SDKは変数取得( _fetch_variables )イベントを送信します。
KARTE管理画面側の設定値配信の接客サービスのデフォルト設定では、トリガーに_fetch_variablesイベントが設定されているため、このイベントをトリガーに配信が行われます。
任意のイベント発火時に接客サービスを配信したい場合は、任意のイベント発火時に変数を取得する(iOS, Android)に従った実装が推奨されます。
この時、配信された接客サービスに含まれる変数は UserDefaults(iOS)やSharedPreference(Android)にキャッシュされます。
加えて、変数を取得した時点で既にキャッシュされた変数がある場合は、キャッシュされた全変数を削除した上で新たに取得した変数をキャッシュします。
また、これと同時にSDKは表示準備( _message_ready )イベントを送信します。
fetch()完了のハンドラについてはFetchCompletion(iOS, Android)が利用可能です。
詳細は設定値配信でのFetchCompletionの利用をご覧ください。
fetch失敗の場合ネットワークエラー等でfetchが失敗した場合、以前fetch成功した時のデータが存在する場合は、そちらが保持されます。
変数名の重複配信された複数の接客サービスの間で、同名の変数が定義されている場合は、接客サービスの
最終更新日時が最近のものがキャッシュされます。
キャッシュのリセットキャッシュには特に有効期限はありませんが、変数の新規取得時以外に、アプリデータ削除(アンインストール等)および
renewVisitorId()によるユーザ紐付け解除時に、同様に全ての変数値の削除が行われます。
2. 設定値配信 新テンプレート (β)の利用方法
現在提供している以下テンプレートの利用方法を記載しています。
- シンプルバナー
- カルーセルバナー
2-1. データモデルを定義する
管理画面で設定した接客のバナーをアプリ側で使用するために、以下のようなデータモデルを定義します。
struct RemoteConfigTemplate: Codable {
let content: Content
struct Content: Codable {
let data: [Data]
let config: Config
}
struct Data: Codable {
let imageUrl: URL
let linkUrl: String?
let index: Int
}
struct Config: Codable {
let templateType: TemplateType
}
enum TemplateType: String, Codable {
case simpleBanner
case carouselWithoutMargin
}
// カルーセルバナー表示用
struct ParsedImageData: Hashable {
let index: Int
let imageUrl: URL
let image: UIImage
let linkUrl: URL?
}
}data class RemoteConfigTemplate(
val content: Content
) {
data class Content(
val data: List<Data>,
val config: Config
)
data class Data(
val imageUrl: String,
val linkUrl: String?,
val index: Int
)
data class Config(
val templateType: TemplateType
)
enum class TemplateType {
SIMPLE_BANNER,
CAROUSEL_WITHOUT_MARGIN;
companion object {
fun fromString(value: String): TemplateType {
return when (value) {
"simpleBanner" -> SIMPLE_BANNER
"carouselWithoutMargin" -> CAROUSEL_WITHOUT_MARGIN
else -> throw Exception("Unknown template type: $value")
}
}
}
}
companion object {
fun fromJsonString(jsonString: String): RemoteConfigTemplate? {
return try {
val root = JSONObject(jsonString)
val content = root.getJSONObject("content")
val dataArray = content.getJSONArray("data")
val config = content.getJSONObject("config")
val dataList = mutableListOf<Data>()
for (i in 0 until dataArray.length()) {
val dataObj = dataArray.getJSONObject(i)
dataList.add(
Data(
imageUrl = dataObj.getString("imageUrl"),
linkUrl = dataObj.optString("linkUrl").takeIf { it.isNotEmpty() },
index = dataObj.getInt("index")
)
)
}
val templateType = TemplateType.fromString(config.getString("templateType"))
RemoteConfigTemplate(
Content(
data = dataList,
config = Config(templateType)
)
)
} catch (e: Exception) {
null
}
}
}
}2-2. 管理画面で実装用IDを保存する
設定値配信 新テンプレート (β)の接客を管理画面で作成し、実装用IDを入力して保存します。
詳細はサポートサイトをご参照ください。
2-3. 配信された値を使用する
変数オブジェクトの取得(iOS, Android)において、設定値配信 新テンプレート (β)用のプレフィックス(KRT_CONFIG_TEMPLATE$)と実装用IDを合わせた文字列をキーに指定することで、接客で設定した値をJSON文字列で取得できます。
例として、管理画面の接客で設定した実装用IDがtop_bannerの場合は、キーはKRT_CONFIG_TEMPLATE$top_bannerとなります。
以下のように値を取り出してデコードすることで、それぞれの接客タイプ(シンプルバナー、カルーセルバナー)に適したデータモデルを使用できます。
シンプルバナー
let prefix = "KRT_CONFIG_TEMPLATE$"
let implementationId = "{{実装用ID}}"
let configKey = "\(prefix)\(implementationId)"
guard let data = Variables.variable(forKey: configKey).string?.data(using: .utf8) else {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}
guard let decoded = try? JSONDecoder().decode(RemoteConfigTemplate.self, from: data) else {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}val prefix = "KRT_CONFIG_TEMPLATE$"
val implementationId = "{{実装用ID}}"
val configKey = "${prefix}${implementationId}"
val variable = Variables.get(configKey)
val variableString = variable.string("")
if (variableString.isEmpty()) {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}
val template = RemoteConfigTemplate.fromJsonString(variableString)
if (template == null) {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}View構築時の実装例
guard let data = decoded.content.data.first else {
return
}
// data.imageUrl, data.linkUrlを使って任意のViewを構築する
let (imageData, _) = try await URLSession.shared.data(from: data.imageUrl)
let iv = UIImageView(frame: .zero)
iv.image = UIImage(data: imageData)
iv.clipsToBounds = true
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = falseval data = template.content.data.firstOrNull()
// data.imageUrl, data.linkUrlを使って任意のViewを構築する
@Composable
private fun SimpleBannerCompose(configKey: String, data: RemoteConfigTemplate.Data) {
val context = LocalContext.current
var hasTrackedOpen by remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = 4.dp
) {
SubcomposeAsyncImage(
model = data.imageUrl,
contentDescription = "Simple Banner",
modifier = Modifier
.fillMaxWidth()
.clickable {
data.linkUrl?.let { linkUrl ->
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl))
context.startActivity(intent)
} catch (e: Exception) {
Log.e("RemoteConfigTemplate", "Failed to open link: $linkUrl", e)
}
}
},
contentScale = ContentScale.Fit,
loading = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier.size(32.dp)
)
}
},
error = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Failed to load image",
color = MaterialTheme.colors.error
)
}
}
)
}
}カルーセルバナー
let prefix = "KRT_CONFIG_TEMPLATE$"
let implementationId = "{{実装用ID}}"
let configKey = "\(prefix)\(implementationId)"
guard let data = Variables.variable(forKey: configKey).string?.data(using: .utf8) else {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}
guard let decoded = try? JSONDecoder().decode(RemoteConfigTemplate.self, from: data) else {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}val prefix = "KRT_CONFIG_TEMPLATE$"
val implementationId = "{{実装用ID}}"
val configKey = "${prefix}${implementationId}"
val variable = Variables.get(configKey)
val variableString = variable.string("")
if (variableString.isEmpty()) {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}
val template = RemoteConfigTemplate.fromJsonString(variableString)
if (template == null) {
// キー(configKey)に対応するデータが見つからない場合の処理
return
}View構築時の実装例
let processedImageData = try await ImageLoader().fetchImagesWithLinkUrl(of: decoded)
for val in processedImageData {
// 任意のViewを構築する
}
// 画像URLの並列処理とソート処理
struct ImageLoader {
func fetchImagesWithLinkUrl(of model: RemoteConfigTemplate) async throws -> [RemoteConfigTemplate.ParsedImageData] {
// 並列リクエストで取得した画像を並び替えるため、インデックス付きのMapを定義する
// [index: (imageUrl, image, linkUrl)]
var temp = [Int: ParsedImageData]()
try await withThrowingTaskGroup(of: (Int, URL, Data, String?).self) { group in
for content in model.content.data {
group.addTask {
let data = try await self.loadData(from: content.imageUrl)
return (content.index, content.imageUrl, data, content.linkUrl)
}
}
for try await (index, imageUrl, data, linkUrl) in group {
if let img = UIImage(data: data) {
temp[index] = .init(index: index, imageUrl: imageUrl, image: img, linkUrl: URL(string: linkUrl ?? ""))
}
}
}
// インデックス付きMapを、インデックス(番号)順でソートされた配列に変換する
let images: [ParsedImageData] = temp.sorted(by: { $0.key < $1.key })
.reduce(into: [ParsedImageData]()) { acc, elem in
acc.append(elem.value)
}
return images
}
private func loadData(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}val dataList = template.content.data
@Composable
private fun CarouselWithoutMarginCompose(configKey: String, dataList: List<RemoteConfigTemplate.Data>) {
val context = LocalContext.current
if (dataList.isEmpty()) return
val pagerState = rememberPagerState(pageCount = { dataList.size })
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.height(200.dp),
elevation = 4.dp
) {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
val data = dataList[page]
SubcomposeAsyncImage(
model = data.imageUrl,
contentDescription = "Carousel item ${page + 1}",
modifier = Modifier
.fillMaxSize()
contentScale = ContentScale.Crop,
loading = {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier.size(32.dp)
)
}
},
error = {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Failed to load image",
color = MaterialTheme.colors.error
)
}
}
)
}
}
}2-4. インプレッションを計測する
バナーが表示されたタイミングで、message_openイベントを送信することでインプレッションを計測することが可能です。
iOS
message_open は、TrackerクラスのtrackOpen(variables:)メソッドで送信することが可能です。
let prefix = "KRT_CONFIG_TEMPLATE$"
let implementationId = "{{実装用ID}}"
let configKey = "\(prefix)\(implementationId)"
let variable = Variables.variable(forKey: configKey)
// バナーが表示されたタイミングで、下記処理を実行
Tracker.trackOpen(variables: [variable])Android
message_open は、VariablesクラスのtrackOpen()メソッドで送信することが可能です。
val prefix = "KRT_CONFIG_TEMPLATE$"
val implementationId = "{{実装用ID}}"
val configKey = "${prefix}${implementationId}"
val variable = Variables.get(configKey)
// バナーが表示されたタイミングで、下記処理を実行
Variables.trackOpen(listOf(variable))2-5. タップを計測する
バナーがタップされたタイミングで、message_clickイベントを送信することでタップを計測することが可能です。
iOS
message_clickは、TrackerクラスのtrackClick(variables:)メソッドで送信することが可能です。
データモデルから引数を設定して、シンプルバナーもしくはカルーセルバナーのタップイベントを送信する実装例
class TapGestureRecognizer: UITapGestureRecognizer {
let index: Int
let linkUrl: URL
init(target: AnyObject, action: Selector, index: Int, linkUrl: URL) {
self.index = index
self.linkUrl = linkUrl
super.init(target: target, action: action)
}
}
@objc
private func imageTapped(_ sender: TapGestureRecognizer) {
let variable = Variables.variable(forKey: configKey)
if variable.isDefined {
// senderからlinkUrlとindexを取得
let values: [String: JSONConvertible] = [
"url": JSONConvertibleConverter.convert(sender.linkUrl.absoluteString),
"banner_template": [
"position_no": JSONConvertibleConverter.convert(sender.index)
]
]
Tracker.trackClick(variable: variable, values: values)
}
if UIApplication.shared.canOpenURL(sender.linkUrl) {
UIApplication.shared.open(sender.linkUrl)
}
}Android
message_clickは、VariablesクラスのtrackClick()メソッドで送信することが可能です。
データモデルから引数を設定して、シンプルバナーもしくはカルーセルバナーのタップイベントを送信する実装例
data.linkUrl?.let { linkUrl ->
// URLを開く前にクリック(イベント)を計測する
val variable = Variables.get(configKey)
if (variable.isDefined) {
val values = mapOf(
"url" to linkUrl,
"banner_template" to mapOf(
"position_no" to page
)
)
Variables.trackClick(listOf(variable), values)
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl))
context.startActivity(intent)
} catch (e: Exception) {
Log.e("BannerTemplate", "Failed to open link: $linkUrl", e)
}
}
}Appendix
設定値配信 新テンプレート (β)と設定値配信の比較
設定値配信 新テンプレート (β)と設定値配信には、共通点と相違点がそれぞれあります。
相違点においては、管理画面側の設定方法やアプリ側の実装方法が、既存の設定値配信とは異なるため、ご注意ください。
共通点
相違点
設定値配信 新テンプレート (β)のデータ型
設定値配信 新テンプレート (β)には、シンプルバナーとカルーセルバナーのテンプレートがあります。
Variablesモジュールを使って値を取得し、パースすることで、テンプレートそれぞれの実装に適したデータ型を簡単に利用できます。
設定値配信 新テンプレート (β)の接客を管理画面で作成すると、変数オブジェクトを取得した際の戻り値(iOS, Android)として、以下のような構造のJSONデータを文字列で返します。
シンプルバナー
{
"content": {
"data": [
{
"imageUrl": "https://example.com/image.png",
"linkUrl": "https://example.com",
"index": 0
}
],
"config": {
"templateType": "simpleBanner"
}
},
"version": "v1"
}カルーセルバナー
{
"content": {
"data": [
{
"imageUrl": "https://example.com/image1.png",
"linkUrl": "https://example.com/page1",
"index": 0
},
{
"imageUrl": "https://example.com/image2.png",
"linkUrl": "https://example.com/page2",
"index": 1
}
],
"config": {
"templateType": "carouselWithoutMargin"
}
},
"version": "v1"
}Updated 27 days ago