Slide 1

Slide 1 text

Recommend Widgetを作った話

Slide 2

Slide 2 text

Masahiro Higuchi / 樋口雅拓 ● グリーグループのリミア株式会社で、LIMIA という住まい領域のメディアを 作っています。ゲーム会社ですが、最近はメディアに力を入れています。 ● 機械学習のエンジニアですが、iOS, Android,JSなどもやっている何でも屋 です。4歳の娘のパパ。twitter: @mahiguch1 ● https://limia.jp/ ● https://arine.jp/ ● https://aumo.jp/ ● https://www.mine-3m.com/mine/

Slide 3

Slide 3 text

LIMIAとは? ● メディアサービス ● 記事一覧を表示し、タップすると 記事詳細を閲覧できる。 ● 記事詳細の最下部に別の記事 への回遊導線が付いている

Slide 4

Slide 4 text

RecommendWidgetとは? ● メディアアプリの記事下に付いている。オスス メ記事と広告をセットにしたもの。 ● オススメ記事はRecommendEngine、広告は 広告システムから取得している。 ● 3, 6, 9枠目が広告のようにして、指定位置に差 し込む。

Slide 5

Slide 5 text

背景と目的 ● 既存のRecommendWidgetを使っていたが、思ったほど成果が上がらな かった。 ● じゃあ、内製化するか! ● 軽い気持ちで始めたら、想定外の要件が発生。 --> 同じ罠にハマる人が減るように、経験を共有します。

Slide 6

Slide 6 text

RecommendWidget実装1 想像通り、サーバから取得しているコンテンツを表示している。 serverIdeaClient.callGetIdeaRecommendation(object : RequestListener> { override fun onSuccess(data: List?) { iIdeaDetailView?.showRecommendationIdeas(data?.let { convertFromDtoList(data, ContentViewModel.ListType.RECOMMENDATION) }) } } override fun showRecommendationIdeas(list: List?) { ideaDetailRecyclerAdapter?.addItems(IdeaDetailRecyclerAdapter.LayoutType.IDEA_RECOMMENDATION, list) } fun addItems(type: LayoutType, list: List?) { contentLst.addAll(list) }

Slide 7

Slide 7 text

RecommendWidget実装2 指定枠(3, 6, 9枠目)には、コンテンツとは別に広告を広告システムか ら取得して表示している。 private fun loadAdvertisement(order: SspViewModel, h: RecyclerView.ViewHolder, position: Int) { adsViewManager.houseAdManager.callAdvertisement(object : LimiaHouseAdViewManager.RequestListener{ override fun onSuccess(houseAdDto: HouseAdDto) { model.houseAdDto = houseAdDto holder.intoView(context, model, object: HouseAdClickListener { 広告取得部分の詳細については、potatotips#60のLT資料で解説しています https://speakerdeck.com/mahiguch/firestorewoshi-tutechun-guang-gao-pei-xin-ji-neng-wozuo-tutahua

Slide 8

Slide 8 text

オススメ記事の生成方法 オススメ記事の一覧は、サーバ側で生成してい る。 ● ユーザの行動履歴を元に、似たような行動 をしているユーザが見ていて自分が見てい ないもの。 ● 同じようなトピックについて書いてある記 事。 これらを混ぜて応答している。

Slide 9

Slide 9 text

リリースしてほっとしていたら。。。 ● 二つのアルゴリズムを混ぜて表示したら、どちらがどれだけ効果があるの かよくわからない。 → CTRを計測することで、この課題を解決しよう!

Slide 10

Slide 10 text

CTRとは? CTR(Click Through Rate) = タップ数 / 表示回数 【表示回数の定義】 ・広告が視聴可能なスクリーンに表示されていること ・広告の一定面積以上が見える状態にあること ・広告が一定の時間以上見える状態であること ・広告が人間によって視聴されていること つまり、一覧表示のChild/Cellが画面上に表示したうち、タップされた割合。 タップ数は簡単に取れるが、スクリーンに表示された回数はどう取れば良いのか?

Slide 11

Slide 11 text

スクリーンに表示されたログ送信(iOS) スクリーンに表示された回数は、表示される度にログを送信すれば実現 できる。iOSの場合、cellのwillDisplayでログ送信すれば実現可能。 override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { ログ送信 } https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/1618087-collectionview?language=objc

Slide 12

Slide 12 text

スクリーンに表示されたログ送信(Android)1 AndroidではRecyclerView.layoutManagerのpositionを取得する ことで実現した。 (obtainRecyclerView()?.layoutManager as? LinearLayoutManager)?.let { val first = it.findFirstVisibleItemPosition() val last = it.findLastVisibleItemPosition() if (first >= 0 && last >= 0) { for (position in first..last) { ログ送信 https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#findFirstVisibleItemPositio n()

Slide 13

Slide 13 text

スクリーンに表示されたログ送信(Android)2 いきなり走らせると更新される度にログが再送されてしまうので、 viewTreeObserver の Listnerに仕込むことで描画してからログ送信されるようにした。 obtainRecyclerView()?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { logImpressionForCurrentVisibleItems { obtainRecyclerView()?.viewTreeObserver?.removeOnGlobalLayoutListener(this) https://developer.android.com/reference/android/view/ViewTreeObserver

Slide 14

Slide 14 text

まとめ ● RecommendWidgetは簡単に作れる。 ● 計測は面倒なので、良い方法があれば教えて欲しい。 ● RecommendEngineの中身は、どこかで話したい。 ご静聴、ありがとうございました!