2012年6月11日月曜日

Fragment から Activity にコールバックする方法

Fragment から Activity にコールバックしたいときに、例えばこんな感じで実装することができます。

  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         MainFragment fragment = new MainFragment();  
  8.   
  9.         fragment.setOnOkBtnClickListener(new MainFragment.OnOkBtnClickListener() {  
  10.   
  11.             @Override  
  12.             public void onOkClicked() {  
  13.                 // TODO Auto-generated method stub  
  14.   
  15.             }  
  16.         });  
  17.   
  18.         getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();  
  19.     }  
  20. }  
  1. public class MainFragment extends Fragment {  
  2.   
  3.     public interface OnOkBtnClickListener {  
  4.         public void onOkClicked();  
  5.     }  
  6.   
  7.     private OnOkBtnClickListener mListener;  
  8.       
  9.     public void setOnOkBtnClickListener(OnOkBtnClickListener l) {  
  10.         mListener = l;  
  11.     }  
  12.       
  13.     @Override  
  14.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  15.         Button okBtn = new Button(inflater.getContext());  
  16.         okBtn.setOnClickListener(new View.OnClickListener() {  
  17.               
  18.             @Override  
  19.             public void onClick(View v) {  
  20.                 if(mListener != null) {  
  21.                     mListener.onOkClicked();  
  22.                 }  
  23.             }  
  24.         });  
  25.           
  26.         return okBtn;  
  27.     }  
  28. }  
このコードには問題があります。
バックグラウンドにある Fragment はメモリが足りなくなると、システムによって破棄され、必要になったときにシステムによって再生成されることがあるため、その際 setOnOkBtnClickListener() 部分は呼ばれないのです。

そのため、システムによって再生成された場合、コールバックを受け取れなくなるということが起こりえます。

以前のエントリで書きましたが、レイアウトで定義した(<fragment> で定義した)Fragment は FragmentTransaction の対象にしてはいけないため、上記のコードの MainFragment が onCreate() 内で生成されるのではなく、レイアウトXMLファイルで定義されている場合は、問題になることは(たぶん)あまりないと思います。


ただ、いずれにしてもこの実装はさけた方が懸命です。 (レイアウトで定義した Fragment を使っているとはいえ、Android UI Cookbook で Activity 側から fragment にリスナーをセットするコードを載せてしまっているので、動的に生成した Fragment には応用しないでください。ごめんなさい。)

で、どうするかというと、Activity 自体にリスナーを実装するようにします。
(私はあまりこの書き方は好きじゃないのですが、Fragment がシステムから再生成されうるのでしょうがないです。。。)

  1. public class MainActivity2 extends Activity implements MainFragment.OnOkBtnClickListener {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         MainFragment fragment = new MainFragment();  
  8.         getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();  
  9.     }  
  10.   
  11.     @Override  
  12.     public void onOkClicked() {  
  13.         // TODO Auto-generated method stub  
  14.           
  15.     }  
  16. }  
  1. public class MainFragment2 extends Fragment {  
  2.   
  3.     public interface OnOkBtnClickListener {  
  4.         public void onOkClicked();  
  5.     }  
  6.   
  7.     private OnOkBtnClickListener mListener;  
  8.   
  9.     @Override  
  10.     public void onAttach(Activity activity) {  
  11.         super.onAttach(activity);  
  12.   
  13.         if (activity instanceof OnOkBtnClickListener == false) {  
  14.             throw new ClassCastException("activity が OnOkBtnClickListener を実装していません.");  
  15.         }  
  16.           
  17.         mListener = ((OnOkBtnClickListener) activity);  
  18.     }  
  19.   
  20.     @Override  
  21.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  22.         Button okBtn = new Button(inflater.getContext());  
  23.         okBtn.setOnClickListener(new View.OnClickListener() {  
  24.   
  25.             @Override  
  26.             public void onClick(View v) {  
  27.                 if (mListener != null) {  
  28.                     mListener.onOkClicked();  
  29.                 }  
  30.             }  
  31.         });  
  32.   
  33.         return okBtn;  
  34.     }  
  35. }  
この場合、Fragment がシステムから再生成されたときも onAttach() を通るので、リスナーがセットされないということにはなりません。
Fragment が Activity の実装に依存しないので、複数の Activity で Fragment を使い回すときに便利です。


Fragment が必ず特定の Activity にしかアタッチされない場合は、次の方法も使えます。

  1. public class MainActivity3 extends Activity {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         MainFragment fragment = new MainFragment();  
  8.         getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "MainFragment").commit();  
  9.     }  
  10.   
  11.     public void onOkClicked() {  
  12.         // TODO Auto-generated method stub  
  13.           
  14.     }  
  15. }  
  1. public class MainFragment3 extends Fragment {  
  2.   
  3.     @Override  
  4.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  5.         Button okBtn = new Button(inflater.getContext());  
  6.         okBtn.setOnClickListener(new View.OnClickListener() {  
  7.   
  8.             @Override  
  9.             public void onClick(View v) {  
  10.                 try {  
  11.                     MainActivity3 activity = (MainActivity3) getActivity();  
  12.                     activity.onOkClicked();  
  13.                 } catch (ClassCastException e) {  
  14.                     throw new ClassCastException("activity が OnOkBtnClickListener を実装していません.");  
  15.                 }  
  16.             }  
  17.         });  
  18.   
  19.         return okBtn;  
  20.     }  
  21. }  
これが一番コードが短くてすみます。



これがベスト!という実装方法がないんですよねー。。。そもそも Fragment のコンストラクタは空コンストラクタでないとダメだよとかって制限があるのに、作れてしまったりするというがフレームワーク的にどうなのよ、と。
しかもシステムに再生成されない限りは普通に動いちゃうので、ぜったいはまるだろこれ、って感じなのです。



0 件のコメント:

コメントを投稿