AndroidでViewPager配下のFragment間でViewの状態を共有する

本業はWebエンジニアでAndroid歴は半年くらいですが、色々試行錯誤したネタを投下していこうと思います。

## やりたいこと
タイトルだけだと何のこっちゃという話ですが、例えばTwitterとかでフォローするボタンがあるじゃないですか。で、ViewPager管理下の各Fragmentにフォローするボタンを置いてどこかでフォローするを押したら、押したページ以外にも押された結果を反映させたいという話です。ViewPagerを使っていなければデータを引き回すなりしてonCreateViewあたりでデータに合わせてViewを編集すればいいのですが、ViewPagerを使ってるとスワイプする時に今見ているページと次に表示するページの両方を表示するので、1ページ目でフォローする→スワイプで2ページ目に行く、というアクションをすると2ページ目はすでに1ページ目の表示時に作成済なのでonCreateViewを通らずに未フォローの状態のままになってしまいます。

## 作ってみた
んで、Observerパターンを使うとすっきり作ることができました。フォロアーリストのModelを作ってそいつのObserverとして各フラグメントを登録することでデータが変更されたらObserverとして観測中のFragmentは変更の通知をキャッチしてViewを変更するようにします。

### ActivityとPagerAdaptor
デフォルトで作成されるPager付きのActivityからいらないものを削ってFragmentを差し替えただけです。

```java:PagerSampleActivity.java
package me.rei_m.androidsample.activitiy;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.support.v13.app.FragmentPagerAdapter;
import android.os.Bundle;

import android.support.v4.view.ViewPager;

import me.rei_m.androidsample.R;
import me.rei_m.androidsample.fragment.PagerItemFragment;

public class PagerSampleActivity extends Activity {

    public static Intent createIntent(Context context) {
        return new Intent(context, PagerSampleActivity.class);
    }

    SectionsPagerAdapter mSectionsPagerAdapter;

    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager_sample);

        mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());

        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);
    }

    public class SectionsPagerAdapter extends FragmentPagerAdapter {
        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }
        @Override
        public Fragment getItem(int position) {
            return PagerItemFragment.newInstance(position);
        }
        @Override
        public int getCount() {
            return 4;
        }
        @Override
        public CharSequence getPageTitle(int position) {
            return String.valueOf(position) + "枚目";
        }
    }
}
```

### Fragment

