Firestoreの非同期処理をMVVMモデルで実装を分離する
Firestoreを利用したデータベースへのデータの設定と取得は非同期処理となります。
Androidにおいて、ActivityやFragmentにFirestoreの処理を実装をしてしまうと、画面更新処理とデータ更新処理が合わさってしまい処理が複雑になりがちです。
そこで、MVVMモデル化する事により画面処理とデータ処理を分離する事で、クリーンな実装になることを紹介したいと思います。
紹介する実装の画面イメージ
ActivityにFragmentがあり、タイトルとサブタイトルの2つのEditTextの内容をFirestoreへ設定と取得を行います。
変更前の内容
まず、MVVMモデル化する前のFirestoreの処理がFragmentにまとまってしまっている実装を紹介します。
変更前クラス図
ItemDetailFragmentにて、画面処理とBrandクラスのカスタムオブジェクトを利用してFirestoreへのデータ処理を行います。
変更前実装
ItemDetailFragment内に画面処理とデータ処理が含まれているので、ぱっと見で分かりづらくなっています。
public class ItemDetailFragment extends Fragment {
private String name = "銘柄名";
private EditText mTitle;
private EditText mSubTitle;
private FirebaseFirestore mDb;
private CollectionReference mCollectionRef;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_item_detail, container, false);
mTitle = view.findViewById(R.id.detail_title);
mSubTitle = view.findViewById(R.id.detail_subtitle);
mDb = FirebaseFirestore.getInstance();
mCollectionRef = mDb.collection("BrandList");
mCollectionRef.whereEqualTo("title", name).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().getDocuments().isEmpty()) return;
Map<String, Object> brandData = (Map<String, Object>) task.getResult().getDocuments().get(0).getData();
String title = (String) brandData.get("title");
String subTitle = (String) brandData.get("subTitle");
mTitle.setText(title);
mSubTitle.setText(subTitle);
} else {
Log.d("FIREBASE", task.getException().toString());
}
}
});
return view;
}
/**
* Toobarのチェックボタンを押下時に呼ばれる
*/
public void confirmEditData() {
String title = mTitle.getText().toString();
String subTitle = mSubTitle.getText().toString();
Brand brand = new Brand(title, subTitle);
mCollectionRef.whereEqualTo("title", brand.getTitle()).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().getDocuments().isEmpty()) {
mCollectionRef.add(brand);
} else {
String id = task.getResult().getDocuments().get(0).getId();
mCollectionRef.document(id).set(brand);
}
} else {
Log.d("FIREBASE", task.getException().toString());
}
}
});
}
変更後の内容
MVVMモデルのアーキテクチャを適用して、画面処理とデータ処理を分けてクリーンな実装に変えます。
変更後クラス図
ItemDetailFragmentでは、画面更新処理とBrandクラスのオブジェクトを設定し、DataRepositoryクラスでFirestoreへのデータ操作を行います。
変更後実装
ItemDetailFragmentでは画面処理、ItemDetailViewModelでバインディングして、DataRepositoryでデータ処理の実装が分離されたことにより、クリーンな実装となりました。
public class ItemDetailFragment extends Fragment {
private String name = "銘柄名";
private EditText mTitle;
private EditText mSubTitle;
private ItemDetailViewModel mItemDetailViewModel
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_item_detail, container, false);
mTitle = view.findViewById(R.id.detail_title);
mSubTitle = view.findViewById(R.id.detail_subtitle);
mItemDetailViewModel = new ViewModelProvider(this).get(ItemDetailViewModel.class);
mItemDetailViewModel.getBrand(name).observe(getViewLifecycleOwner(), brand -> {
mTitle.setText(brand.getTitle());
mSubTitle.setText(brand.getSubTitle());
});
return view;
}
/**
* Toobarのチェックボタンを押下時に呼ばれる
*/
public void confirmEditData() {
String title = mTitle.getText().toString();
String subTitle = mSubTitle.getText().toString();
Brand brand = new Brand(title, subTitle);
mItemDetailViewModel.updateBrand(brand);
}
public class ItemDetailViewModel extends AndroidViewModel {
private PonshuRepository mRepository;
public ItemDetailViewModel(@NonNull Application application) {
super(application);
mRepository = PonshuRepository.getInstance();
}
public LiveData<Brand> getBrand(String name) {
return mRepository.getBrand(name);
}
public void updateBrand(Brand brand) {
mRepository.updateBrand(brand);
}
}
public class PonshuRepository {
private static PonshuRepository mInstance;
private FirebaseFirestore mDb;
private CollectionReference mCollectionRef;
private MutableLiveData<Brand> mBrand;
public static PonshuRepository getInstance() {
if (mInstance == null) {
mInstance = new PonshuRepository();
}
return mInstance;
}
private PonshuRepository() {
mDb = FirebaseFirestore.getInstance();
mCollectionRef = mDb.collection("BrandList");
}
public LiveData<Brand> getBrand(String name) {
if (mBrand == null) {
mBrand = new MutableLiveData<>();
}
mCollectionRef.whereEqualTo("title", name).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().getDocuments().isEmpty()) return;
Map<String, Object> brandData = (Map<String, Object>) task.getResult().getDocuments().get(0).getData();
String title = (String) brandData.get("title");
String subTitle = (String) brandData.get("subTitle");
Brand brand = new Brand(title, subTitle);
mBrand.setValue(brand);
} else {
Log.d("FIREBASE", task.getException().toString());
}
}
});
return mBrand;
}
public void updateBrand(Brand brand) {
mCollectionRef.whereEqualTo("title", brand.getTitle()).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().getDocuments().isEmpty()) {
mCollectionRef.add(brand);
} else {
String id = task.getResult().getDocuments().get(0).getId();
mCollectionRef.document(id).set(brand);
}
} else {
Log.d("FIREBASE", task.getException().toString());
}
}
});
}
さいごに
変更後の処理の方が合計ステップ数としては増えていますが、画面に配置するレイアウトやデータが増えたりすると、どんどん実装が増えていき複雑化していきます。
なので、このようにMVVMモデルのアーキテクチャを適用すると実装が増えていってもクリーンな実装が保てるようになると思います。
閲覧ありがとうございました!
さいごに、自分が個人開発で公開しているアプリの紹介です。
Discussion