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

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

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

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

解決済

【Android】Recyclerviewに、Retrofit2で取得したデータをリアルタイムで反映させたい

nomabe
nomabe

総合スコア1

Android

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

1回答

0評価

0クリップ

336閲覧

投稿2022/04/21 11:56

1.前提・実現したいこと

JavaでAndroidアプリ開発のMVVM設計を勉強中です。今は、郵便番号から町域名をリスト表示するサンプルプログラムを作っています。

以下の流れで実現させたいと考えています。

❶ Searchviewで7桁の郵便番号を入力
❷ ❶をもとにAPI通信(今回はZipcloudの郵便番号検索APIを使用)
❸ ❷で取得したデータをRecyclerviewに表示

因みに、ライブラリは以下を使用しています。

  • リストRetrofit2
  • リストViewmodel
  • リストLivedata
  • リストDatabinding

2.発生している問題

Searchview(onQueryTextChangeメソッドで実現)に、実際に存在する7桁の郵便番号を入力しても、一回でリアルタイムで反映されません。

7桁(1)▶︎6桁▶︎7桁(2)の順で入力すると、7桁(1)で入力した郵便番号に対応する住所がまず表示されてしまいます。

つまり、**1回分ずれてRecyclerviewにデータが反映されます。**今回はここを解決したいです。

3.該当のソースコード

<ViewModel箇所>

CityViewModel.java

public class CityViewModel extends AndroidViewModel { private MutableLiveData<List<Address>> address; private CityRepository repository; public CityViewModel(@NonNull Application application) { super(application); repository = new CityRepository(); } public LiveData<List<Address>> getAddress() { if(address == null) { address = new MutableLiveData<List<Address>>(); } return address; } //取得したデータを設定 public void searchAddress(String zipcode) { //CityRepository.executorService.execute(() -> { address.postValue(repository.getAddress(zipcode)); //}); } public void clearAddress() { //CityRepository.executorService.execute(() -> { address.postValue(repository.clearAddress()); //}); } }

<Model箇所>

CityRepository.java

public class CityRepository { private ApiInterface service; private List<Address> address; private static final int NUMBER_OF_THREADS = 4; public static final ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS); public CityRepository() { //APIインスタンスを生成 service = new Retrofit.Builder() .baseUrl("https://zipcloud.ibsnet.co.jp/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiInterface.class); } public List<Address> getAddress(String zipcode) { //APIを呼び出し service.getCity(zipcode).enqueue(new Callback() { //リクエスト成功時 @Override public void onResponse(Call call, Response response) { Log.i("message2", "Successed to request"); //通信結果を受け取る City city = (City) response.body(); address = city.getResults(); } @Override public void onFailure(Call call, Throwable t) { Log.i("message2", "error:" + t); } }); return address; } public List<Address> clearAddress() { return new ArrayList<Address>(); } }

<View箇所>

MainFragment.java

public class MainFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment //return inflater.inflate(R.layout.fragment_main, container, false); //DataBindingインスタンス生成 binding = DataBindingUtil.inflate(inflater,R.layout.fragment_main, container, false); //DataBindingに紐付け binding.setLifecycleOwner(getActivity()); return binding.getRoot(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //ViewModelインスタンス生成 viewModel = new ViewModelProvider(requireActivity()).get(CityViewModel.class); //SearchViewにリスナ設定 binding.svZipcode.setOnQueryTextListener(new searchViewListener()); //RecyclerViewの表示設定 binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); binding.recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),LinearLayoutManager.VERTICAL)); adapter = new CityAdapter(new CityAdapter.CountryDiff(),getActivity()); binding.recyclerView.setAdapter(adapter); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //LiveDataを監視 viewModel.getAddress().observe(getViewLifecycleOwner(),new Observer<List<Address>>() { @Override public void onChanged(List<Address> addresses) { //変更があるたびに、差分データを更新 adapter.submitList(addresses); } }); } //SearchViewのリスナークラス private class searchViewListener implements SearchView.OnQueryTextListener { @Override public boolean onQueryTextSubmit(String query) { if(query.length() == 7) { //検索処理 //viewModel.searchAddress(query); } return false; } @Override public boolean onQueryTextChange(String newText) { if(newText.length() == 7) { //検索処理 viewModel.searchAddress(newText); } else { //空にする viewModel.clearAddress(); } return false; } } }

CityAdapter.java

public class CityAdapter extends ListAdapter<Address, RecyclerView.ViewHolder> { private LifecycleOwner owner; public CityAdapter(@NonNull DiffUtil.ItemCallback<Address> diffCallback,LifecycleOwner _owner) { super(diffCallback); owner = _owner; } //ビューホルダーオブジェクトを生成 @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { //DataBindingインスタンス生成 ListItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.list_item, parent, false); //RootViewを取得 View rootView = binding.getRoot(); //RootViewにbindingを設定 rootView.setTag(binding); return new RecyclerView.ViewHolder (rootView) {}; } //データの割り当て @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { ListItemBinding binding = (ListItemBinding)holder.itemView.getTag(); binding.setLifecycleOwner(owner); binding.setAddress(getItem(position)); binding.executePendingBindings(); } //変化を検知するインナークラス public static class CountryDiff extends DiffUtil.ItemCallback<Address> { //変化後、インスタンスが同じかどうかを判定 @Override public boolean areItemsTheSame(@NonNull Address oldItem, @NonNull Address newItem) { return oldItem == newItem; } //変化後、インスタンスの中身が同じかどうかを判定 @Override public boolean areContentsTheSame(@NonNull Address oldItem, @NonNull Address newItem) { return oldItem.getAddress3().equals(newItem.getAddress3()); } } }

fragment_main.xml

<?xml version="1.0" encoding="utf-8"?> <layout 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"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".view.MainActivity"> <androidx.appcompat.widget.SearchView android:id="@+id/sv_zipcode" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:queryHint="郵便番号" android:iconifiedByDefault="false" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sv_zipcode" app:layout_constraintVertical_bias="0.0" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="address" type="com.example.mvvmtrialapp2.model.Address" /> </data> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="10dp" android:orientation="vertical"> <TextView android:id="@+id/tv_address1" android:text="@{address.address1}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" /> <TextView android:id="@+id/tv_address2" android:text="@{address.address2}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" /> <TextView android:id="@+id/tv_address3" android:text="@{address.address3}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="25sp" /> </androidx.appcompat.widget.LinearLayoutCompat> </layout>

4.自分で試したこと

  • リストログより、Zipcloudの郵便番号検索APIからデータの取得自体は出来ている。

▶︎Retrofit2周りが原因ではなさそう。

  • リスト【2.発生している問題】にもある通り、ユーザーのアクションから1回分ずれてRecyclerviewに表示される。

▶︎viewModel.searchAddress(Searchviewで入力された文字)でLivedataの値を変更する際に、反映の通知が遅れているためかなと考える。

5.補足情報

<開発環境>
Mac
Android Studio Bumblebee
Java 8

<エミュレータ>
Google Pixel 5(Android 12)
compileSdkVersion 31
minSdkVersion 16

良い質問の評価を上げる

以下のような質問は評価を上げましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

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

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

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

jimbe

2022/04/22 11:29

CityRepository.getAddress メソッドの最後の return address の直前にも Log を入れて、 "Successed to request" とどちらが先に表示されるか試しては如何でしょう。
nomabe

2022/04/22 13:10

アドバイスありがとうございます。試します。

まだ回答がついていません

会員登録して回答してみよう

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

ただいまの回答率
87.20%

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

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

質問する

関連した質問

同じタグがついた質問を見る

Android

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