پیاده سازی معماری MVVM در اندروید – بخش دوم
در مطلب قبلی به شرح الگوی معماری MVVM پرداخته شد و در این مطلب قصد داریم به نحوه پیاده سازی آن بپردازیم، با ما همراه باشید.
همانطور که در نمودار مشاهده می کنید، View نه تنها دستورات، بلکه چرخه عمر (Life cycle) خود را هم برای ViewModel ارسال می کند. زیرا این عملیاتی است که باید توسط کاربر آغاز شود و به خاطر اقدامات کاربر وضعیت صفحه نمایش تغییر پیدا می کند. لازم است فراخوانی های مناسبی به VM داشته باشیم.
به عنوان نمونه ممکن است لازم باشد هر بار که کاربر به اکتیویتی برمی گردد اطلاعاتی را دانلود کنید، برای این کار باید متد دانلود داده را برای ()onResume فراخوانی کنید.
ProfileActivity را تغییر می دهیم:
private ProfileViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityProfileBinding binding = DataBindingUtil.setContentView(this, LAYOUT_ACTIVITY);
viewModel = new ProfileViewModel(this);
binding.setViewModel(viewModel);
}
@Override
protected void onResume() {
super.onResume();
viewModel.onResume();
}
و همان متد را در ProfileViewModel تعریف می کنیم:
public void onResume() {
isLoading.set(this.user.get() == null);
userRepo.getUser(this::onUserLoaded);
}
با این کار داده ها هر بار که کاربر به آن پنجره برمی گردد آپدیت می شوند و اگر اطلاعات قبلا دریافت نشده باشد، وضعیت مناسب نمایش داده خواهد شد، به همین سادگی!
همین کار را باید با ساید متدها نیز انجام داد. البته با هر بار ساخت VM تعیین این موضوع دشوار است، لذا باید این منطق را برای کلاس های اصلی پیاده سازی کرد که BindingActivity و ActivityViewModel نام دارند:
public abstract class BindingActivity extends AppCompatActivity {
…
@Override
protected void onStart() {
super.onStart();
viewModel.onStart();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
viewModel.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onResume() {
super.onResume();
viewModel.onResume();
}
@Override
public void onBackPressed() {
if (!viewModel.onBackKeyPress()) {
super.onBackPressed();
}
}
//….other methods
}
public abstract class ActivityViewModel extends BaseObservable {
…
public void onStart() {
//Override me!
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//Override me!
}
public void onResume() {
//Override me!
}
public void onBackPressed() {
//Override me!
}
//….other methods
}
در این مرحله تنها کار لازم اورراید کردن متدهای مناسب برای واکنش به تغییرات خاص می باشد.
با هر بار ساخت یک اکتیویتی، نیازی به ساخت bindings و VM connection نمی باشد و این منطق را می توان برای کلاس اصلی اما با تغییر متد ()onCreate پیاده سازی کرد. همین کار را هنگام ساخت اکتیویتی برای VM نیز انجام می دهیم و چندین متد انتزاعی برای پارامترهای لازم می افزاییم:
private AppCompatActivity binding;
private ActivityViewModel viewModel;
public abstract ActivityViewModel onCreate();
public abstract @IdRes int getVariable();
public abstract @LayoutRes int getLayoutId();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bind();
}
public void bind() {
binding = DataBindingUtil.setContentView(this, getLayoutId());
this.viewModel = viewModel == null ? onCreate() : viewModel;
binding.setVariable(getVariable(), viewModel);
binding.executePendingBindings();
}
تنها کار باقی مانده ساخت یک کلاس اصلی و پایه برای ActivityViewModel می باشد، تنها باید یک کپی از اکتیوتی اضافه کنید و intent بسازید:
public abstract class ActivityViewModel extends BaseObservable {
protected Activity activity;
public ActivityViewModel(Activity activity) {
this.activity = activity;
}
public Activity getActivity() {
return activity;
}
//...lifecycle methods
}
کار با اکتیویتی ها تمام شد و هم اکنون ابزار لازم برای شرح منطق را در اختیار داریم. ViewModel و binding در اکتیوتی اصلی به صورت ضمنی تایپ شده اند و همین امر کار با آنها را پیچیده می کند و هر بار محبور به دریافت نوع آنها هستید، لذا باید کلاس ها را به صورت زیر خلاصه بندی کنیم:
public abstract class BindingActivity<B extends ViewDataBinding, VM extends ActivityViewModel> extends AppCompatActivity {
private B binding;
private VM viewModel;
public B getBinding() {
return binding;
}
}
public abstract class ActivityViewModel<A extends AppCompatActivity>
extends BaseObservable {
protected A activity;
public ActivityViewModel(A activity) {
this.activity = activity;
}
public A getActivity() {
return activity;
}
}
با این اقدامات این کلاس اکتیویتی نتیجه خواهد شد:
public class ProfileActivity
extends BindingActivity<ActivityProfileBinding, ProfileViewModel> {
@Override
public ProfileViewModel onCreate() {
return new ProfileViewModel(this);
}
@Override
public int getVariable() {
return BR.viewModel;
}
@Override
public int getLayoutResources() {
return R.layout.activity_profile;
}
}
()getVariable باید نام متغیر را برگرداند که در تگ data-variable در فایل XML تعیین شده است، ()getLayout هم باید همان xml را برگرداند. شایان ذکر است که ProfileViewModel باید ویژگی های خود را از ActivityViewModel به ارث ببرد.
پیاده سازی این کلاس ها برای فرگمنت ها کمی متفاوت بوده و جزئیات آن در این مقاله مورد بررسی قرار نمی گیرد، زیرا مفهوم کلی برای تمامی آنها مشابه است.
در مطلب بعدی به شرح مثال های برای پیاده سازی الگوی MVVM می پردازیم، با ما همراه باشید.