質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Q&A

解決済

1回答

5744閲覧

RecyclerView.AdapterのonBindViewHolder内でnotifyDataSetChangedに対するエラーの回避策について

jun74

総合スコア338

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

0グッド

0クリップ

投稿2019/08/20 15:37

編集2019/08/20 16:28

前提・実現したいこと

RecyclerView.AdapterのonBindViewHolder内でnotifyDataSetChangedに対するエラーの回避策について知りたいです。
Collections.sort(AlarmList)で、RecyclerViewに設定するArrayListの並び順がスイッチを押されることで変わることは確認しました。
その後、リサイクルビュー更新「notifyDataSetChanged()」で更新しようとしたところ、エラーが発生しました。

以下のページに同様の事象が報告されています。
Android RecyclerView : notifyDataSetChanged() IllegalStateException
具体的にどのように記述して対応すれば良いのでしょうか?

発生している問題・エラーメッセージ

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.a.b, PID: 10998 java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling android.support.v7.widget.RecyclerView{cf244b5 VFED..... .F....ID 0,125-1080,1727 #7f08006e app:id/lv_alarm}, adapter:com.a.b.AlarmsAdapter@d14974a, layout:android.support.v7.widget.LinearLayoutManager@51926bb, context:com.a.b.MainActivity@c1f5cf9 at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2880) at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:5281) at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:11997) at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:7070) at com.a.b.AlarmsAdapter.alarm_switch_upd(AlarmsAdapter.java:200)

該当のソースコード

java

1public class AlarmsAdapter extends RecyclerView.Adapter<AlarmsAdapter.ItemViewHolder> { 2・・・ 3 @Override 4 public void onBindViewHolder(final ItemViewHolder holder, final int position) { 5 //行の値設定 6 holder.sw_alarm.setChecked(AlarmList.get(position).m_sw_alarm); 7 holder.t_gozengogo.setText(AlarmList.get(position).m_t_gozengogo); 8 holder.t_time.setText(AlarmList.get(position).m_t_time); 9 holder.t_ampm.setText(AlarmList.get(position).m_t_ampm); 10 holder.t_alarm_name.setText(AlarmList.get(position).m_t_alarm_name); 11 holder.t_week.setText(AlarmList.get(position).m_t_week); 12 13 //アラームスイッチ変更 14 holder.sw_alarm.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 15 @Override 16 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 17 if(holder.sw_alarm.isChecked()) { 18 //mSwitch : Off -> On の時の処理 19 20 // コンテキスト取得 21 context = buttonView.getContext(); 22 //アラームスイッチ更新 23 alarm_switch_upd(holder.sw_alarm.isChecked() ,position); 24 } else { 25 //mSwitch : On -> Off の時の処理 26 27 // コンテキスト取得 28 context = buttonView.getContext(); 29 //アラームスイッチ更新 30 alarm_switch_upd(holder.sw_alarm.isChecked() ,position); 31 } 32 //アーリーリスト再ソート 33 Collections.sort(AlarmList); 34 35 //リサイクルビュー更新 36 notifyDataSetChanged(); 37 } 38 }); 39 } 40

補足情報(FW/ツールのバージョンなど)

Android Studio3.4
API14から28対象ターゲット28

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

notifyDataSetChanged によって RecyclerList が再表示を行おうと 全データに対し onBindViewHolder を呼び出し, スイッチを On にした行で setChecked を実行した際に そのスイッチに残っている OnCheckedChangeListener によって再度 notifyDataSetChanged が呼ばれ... と無限ループするので, 例外が発生するということですね.

解決策としては, setChecked() の前にリスナを消すとなっています.

//行の値設定 holder.sw_alarm.setOnCheckedChangeListener(null); //追加 holder.sw_alarm.setChecked(AlarmList.get(position).m_sw_alarm); :

ついでですが,

java

1 if(holder.sw_alarm.isChecked()) { 2 //mSwitch : Off -> On の時の処理 3 // コンテキスト取得 4 context = buttonView.getContext(); 5 //アラームスイッチ更新 6 alarm_switch_upd(holder.sw_alarm.isChecked() ,position); 7 } else { 8 //mSwitch : On -> Off の時の処理 9 // コンテキスト取得 10 context = buttonView.getContext(); 11 //アラームスイッチ更新 12 alarm_switch_upd(holder.sw_alarm.isChecked() ,position); 13 }

この if 文は Off->On も On->Off も同じなのですが, if 文の必要があるのでしょうか.
また holder.sw_alarm.isChecked() はパラメータに isClicked がありますし, context は何に使うのか分かりません.

単純に

java

1//アラームスイッチ更新 2alarm_switch_upd(isChecked, position);

で済むように思います.


データベースの更新を MainActivity に移す例(カスタムリスナ)です:

AlarmsAdapter にリスナ関係を追加します.

java

1 static interface AlarmSwitchListener { 2 void onAlermSwitchChanged(int id, boolean isChecked); 3 } 4 private AlarmSwitchListener alarmSwitchListener; 5 void setAlarmSwitchListener(AlarmSwitchListener l) { 6 alarmSwitchListener = l; 7 }

alarm_switch_upd の呼び出しをリスナの呼び出しに変更します.
その際, alarm_switch_upd 内にある sw_alarm の設定はこちらに抜き出しておきます.

java

1 //アラームスイッチ変更 2 holder.sw_alarm.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 3 @Override 4 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 5 AlarmList.get(position).sw_alarm = isChecked; 6 if(alarmSwitchListener != null) { //リスナが登録されていたら呼び出す 7 alarmSwitchListener.onAlermSwitchChanged(rowData.id, isChecked); 8 } 9 } 10 });

MainActivity でリスナを実装, Adapter 作成時に自身をリスナとして登録します.

public class MainActivity extends AppCompatActivity implements AlarmsAdapter.AlarmSwitchListener { : @Override protected void onCreate(Bundle savedInstanceState) { : AlarmsAdapter adapter = new AlarmsAdapter( ... ); adapter.setAlarmSwitchListener(this); : } : //アラームスイッチ更新 @Override public void onAlermSwitchChanged(int id, boolean isChecked) { //alarm_switch_upd にあった内容です. //dbオープン MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this); //MainActivity は Context としても使えます try{ mydb = helper.getWritableDatabase(); }catch(SQLiteException e){ //異常終了 return; } //アラームテーブルのスイッチ更新 String update_table = "update alarm_table " + "set switch = '" + isChecked + "'" + " where _id = " + id; mydb.execSQL(update_table); }

投稿2019/08/20 16:54

編集2019/08/21 15:06
jimbe

総合スコア12646

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jun74

2019/08/21 13:15

いつもありがとうございます。 オンオフで分ける必要はないので合わせて直しました。 contextはalarm_switch_upd(isChecked, position);内の //dbオープン MySQLiteOpenHelper helper = new MySQLiteOpenHelper(context.getApplicationContext()); 上記で必要になったのですが、contextは何者なのかが、イマイチ良く分かってない感じではあります。 MainActivityだと上記の「context.」は省略できたのですが。。
jimbe

2019/08/21 14:33

なるほど, 確かに Adapter の最後のほうにデータベースの更新がありました. となりますと, また煩くて申し訳ないですが, その alarm_switch_upd は MainActivity に移したほうが良いかと思います. 移す方法は, 以前ご質問されていた [RecyclerView.Adapter内で判定している、RecyclerView内のswitchの変更判定を呼び元のActivityで判定したい]( https://teratail.com/questions/206795 ) のカスタムリスナーです.
jun74

2019/08/21 14:36

更新は出来てますが致命的な問題があるのでしょうか? データベース更新のみ移せば問題ないですかね?
jimbe

2019/08/21 14:45

動作はすると思いますが, これは設計の問題です. Adapter は通常 View のためのデータを保持するモノで, 画面操作の結果をデータベースに保存する機能というのは, Adapter の役割上からは越権行為と見られます. スイッチの操作によってデータベースを更新するのでしたら, Adapter に「スイッチが操作された場合に呼ばれるリスナインターフェース」を作り, それを MainActivity 等に implements させて Adapter に登録, スイッチが操作されたらそのリスナメソッドが実行され, context を得てデータベースを更新する... という形にしますと, Adapter の役割がはっきりします.
jun74

2019/08/21 15:04

越権行為というのは、なるほどですね。 RecyclerViewの動きが全てAdapter内に収まっているため、スイッチ変更時のデータベース更新のみリスナーインターフェースでMainActivityに移ると自分的にはソースが追いづらくなるイメージなんですが。。。悩みます。。
jimbe

2019/08/21 15:09

そうですね, 多数の画面がそれぞれデータベースを更新するような場合に問題となり易いということですので, 精々数箇所しか無い程度でどこまでやるかということはあると思います.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問