はじめに
Android Studio(java) でアプリを開発していて、
「ダイアログのボタンクリックイベントをコールバックして呼び出し元のフラグメントで処理させたい」という場面がありました。
もう少し具体的に言うと、ダイアログでAPI通信+画面遷移の処理を実装したところスレッドが異なるためか画面遷移ができない、ということがありコールバックを実装しました。
こんな感じのエラーが出た Android IllegalStateException: fragment not attached to activity
そこでコールバックを利用して処理を実現できたので、その際学んだ内容を書きます。
作成したサンプルアプリの概要
アクティビティにボタンがあるフラグメントを表示し、ボタンを押すとダイアログを表示。ダイアログOKボタンを押すとボタンのテキストを変更する。
だけのシンプルなアプリ
- フラグメントを表示
- ボタンをクリックする



実装コード
layoutファイル
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/fragment_sample"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_sample.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout_sample"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SampleFragment">
<Button
android:id="@+id/bt_show_dialog"
android:layout_width="142dp"
android:layout_height="96dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="ダイアログ表示"/>
</androidx.constraintlayout.widget.ConstraintLayout>
javaファイル
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Activityにフラグメントをセット
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_sample, new SampleFragment())
.commit();
}
}
※ボタンを押した後に呼ばれる箇所を呼び出される順番で表記
SampleFragment.java
// インターフェースを実装するためにimplementsする
public class SampleFragment extends Fragment implements SampleDialogFragment.SampleDialogListener {
// ダイアログ表示のボタン要素をグローバル変数として定義
private Button button;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_sample, container, false);
// アクションバーにタイトルをセット
MainActivity activity = (MainActivity) getActivity();
activity.setTitle("ダイアログコールバックサンプル");
// ボタン要素をレイアウトから取得
button = view.findViewById(R.id.bt_show_dialog);
// ボタンクリックされたときの処理・・・①
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// フラグメントマネージャーを取得
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
// SampleDialogFragmentのインスタンスオブジェクトを生成
SampleDialogFragment dialogFragment = SampleDialogFragment.newInstance();
// SampleDialogFragmentのオブジェクトに呼び出し元のSampleFragmentオブジェクトをセット
dialogFragment.setTargetFragment(SampleFragment.this, 0);
// ダイアログを表示
dialogFragment.show(fragmentManager, "");
}
});
return view;
}
// コールバックされて実行される処理・・・⑤
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
// ボタンに表示されている文字を変更
button.setText("コールバック成功");
}
}
SampleDialogFragment.java
public class SampleDialogFragment extends DialogFragment {
// インスタンスを生成するメソッド
public static SampleDialogFragment newInstance() {
return new SampleDialogFragment();
}
// イベントのコールバックを受け取るためのインターフェースを実装
public interface SampleDialogListener {
void onDialogPositiveClick(DialogFragment dialog);
}
// クリックイベント発火を伝えるために使用するインターフェースインスタンスを定義
private SampleDialogListener listener;
// onAttach()で呼び出し元の親フラグメントがインターフェースを実装しているかを検証
// onAttach(): フラグメントのライフサイクルで最初に呼ばれるメソッドであり、
// フラグメントがアクティビティと関連づけられたときに一度だけ呼び出される。contextには所属親アクティビティが入っている・・・②
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
// 親フラグメントにイベントを送信できるように呼び出し元であるSampleFragmentオブジェクトを取得し、
// listenerのインスタンスを生成する
listener = (SampleDialogListener) getTargetFragment();
} catch (ClassCastException e) {
// 親フラグメントがインターフェースを実装していない場合は例外を投げる
throw new ClassCastException(getTargetFragment().toString() + "はインターフェースを実装していません");
}
}
// ダイアログを生成するonCreateDialog
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// ダイアログを生成する・・・③
return new AlertDialog.Builder(getActivity())
.setTitle("確認")
.setMessage("コールバックを開始しますか?")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// 処理を親のフラグメントにコールバックする・・・④
listener.onDialogPositiveClick(SampleDialogFragment.this);
}
})
.setNegativeButton("キャンセル", null) // キャンセルボタンでは何もしないためnull
.create();
}
}
最後に
まだ理解が浅い部分、多分にありますが非常に勉強になりました。
間違いなどあればコメントいただけると幸いです。
参考資料
非常に助かりました!ありがとうございました!