MVP اندروید
در این مقاله قصد داریم تا یکی از Design pattern های بسیار پرکاربرد در اندروید به نام MVP را مورد بررسی قرار دهیم. MVP و یا Model View Presenter به روشی از کدنویسی اتلاق میشود که که در آن یک لایه واسط میان View و Model قرار میگیرد. در واقع در این روش دیگر پردازش ها در لایه View صورت نمیپذیرد و Model ها نیز تنها وظیفه نگهداری و تامین داده ها را برعهده دارند. پردازش و همچنین بروزرسانی هریک از این اجزا توسط presenter انجام میشود. این امر باعث میشود تا فرآیند Test پروژه با دقت و سرعت بهتری انجام شود.
مزیت های استفاده از MVP
فرآیند Test و مشکل یابی آسان تر انجام میشود
وقتی از MVP استفاده میشود, از سه لایه برای پیاده سازی برنامه ها استفاده میکنیم. به این ترتیب اعمال تغییرات نیز آسان تر انجام میشود و به تبع احتمال وقوع اشتباه و خطا کاهش مییابد. همچنین چون بخش های مختلف برنامه از هم جدا هستند در صورت بروز هرگونه مشکلی در هریک از بخش ها, یافتن مشکل و رفع آن به راحتی انجام میشود. همچنین این تفکیک بودن بخش ها باعث میشود تا فرآیند Unit Testing با دقت و تمرکز بیشتری بر روی Logic برنامه اعمال شود. در MVP تغییرات در Logic برنامه بسیار سریع تر و آسان تر است زیرا یک لایه به این عمل اختصاص یافته است.
Re-Usability و استفاده مجدد
وقتی بخش های اصلی برنامه از هم جدا باشند, به راحتی میتوانیم از کد های نوشته شده در موارد دیگر نیز استفاده کنیم. به این معنا که میتوانیم برای یک View چندین Presenter بنویسیم و از آنها استفاده کنیم. همین عمل باعث میشود تا فرآیند توسعه برنامه ها با سرعت بهتری انجام شود.
اکنون قصد داریم تا در قالب یک پروژه شیوه پیاده سازی نرم افزار با استفاده از MVP را بررسی کنیم. توجه داشته باشید که شیوه کد نوسی توسط هر برنامه نویس متناسب با تجربیات وی است. بنابراین ممکن است در بعضی از مقالات آموزشی روش های پیاده سازی دیگری را مشاهده کنید. در واقع MVP یک ساختار برای پروژه ها است و هر برنامه نویس متناسب با ابزار و توانایی های خود آن را پیاده سازی میکند.
ابتدا یک پروژه جدید میسازیم و سه package در آن ایجاد میکنیم:
همانطور که مشاهده میکنید سه لایه برنامه را در سه package قرار دادیم. اکنون لایه model را تکمیل میکنیم. در این لایه باید ساختار دادهای و کلاس های مورد نیاز برای تامین داده ها را پیاده سازی کنیم.
public class UserEntity { private String username; private String password; private String token; }
کلاس فوق مدل دادهای ما است. توجه داشته باشید که برای مختصر شدن کد ها از نوشتن متد های encapsulation صرف نظر کردهایم. سپس یک کلاس به نام ApiHandler ایجاد میکنیم تا با استفاده از retrofit و RxAndroid این اطلاعات را از یک API برای ما دریافت کند:
public class ApiHandler { public Observable<UserEntity> getUserByUsername(String username) { // get data with Retrofit and RxAndroid from https://api.zerotohero.ir } }
اکنون لایه view را پیاده سازی میکنیم. در این لایه قصد داریم تا پس از کلیک بر روی یک button اطلاعات از یک Edittext گرفته و پس از جستجو داده های آن نمایش داده شود. برای این کار یک interface ایجاد میکنیم و متد هایی که نیاز است presenter با آنها در ارتباط باشد را مینویسیم. این عمل میتواند در استفاده مجدد از کد ها بسیار سودمند باشد:
public interface MainView { void setUserData(UserEntity userData); void noUserFound(); void waitForUserResponse(); void userException(String message); }
و پیاده سازی آن در Actiivty به این صورت است:
public class MainActivity extends AppCompatActivity implements MainView { /** * Binding Views * Use {@link BindView} to inject view items to {@link MainActivity} */ @BindView(R.id.search) Button search; @BindView(R.id.response_error) TextView responseError; @BindView(R.id.password) TextView password; @BindView(R.id.wait) ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * When data of user found get data as {@link UserEntity} and show password in the responseError {@link TextView} * * @param userData is data received from api */ @Override public void setUserData(UserEntity userData) { password.setText(userData.getPassword()); progressBar.setVisibility(View.GONE); } /** * When no data found for user info that sent to server * this method will be called and shows no data to user * This method calls when server response status code is 404 */ @Override public void noUserFound() { responseError.setText(R.string.not_found_message); progressBar.setVisibility(View.GONE); } /** * After click search button the {@link android.widget.ProgressBar} * shows to user to wait for server response */ @Override public void waitForUserResponse() { progressBar.setVisibility(View.VISIBLE); } /** * When server response status code is not 200 and * there is a problem for retrieving data * * @param message This is message to show to user */ @Override public void userException(String message) { responseError.setText(message); progressBar.setVisibility(View.GONE); } @OnClick(R.id.search) public void searchClick() { CharSequence username = search.getText(); waitForUserResponse(); } }
همانطور که مشاهده میکنید, تمام متد های مورد نیاز override شدهاند و در بالای هریک از آنها یک JavaDoc قرار دارد که کارکرد آن متد را توضیح میدهد. اکنون کلاس Presenter را پیاده سازی میکنیم. برای presenter نیز یک interface پیاده سازی میکنیم:
public interface MainPresenter { void getDataFromApi(String username); }
و سپس presenter را پیاده سازی میکنیم:
public class Presenter implements MainPresenter { private MainView mainView; private ApiHandler apiHandler; public Presenter(MainView mainView) { this.mainView = mainView; this.apiHandler = new ApiHandler(); } @Override public void getDataFromApi(String username) { apiHandler.getUserByUsername(username)->subscribe(this::setUserData, this::onFailure); } private void setUserData(UserEntity userData) { mainView.setUserData(userData); } private void onFailure(Throwable throwable) { if (throwable.equals(NotFoundException)) { mainView.noUserFound(); } else { mainView.userException(throwable.getMessage()); } } }
در این کد Presenter به View متصل است. همانطور که مشاهده میکنید در presenter تمام پردازش های منطقی و ارتباط با API انجام میشود و تنها نتیجه آن به View ارسال میشود. به این ترتیب View کاملا مستقل از Model است. یکی از موارد پر اهمیت در MVP, بحث مدیریت Dependency ها است. زیرا بخش ها از یکدیگر جدا شدهاند و تنها Instance از بخش ها درون یکدیگر وجود دارد. به این دلیل معمولا از ابزار Dependency Injection مانند Dagger2 استفاده میکنیم.
اکنون باید View را به Presenter متصل کنیم:
... private MainPresenter mainPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainPresenter = new Presenter(this); } ... @OnClick(R.id.search) public void searchClick() { CharSequence username = search.getText(); waitForUserResponse(); mainPresenter.getDataFromApi(username.toString()); }
در این مقاله سعی بر این شد تا با مفهوم کلی و ساختار MVP design pattern آشنا شویم. همانطور که در طول مقاله اشاره شد, شیوه پیاده سازی آن ممکن است توسط برنامه نویسان دیگر متفاوت باشد. اما ساختار کلی آن که جدا سازی View از model است توسط presenter انجام میشود. روشی که در این مقاله استفاده شده است, توسط Android Annotaions کارایی بالایی دارد و پیاده سازی پروژه را بسیار سریع تر و آسان تر مینماید.
با ما همراه باشید.
مطالب زیر را حتما مطالعه کنید
آموزش Gradle – اهمیت Project Automation
درک مفهوم کدنویسی تمیز در اندروید
5 هک ساده برای کاهش سایز فایل APK
آشنایی با RecyclerView در اندروید
Open/Closed Principle در قوانین Solid
توابع در زبان برنامه نویسی Kotlin
4 Comments
Join the discussion and tell us your opinion.
دیدگاهتان را بنویسید لغو پاسخ
برای نوشتن دیدگاه باید وارد بشوید.
درود بر شما
چیزایی مثل اینتنت به اکتیوتی دیگه و نمایش یه دیالوگ تو کدوم بخش این مدل باید نوشته بشه؟
سلام دوست عزیز. ممنون از همراهیتون. این کار در لایه view انجام میشه. حالا ممکنه از طریق presenter دستورش به view صادر بشه. اما در اصل نمایش dialog و جابجایی با intent ها در لایه view هست
مقاله بسیار عالی بود. فرق این دیزاین پترن با MVC چیه؟
ممنون امین جان.
توی MVC همه کنترل کردن ها با controller هست. یعنی view بعد از init شدن از controller میخواد که همه setup هارو انجام بده و عملا همین باعث میشه قابلیت تست controller پایین تر بیاد. یعنی خود view اصلا اطلاعی نداره وقتی روی یه button کلیک میشه چه اتفاقی میوفته. اما توی mvp پردازش اون کلیک با presenter هست. یعنی presenter کاری نداره که view چطوری داره پیاده سازی میشه. برای همین view خیلی راحت تر قابل تغییر هست و از طرفی presetner که منطق اصلی برنامه هست راحت میتونه تست بشه. همین خاصیت باعث میشه خیلی راحت تر برنامه بخش بندی کنی.
ممنون از توجهت