ViewPager + Fragment + GoogleAnalytics の整合性の保ち方


## 何の話？
ViewPagerの中身にFragmentを持たせて、 FragmentPagerAdapterでsetAdapterって、よくやりますよね。
で、その時、ViewPagerの特性上、現在UIに乗ってるFragmentの両隣もすでにonCreateViewされています。
そのせいで、GoogleAnalyticsの出力が、思っているのと異なる結果になります。
その解決策として、ViewPager#addOnPageChangeListener()を使おうって話です。

[今回作成したプロジェクトは、GitHubで公開してます](https://github.com/mickamy9/GA-ViewPager)
一度やってみたかったんです。
わざわざリポジトリつくるほどのものじゃないけど、許してやってください。

備考）筆者はプログラミング一年目の初心者です。あまり信用しないでください。たくさんの突っ込みお待ちしてます。よろしくです。


### 前提条件
- Activityは基本的にFragmentの管理のみで、実際のユーザアクションにひも付けられているのはもっぱらFragment
- だから、FragmentのScreenNameが欲しい
- けど、ViewPagerだとFragmentのレイアウトがinflateされるのは、実際に表示される以前
- だから、Fragment内でgetTracker().setScreenNameとかやっても、思ったのと違う結果に...

### 今回の方針
ViewPagerのスクロール（もち、水平方向。スワイプ）を探知するリスナーで、ページが変わったらsetScreenNameなりをする。

##サンプルアプリ
![7c5c2cb47dfbdc6c38ba8bb857af0a50.gif](https://qiita-image-store.s3.amazonaws.com/0/75088/1334201b-bd40-1a0b-2962-164e77ef1c65.gif "7c5c2cb47dfbdc6c38ba8bb857af0a50.gif")

まず、ダメな例

```Java:PlaceHolderFragment.java
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です。

```Log:logcat
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の順にボタンクリックしてます。

```logcat
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らしいです。

で、これが書いたコードです。

```java:MainActivity.java

        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に指定した文字列変えちゃったけど、ごめんなさい。
直すの面倒です。

で、これが実行結果のログです。

```logcat
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だって、一旦全部コピーして関係ないとこいちいち消したんです。フィルタくらいはかけたけど。
だるかったです。お願いします。