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
1public class CityViewModel extends AndroidViewModel { 2 3 private MutableLiveData<List<Address>> address; 4 private CityRepository repository; 5 6 public CityViewModel(@NonNull Application application) { 7 super(application); 8 repository = new CityRepository(); 9 } 10 11 public LiveData<List<Address>> getAddress() { 12 13 if(address == null) { 14 15 address = new MutableLiveData<List<Address>>(); 16 } 17 return address; 18 } 19 20 //取得したデータを設定 21 public void searchAddress(String zipcode) { 22 23 //CityRepository.executorService.execute(() -> { 24 25 address.postValue(repository.getAddress(zipcode)); 26 //}); 27 } 28 29 public void clearAddress() { 30 31 //CityRepository.executorService.execute(() -> { 32 address.postValue(repository.clearAddress()); 33 //}); 34 } 35}
<Model箇所>
CityRepository.java
1public class CityRepository { 2 3 private ApiInterface service; 4 private List<Address> address; 5 private static final int NUMBER_OF_THREADS = 4; 6 public static final ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS); 7 8 public CityRepository() { 9 10 //APIインスタンスを生成 11 service = new Retrofit.Builder() 12 .baseUrl("https://zipcloud.ibsnet.co.jp/") 13 .addConverterFactory(GsonConverterFactory.create()) 14 .build() 15 .create(ApiInterface.class); 16 } 17 18 public List<Address> getAddress(String zipcode) { 19 20 //APIを呼び出し 21 service.getCity(zipcode).enqueue(new Callback() { 22 23 //リクエスト成功時 24 @Override 25 public void onResponse(Call call, Response response) { 26 27 Log.i("message2", "Successed to request"); 28 29 //通信結果を受け取る 30 City city = (City) response.body(); 31 address = city.getResults(); 32 } 33 34 @Override 35 public void onFailure(Call call, Throwable t) { 36 37 Log.i("message2", "error:" + t); 38 } 39 }); 40 41 return address; 42 } 43 44 public List<Address> clearAddress() { 45 46 return new ArrayList<Address>(); 47 } 48 49}
<View箇所>
MainFragment.java
1public class MainFragment extends Fragment { 2 3@Override 4 public View onCreateView(LayoutInflater inflater, ViewGroup container, 5 Bundle savedInstanceState) { 6 // Inflate the layout for this fragment 7 //return inflater.inflate(R.layout.fragment_main, container, false); 8 9 //DataBindingインスタンス生成 10 binding = DataBindingUtil.inflate(inflater,R.layout.fragment_main, container, false); 11 //DataBindingに紐付け 12 binding.setLifecycleOwner(getActivity()); 13 return binding.getRoot(); 14 } 15 16 @Override 17 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 18 super.onViewCreated(view, savedInstanceState); 19 20 //ViewModelインスタンス生成 21 viewModel = new ViewModelProvider(requireActivity()).get(CityViewModel.class); 22 23 //SearchViewにリスナ設定 24 binding.svZipcode.setOnQueryTextListener(new searchViewListener()); 25 26 //RecyclerViewの表示設定 27 binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 28 binding.recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),LinearLayoutManager.VERTICAL)); 29 adapter = new CityAdapter(new CityAdapter.CountryDiff(),getActivity()); 30 binding.recyclerView.setAdapter(adapter); 31 } 32 33 @Override 34 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 35 super.onActivityCreated(savedInstanceState); 36 37 //LiveDataを監視 38 viewModel.getAddress().observe(getViewLifecycleOwner(),new Observer<List<Address>>() { 39 @Override 40 public void onChanged(List<Address> addresses) { 41 42 //変更があるたびに、差分データを更新 43 adapter.submitList(addresses); 44 } 45 }); 46 } 47 48 //SearchViewのリスナークラス 49 private class searchViewListener implements SearchView.OnQueryTextListener { 50 51 @Override 52 public boolean onQueryTextSubmit(String query) { 53 54 if(query.length() == 7) { 55 56 //検索処理 57 //viewModel.searchAddress(query); 58 } 59 return false; 60 } 61 62 @Override 63 public boolean onQueryTextChange(String newText) { 64 65 if(newText.length() == 7) { 66 //検索処理 67 viewModel.searchAddress(newText); 68 69 } else { 70 //空にする 71 viewModel.clearAddress(); 72 } 73 return false; 74 } 75 } 76}
CityAdapter.java
1public class CityAdapter extends ListAdapter<Address, RecyclerView.ViewHolder> { 2 3 private LifecycleOwner owner; 4 5 public CityAdapter(@NonNull DiffUtil.ItemCallback<Address> diffCallback,LifecycleOwner _owner) { 6 super(diffCallback); 7 owner = _owner; 8 } 9 10 //ビューホルダーオブジェクトを生成 11 @NonNull 12 @Override 13 public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 14 15 //DataBindingインスタンス生成 16 ListItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.list_item, parent, false); 17 18 //RootViewを取得 19 View rootView = binding.getRoot(); 20 21 //RootViewにbindingを設定 22 rootView.setTag(binding); 23 24 return new RecyclerView.ViewHolder (rootView) {}; 25 } 26 27 //データの割り当て 28 @Override 29 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 30 31 ListItemBinding binding = (ListItemBinding)holder.itemView.getTag(); 32 binding.setLifecycleOwner(owner); 33 binding.setAddress(getItem(position)); 34 binding.executePendingBindings(); 35 } 36 37 //変化を検知するインナークラス 38 public static class CountryDiff extends DiffUtil.ItemCallback<Address> { 39 40 //変化後、インスタンスが同じかどうかを判定 41 @Override 42 public boolean areItemsTheSame(@NonNull Address oldItem, @NonNull Address newItem) { 43 return oldItem == newItem; 44 } 45 46 //変化後、インスタンスの中身が同じかどうかを判定 47 @Override 48 public boolean areContentsTheSame(@NonNull Address oldItem, @NonNull Address newItem) { 49 return oldItem.getAddress3().equals(newItem.getAddress3()); 50 } 51 } 52}
fragment_main.xml
1<?xml version="1.0" encoding="utf-8"?> 2<layout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools"> 5 6 <androidx.constraintlayout.widget.ConstraintLayout 7 android:layout_width="match_parent" 8 android:layout_height="match_parent" 9 tools:context=".view.MainActivity"> 10 11 <androidx.appcompat.widget.SearchView 12 android:id="@+id/sv_zipcode" 13 android:layout_width="match_parent" 14 android:layout_height="wrap_content" 15 app:layout_constraintEnd_toEndOf="parent" 16 app:layout_constraintStart_toStartOf="parent" 17 app:layout_constraintTop_toTopOf="parent" 18 app:queryHint="郵便番号" 19 android:iconifiedByDefault="false" /> 20 21 <androidx.recyclerview.widget.RecyclerView 22 android:id="@+id/recyclerView" 23 android:layout_width="match_parent" 24 android:layout_height="0dp" 25 app:layout_constraintBottom_toBottomOf="parent" 26 app:layout_constraintEnd_toEndOf="parent" 27 app:layout_constraintHorizontal_bias="0.0" 28 app:layout_constraintStart_toStartOf="parent" 29 app:layout_constraintTop_toBottomOf="@id/sv_zipcode" 30 app:layout_constraintVertical_bias="0.0" /> 31 32 </androidx.constraintlayout.widget.ConstraintLayout> 33</layout>
list_item.xml
1<?xml version="1.0" encoding="utf-8"?> 2<layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 4 <data> 5 <variable 6 name="address" 7 type="com.example.mvvmtrialapp2.model.Address" /> 8 9 </data> 10 11 <androidx.appcompat.widget.LinearLayoutCompat 12 android:layout_width="match_parent" 13 android:layout_height="wrap_content" 14 android:paddingLeft="10dp" 15 android:orientation="vertical"> 16 17 <TextView 18 android:id="@+id/tv_address1" 19 android:text="@{address.address1}" 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content" 22 android:textSize="25sp" /> 23 24 <TextView 25 android:id="@+id/tv_address2" 26 android:text="@{address.address2}" 27 android:layout_width="wrap_content" 28 android:layout_height="wrap_content" 29 android:textSize="25sp" /> 30 31 <TextView 32 android:id="@+id/tv_address3" 33 android:text="@{address.address3}" 34 android:layout_width="wrap_content" 35 android:layout_height="wrap_content" 36 android:textSize="25sp" /> 37 38 </androidx.appcompat.widget.LinearLayoutCompat> 39</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
回答1件