何の話?
ViewPagerの中身にFragmentを持たせて、 FragmentPagerAdapterでsetAdapterって、よくやりますよね。
で、その時、ViewPagerの特性上、現在UIに乗ってるFragmentの両隣もすでにonCreateViewされています。
そのせいで、GoogleAnalyticsの出力が、思っているのと異なる結果になります。
その解決策として、ViewPager#addOnPageChangeListener()を使おうって話です。
今回作成したプロジェクトは、GitHubで公開してます
一度やってみたかったんです。
わざわざリポジトリつくるほどのものじゃないけど、許してやってください。
備考)筆者はプログラミング一年目の初心者です。あまり信用しないでください。たくさんの突っ込みお待ちしてます。よろしくです。
前提条件
- Activityは基本的にFragmentの管理のみで、実際のユーザアクションにひも付けられているのはもっぱらFragment
- だから、FragmentのScreenNameが欲しい
- けど、ViewPagerだとFragmentのレイアウトがinflateされるのは、実際に表示される以前
- だから、Fragment内でgetTracker().setScreenNameとかやっても、思ったのと違う結果に...
今回の方針
ViewPagerのスクロール(もち、水平方向。スワイプ)を探知するリスナーで、ページが変わったらsetScreenNameなりをする。
サンプルアプリ
まず、ダメな例
public class PlaceHolderFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "section_number";
private int sectionNumber;
public static PlaceHolderFragment newInstance(int sectionNumber) {
Log.d("Fragment#" + sectionNumber, "newInstance");
PlaceHolderFragment fragment = new PlaceHolderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceHolderFragment() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Log.d("Fragment#" + sectionNumber, "onCreate start");
super.onCreate(savedInstanceState);
if (getArguments() != null) {
sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER);
// ScreenNameをセットしたつもりだけど...
GoogleAnalyticsUtil.setScreenName("PlaceHolderFragment "
+ Integer.toString(sectionNumber));
Log.d("Fragment#" + sectionNumber, "onCreate finish");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("Fragment#" + sectionNumber, "onCreateView start");
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
String text = getString(R.string.section_format, sectionNumber);
textView.setText(text);
final Button button = (Button) rootView.findViewById(R.id.button);
final String sectionString = Integer.toString(sectionNumber);
button.setText("Button " + sectionString);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getActivity(),
button.getText() + " is pushed!",
Toast.LENGTH_SHORT).show();
// クリックイベントをGAにsendしてるんだけど,,,
GoogleAnalyticsUtil.sendEvent(
"Category:Button",
"Action:Push",
"Label:" + sectionString);
}
});
Log.d("Fragment#" + sectionNumber, "onCreateView finish");
return rootView;
}
}
GoogleAnalyticsUtilってのは、いちいちBuilder.build()するのが面倒だから作ったやつです。大したことないのでお気になさらず。
実行結果ログ
もちろん、Fragment#の後の数字はViewPagerのpositionです。
11-05 11:15:12.635 com.mickamy.ga_viewpager D/PagerAdapter: created
11-05 11:15:12.635 com.mickamy.ga_viewpager D/PagerAdapter: setAdapter
11-05 11:15:12.705 com.mickamy.ga_viewpager D/Fragment#1: newInstance
11-05 11:15:12.706 com.mickamy.ga_viewpager D/Fragment#2: newInstance
11-05 11:15:12.706 com.mickamy.ga_viewpager D/Fragment#0: onCreate start
11-05 11:15:12.759 com.mickamy.ga_viewpager D/Fragment#1: onCreate finish
11-05 11:15:12.759 com.mickamy.ga_viewpager D/Fragment#1: onCreateView start
11-05 11:15:12.763 com.mickamy.ga_viewpager D/Fragment#1: onCreateView finish
11-05 11:15:12.767 com.mickamy.ga_viewpager D/Fragment#0: onCreate start
11-05 11:15:12.767 com.mickamy.ga_viewpager D/Fragment#2: onCreate finish
11-05 11:15:12.767 com.mickamy.ga_viewpager D/Fragment#2: onCreateView start
11-05 11:15:12.770 com.mickamy.ga_viewpager D/Fragment#2: onCreateView finish
11-05 11:15:23.368 com.mickamy.ga_viewpager D/Fragment#3: newInstance
11-05 11:15:23.368 com.mickamy.ga_viewpager D/Fragment#0: onCreate start
11-05 11:15:23.368 com.mickamy.ga_viewpager D/Fragment#3: onCreate finish
11-05 11:15:23.368 com.mickamy.ga_viewpager D/Fragment#3: onCreateView start
11-05 11:15:23.371 com.mickamy.ga_viewpager D/Fragment#3: onCreateView finish
ね、せっかちでしょ。
そのおかげで、スワイプにも対応できてるんだろうけどさ。
当然、GAの出力もこんな感じ。
もちろん、Fragment#1, #2, #3の順にボタンクリックしてます。
11-05 11:15:12.709 com.mickamy.ga_viewpager I/GAv4: Google Analytics 8.1.15 is starting up. To enable debug logging on a device run:
11-05 11:15:20.852 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446740120849, _v=ma8.1.15, a=1008483049, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=PlaceHolderFragment 2, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:1, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
11-05 11:15:23.593 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446740123593, _v=ma8.1.15, a=1008483049, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=PlaceHolderFragment 3, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:2, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
11-05 11:15:27.020 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446740127017, _v=ma8.1.15, a=1008483049, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=PlaceHolderFragment 3, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:3, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
読みにくいのはご勘弁願います。
ログってどうやったら読みやすくできるのよ。だれか教えてください。
で、上の読みにくい出力の中に、
Categoy:Button
Action:Push
Label:sectionNumber(1~3)
ってのがあります。これが送られるイベントです。
ってあれ、普通にできてるや。
じゃあイベントはオッケーでいいです笑
問題はScreenNameですよ!
今回は、PlaceHolderFragment + sectionNumber
でセットしたつもりなんですけど、ほら!
1,2,3って順番になってるべきところが2,3,3ってなってますよね
けど、これはおれがonCreateでsetScreenしたからで、onCreateViewに書いてたらよかったんじゃないのって?
ちがうでしょ。今回はたまたまこんな具合になったけど、それぞれのFragmentでのViewを作るのにかかる時間が、同じなわけないじゃん。
当然その都度、上にあげたログの生成順は変わります。たぶん
だから、adaptされるFragment内でsetScreenNameするのは危険なんです。たぶん。
もしかしたら僕がダメなだけかもしれないです。
お前がバカなだけだって思った方、コメントいただけたら幸いです。
ViewPager#addOnPageChangeListener()
ちょいと昔にはsetOnPageChangeってのがあったらしいけど、いまはもうdeprecatedらしいです。
で、これが書いたコードです。
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 今回は無視
}
@Override
public void onPageSelected(int position) {
// 各ポジションでScreenNameをセット
switch (position) {
case FIRST_FRAGMENT:
GoogleAnalyticsUtil.setScreenName("Fragment#1");
break;
case SECOND_FRAMGENT:
GoogleAnalyticsUtil.setScreenName("Fragment#2");
break;
case THIRD_FRAGMENT:
GoogleAnalyticsUtil.setScreenName("Fragment#3");
break;
}
}
@Override
public void onPageScrollStateChanged(int state) {
// 今回は無視
}
});
はい、なんかScreenNameに指定した文字列変えちゃったけど、ごめんなさい。
直すの面倒です。
で、これが実行結果のログです。
11-05 12:18:18.141 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446743898140, _v=ma8.1.15, a=462218005, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=Fragment#1, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:1, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
11-05 12:18:21.394 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446743901394, _v=ma8.1.15, a=462218005, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=Fragment#2, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:2, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
11-05 12:18:23.543 com.mickamy.ga_viewpager I/GAv4: Dry run enabled. Would have sent hit: ht=1446743903542, _v=ma8.1.15, a=462218005, aid=com.mickamy.ga_viewpager, an=GA-ViewPager, av=1.0, cd=Fragment#3, cid=b2ec3ade-0563-4b8e-829b-3b8d0b87da85, ea=Action:Push, ec=Category:Button, el=Label:3, sr=1440x2392, t=event, tid=UA-69540714-4, ul=en-us, v=1
はい、きれいに1,2,3と並んでますね。
これだと、きちんとユーザがそのFragmentを視認した瞬間にsetScreenNameしてsendするわけですから、画面に対するセッション時間をきちんと取れます。
よかった。
まとめ
- ViewPagerの中身がスワイプして初めてViewをinflateすると思ってたら痛い目に遭います。
- その対策として、ViewPagerの横スクロールを探知するOnPageChangeListenerがあります。
- この方法は、ViewPager間でViewを反映させたい時とかにも使えると思います。
- だれか僕にログを見やすく出力する方法を教えてください。
今回貼ったlogcatだって、一旦全部コピーして関係ないとこいちいち消したんです。フィルタくらいはかけたけど。
だるかったです。お願いします。