フラグメントはこんな見た目でページ番号が変わる以外は全部同じです。
![fragment](http://cdn-ak.f.st-hatena.com/images/fotolife/R/Rei19/20150212/20150212093251.png)

```java:PagerItemFragment.java
package me.rei_m.androidsample.fragment;

import android.app.Fragment;
import android.os.Bundle;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.Observable;
import java.util.Observer;

import me.rei_m.androidsample.R;
import me.rei_m.androidsample.entity.FollowerEntity;
import me.rei_m.androidsample.model.FollowerListModel;
import me.rei_m.androidsample.model.ModelLocator;

public class PagerItemFragment extends Fragment implements Observer, View.OnClickListener {
    // Modelからの通知を受け取る必要があるのでObserverを実装する

    private static final String ARG_PAGE_NO = "pageNo";

    public static PagerItemFragment newInstance(int pageNo) {
        Bundle args = new Bundle();
        args.putInt(ARG_PAGE_NO, pageNo);

        PagerItemFragment fragment = new PagerItemFragment();
        fragment.setArguments(args);

        return fragment;
    }

    private FollowerEntity mUser;
    private int mPageNo;
    private TextView mTextView;
    private Button mButton;

    private FollowerListModel mFollowerListModel;

    public PagerItemFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ページ番号を取得
        mPageNo = getArguments().getInt(ARG_PAGE_NO);

        // ModelのObserverにインスタンスを登録
        mFollowerListModel = ModelLocator.getInstance().getFollowerListModel();
        mFollowerListModel.addObserver(this);

        // 仮のユーザを設定
        mUser = new FollowerEntity();
        mUser.setId("0000001");
        mUser.setName("ほげー");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_pager_item, container, false);

        // TextViewとButtonを取得
        mTextView = (TextView) view.findViewById(R.id.text_pager_item_title);
        mTextView.setText(String.valueOf(mPageNo));
        mButton = (Button) view.findViewById(R.id.button_pager_item);
        mButton.setOnClickListener(this);

        // 初期表示の設定。対象のユーザーをフォロー済かどうかで表示を変える
        if(mFollowerListModel.hasFollower(mUser)){
            setFollowedStyle();
        }else{
            setUnFollowedStyle();
        }

        return view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // ModelのObserverからインスタンスを解除
        mFollowerListModel.deleteObserver(this);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mTextView = null;
        mButton = null;
        mFollowerListModel = null;
        mUser = null;
    }

    @Override
    public void update(Observable observable, Object data) {
        // Modelからフォロアーリストの更新通知が来たら走る処理
        if(data instanceof FollowerListModel.ChangeFollowerListEvent){
            FollowerListModel.ChangeFollowerListEvent event = (FollowerListModel.ChangeFollowerListEvent) data;
            // フォローアクションの状態によって表示を変える
            if(event.getIsAdded()){
                setFollowedStyle();
            }else{
                setUnFollowedStyle();
            }
        }
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()){
            case R.id.button_pager_item:
                // フォロアーリストを確認して、追加済であれば削除、未追加であれば追加
                // ボタンのイベントではViewの表示は変えない
                if(mFollowerListModel.hasFollower(mUser)){
                    mFollowerListModel.removeFollower(mUser);
                }else{
                    mFollowerListModel.addFollower(mUser);
                }
                break;
        }
    }

    // フォロー済の状態に表示を変更する
    private void setFollowedStyle(){
        mButton.setText(mUser.getName() + "をふぉろーしてるよ");
        mTextView.setBackgroundColor(getResources().getColor(R.color.active));
    }

    // 未フォローの状態の表示を変更する
    private void setUnFollowedStyle(){
        mButton.setText(mUser.getName() + "はまだふぉろーしてないよ");
        mTextView.setBackground(null);
    }
}
```

### ModelとEntity

Obserbleを実装したModelを作ってデータに変更があったら更新の通知を送るように実装します。Entitiyは適当。

```java:FollowerListModel.java
package me.rei_m.androidsample.model;

import java.util.EventObject;
import java.util.HashMap;
import java.util.Observable;

import me.rei_m.androidsample.entity.FollowerEntity;

public class FollowerListModel extends Observable{

    public static FollowerListModel createInstance(){

        FollowerListModel instance = new FollowerListModel();
        instance.mFollowerList = new HashMap<>();

        return instance;
    }

    private FollowerListModel(){}

    private HashMap<String, FollowerEntity> mFollowerList;

    public HashMap<String, FollowerEntity> getFollowerList() {
        return mFollowerList;
    }

    // フォロー済か確認する
    public boolean hasFollower(FollowerEntity target){
        return mFollowerList.containsKey(target.getId());
    }

    // フォロアーリストに追加する
    public void addFollower(FollowerEntity target){
        mFollowerList.put(target.getId(), target);
        notifyEvent(true);
    }

    // フォロアーリストから削除する
    public void removeFollower(FollowerEntity target){
        mFollowerList.remove(target.getId());
        notifyEvent(false);
    }

    // フォロアーリストの更新通知をObserverに対して送る
    private void notifyEvent(boolean isAdded){
        ChangeFollowerListEvent event = new ChangeFollowerListEvent(this);
        event.setIsAdded(isAdded);
        setChanged();
        notifyObservers(event);
    }

    // 通知用のEventObject
    public static class ChangeFollowerListEvent extends EventObject {
        public ChangeFollowerListEvent(Object source){
            super(source);
        }
        private boolean isAdded;

        public boolean getIsAdded() {
            return isAdded;
        }

        public void setIsAdded(boolean isAdded) {
            this.isAdded = isAdded;
        }
    }

}
```

```java:FollowerEntity.java
package me.rei_m.androidsample.entity;

import java.io.Serializable;

public class FollowerEntity implements Serializable {

    private String id;
    private String name;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
```

こんな感じで予定通りに動いてくれました。これで1ページ目でフォローする→スワイプで2ページ目に行っても2ページ目もちゃんとフォロー済の表示になります。変なとこがあればご指摘ください〜

コードはこちら

https://github.com/rei-m/androidSample