آموزش الگوی MVVM و RxJava به صورت پروژه محور ، توی این آموزش ما قصد داریم که با انجام یک پروژه تا حدودی با دو مفهوم الگوی طراحی MVVM و RxJava آشنا بشیم.
دلیل استفاده از این دو مفهوم نیز این است که پروژه ای که ایجاد میکنیم رو طبق استاندارد های جهانی برنامه نویسی پیش ببریم تا معتبرتر و البته منعطف تر بشوند.
قبل از شروع به کار پروژه بهتره تا کمی با تئوری این دو مفهوم آشنا بشیم.
چرا از الگوی طراحی یا design pattern استفاده کنیم؟!
سوال جالبیه خب چه کاریه وقتی میتونیم کدهامون رو هرطور ی با هرساختاری ایجاد کنیم بیایم از این الگو ها استفاده کنیم!؟
خب البته اجباری هم برای این کار نیست! درواقع دلیل استفاده از الگوهای طراحی یا design patternها به عبارتی تمیز کردن کدهای ما هستن
چون در نهایت خروجی با هر الگویی بوجود میاد اما این پشت پرده ماجرا است که برنامه نویس باهاش سروکار داره و قطعا نیاز به آپدیت کدهاش هست و البته تا آخر عمر که خودش رو پروژه نیست ! به احتمال زیاد یک نفر دیگه ممکنه بخواد با کدها کار کنه و اون موقع است که برنامه نویس بعدی باید متوجه بشه که برنامه نویس قبلی چیکار کرده !!
دلیل مهم دیگر استفاده از design pattern ها امکان testable یا تست پذیر بودن هر بخش بدون نیاز به بخش دیگر است(که این خود موضوع قابل بحثی است برای اطلاعات بیشتر در ارتباط با تست پذیر کردن پروژه به این مقاله مراجعه کنید)
همین دلایل و بسیاری دلایل دیگه هست که باعث میشه ما از design pattern ها استفاده کنیم
اما نکته قابل توجه این است که هنوز هم اجباری در استفاده از الگوهای پیشنهادی خود اندروید (MVC, MVP, MVVM) نیست.
شما اگر خودتون یک ساختار استاندارد سفارشی شده برای خودتون دارید که از مشکلات گفته شده جلوگیری میکنه میتونید از همون استفاده کنید.
خب بریم سراغ design pattern هایی که اندروید زحمتشو کشیده که تا به حال ۳ تا ارئه داده به شرح زیر (میتونید آموزش کامل هر ۳ الگو رو در این مقاله مطالعه کنید )
دارای سه بخش Model View Controller است که به صورت زیر عمل میکنند
در واقع همان POJO ها یا کلاس هاس معمولی است که از هیچ کلاسی ارث بری نمیکنند و وظیفه تعریف ساختار و ویژگی های یک شی را دارند.
نماینده Modelها هستند. وظیفه viewها نمایش UI مناسب به کاربر است و ارتباط مستقیم با Controller جهت اعمال دستورات user میباشد.
درواقع هسته مرکزی این الگوست چون تمامی عملیات اصلی درون این بخش انجام میشود و رابطی بین View و Model میباشد برای مثال زمانی که کاربر در view یک کلید را فشرد conteroller عملیات لازم را در model اعمال میکند
دارای سه بخش Model View Presenter است که به صورت زیر عمل میکنند.
در واقع همان نقش سابق را دارد (کلاس هاس ساده POJO )
ها در این الگو کمی متفاوت هستند.در این design pattern اکتیویتی ها و فرگمنت ها به عنوان بخشی از view معرفی شدند و دیگر لازم نیست تمامی عملیات را مثل الگوی قبل انجام دهند و تنها کافی است از یک interface ساخته شده با عنوان view استفاده کنند تا نتیجه عملیاتی که در presenter انجام میشود را به کاربر نمایش دهد.
ها در این الگو دقیقا وظیفه Controller هارا درالگوی mvc به عهده دارند با این تفاوت که آنها اصلا با view هادر ارتباط نیستند و درصورت نیاز از interface ها استفاده میکنند.
این الگو نیز دارای ۳ بخش Model View ViewModel میباشد که یک تفاوت عمده با دو الگوی قبلی دارد و آن استفاده از Data Binding on Adnroid است که علاوه بر بالابردن امکان ماژولاری و تست پذیر کردن پروژه باعث کاهش کدنویسی جهت اتصال view و model نیز شده و عملکرد هر بخش نیز به شرح زیر است
عملکرد سابق را دنبال میکند(کلاس های ساده POJO به همراه getter ها و setter ها)
در واقع باز به اکتیویتی وفرگمنت هایی اشاره میکند که عناصرشان bind شده به viewModel با استفاده از همان ویژگی جالب DataBinding علاوه بر فیلد ها همچنین اکشن ها نیز برای مثال اجرای یک متد هنگام فشردن یک کلید نیز bind شده به viewModel
وظیفه دارد که اطلاعات مورد نیاز را به هرشکل وشمایلی که نیاز view است از model دریافت و برای نمایش در view ارائه دهد همچنین ViewModel به طور مستقیم با view در ارتباط نیست بلکه از یک interface جهت اعمال عملیات خاص خود در view (اکتیویتی یا فرگمنت) استفاده میکند.
ما در این پروژه از آخرین الگوی ارائه شده یعنی الگوی MVVM استفاده میکنیم.
حالا مفهوم RxJava رو تا حدودی آشنا بشیم
کلا RxJava و reactive programming یا برنامه نویسی واکنشی یعنی چی؟!
برنامه نویسی واکنشی یا reactive programming همونطور که از اسمش مشخصه یعنی واکنش در لحظه !
Reactive programming is a programming paradigm oriented around data flows and the propagation of change. This means that it should be possible to express static or dynamic data flows with ease in the programming languages used, and that the underlying execution model will automatically propagate changes through the data flow.
به عبارت ساده تر برنامه نویسی واکنشی حول محور جریان داده ها حرکت میکند و در مواقع معین تغییرات را منتشر میکند و این دلیلی است برای اینکه برنامه نویسی asynchronous یا ناهمگام رو برنامه نویسی reactieve هم میگن. در این نوع برنامه نویسی ما از observerها یا مشاهده گر ها استفاده میکنیم برای انتشار تغییر حالت یا رویداد.
و حال RxJava هم یک پیاده سازی براساس جاوا با مفهوم reactive programming هست که Api های جاوا رو جهت برنامه نویسی ناهمگام یا asynchronous با Observabel stream رو برای ما فراهم میکنه!
reactive programming مختص جاوا و برنامه نویسی اندروید نیست بلکه reactivex یا Reactive Extensions افزونه هایی هستند برای زبان های برنامه نویسی مختلف که امکان برنامه نویسی reactive رو در پروژه فراهم میکنند.
توی مبحث RxJava ما با سه دستور خیلی سرو کار خواهیم داشت و زیاد می بینیمشون پس بد نیست باهاشون آشنا بشیم
مشاهده گرها یا observableها چیزی جز جریان داد ها نیستند.
observableها داده هایی که میتوانند (ویا باید) از یک thread به thread دیگر منتقل شوند به صورت بسته ای از داده ها ارسال میکنند.
اونها اساسا داده هارا منتشر یا emit میکنند که این emitکردن ممکن است براساس نوع تنظیمات تعیین شده به صورت دوره ای ویا یکجا و یکبار باشد. به عبارت دیگر هم میتوان گفت Observableها تامین کننده های ما هستند.
تعداد بسیار زیادی operator در Rxjava وجود دارد(همین عامل باعث محبوبیت بیش از حد Rxjava نیز میباشد ) که به Observable جهت به دست آوردن خروجی ویژه تر متناسب با خواست ما کمک میکند.
همه operatorها رو میتونید از اینجا ببینید.
observerها جریان داده های تولیدی observable رو مصرف میکنند. observer ها با متد subscribeOn() دستورات observable رو اجرا میکنند تا به داده های منتشر شده توسط observable رو دریافت کنند.
هرزمان که observable داده هارو منتشر میکنه تمامی observer هایی که به این observable اشاره کردند داده هارو در متد onNext() خودشون دریافت میکنند. و اگر هم با خطایی مواجه شد اون خطارو داخل متد onError میتونیم شناسایی کنیم.
همونطور که بالاتر گفتیم Rx به معنای برنامه نویسی asynchronous یا ناهمگام هستش پس ما باید threadها رو جهت تقسیم بندی کارها کنترل کنیم.
اینجاست که Schedularها میان توی کدهای ما! درواقع Schedularها جزئی از Rx هستند که به Observable و Observer میگن که داخل کدوم thread باید اجرا بشن.
با استفاده از متد observeOn() مشخص میکنیم که Observeها داخل کدوم thread و با استفاده از subscribeOn() به Observable میگیم که داخل کدوم thread اجرا بشن.
برای مثال پارامتر Schedulers.io() مشخص میکنه که برای مثال observer یا observable در thread مربوط به io یا ورودی خروجی (برای مثال ارتباط با سرور) اجرا بشه.
ما توی برنامه نویسی اندروید علاوه بر Rxjava از RxAndroid هم استفاه میکنیم درواقع RxAndroid یک فرمت برای RxJava است چون ما در برنامه نویسی اندروید از جاوا استفاده میکنیم پس RxJava رو داریم اما برخی مباحث اندروید هستند که در RxJava موجود نیست و برای خود اندروید هستند مثل multi threading که بالا با عنوان Schedulers باهاش آشنا شدیم به همین جهت ما از RxAndroid برای برخی ویژگی های خاص اندروید هم استفاده میکنیم..
خب حالا که تا حدودی با این دو مفهوم آشنا شدیم بریم سراغ کدزدن.
ما در این آموزش میخوایم یه پروژه اندروید متصل به سرور asp.net ایجاد کنیم که اگر آموزش قبل مارو دیده باشید مبحث اتصال اندروید به سرور asp.net با retrofit رو آموزش دادیم
و داخل این آموزش تنها میخوایم دستورات داخل اندروید استودیو رو به الگوی MVVM و دستورات سرور رو به RxJava تبدیل کنیم پس پیشنهاد میکنم اگر آموزش قبل مارو ندیدید حتما مطالعه ای داشته باشید.
قبل از هرکار کتابخانه های مربوط به RxJava رو اضافه میکنیم
compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'io.reactivex.rxjava2:rxjava:2.1.5' compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'com.google.code.gson:gson:2.6.2' compile 'com.squareup.retrofit2:retrofit:2.0.2' compile 'com.squareup.retrofit2:converter-gson:2.0.2' compile 'com.android.support:cardview-v7:25.3.1'
و مهم تر از همه فعال کردن ویژگی DataBinding پروژه هستش چون داخل الگوی MVVM ما از DataBinding استفاده میکنیم پس
android { .... dataBinding{ enabled = true } }
خب بریم سراغ کلاس مدل که ما Person رو داریم که بدین صورته
package com.tejariapp.mvvmrxjava.data.model; import com.google.gson.annotations.SerializedName; /** * Created by Marjan on 23/10/2017. */ public class Person { @SerializedName("Id") private int pID; @SerializedName("Name") private String pName; @SerializedName("Family") private String pFamily; public Person(){} public Person(int id,String pName,String pFamily){ this.pID=id; this.pName=pName; this.pFamily=pFamily; } public Person(String pName,String pFamily){ this.pName=pName; this.pFamily=pFamily; } public int getpID() { return pID; } public String getpFamily() { return pFamily; } public String getpName() { return pName; } public void setpID(int pID) { this.pID = pID; } public void setpName(String pName) { this.pName = pName; } public void setpFamily(String pFamily) { this.pFamily = pFamily; } }
مشخصه که تغییر آنچنانی با آموزش قبل نکرده چون طبق گفته هادر بخش design pattern ها قسمت model در تمامی الگوهای طراحی یکسان بود
حالا میریم سراغ اینترفیس (interface) taskService که وظیفه ارتباط با سرور رو داشت
این قسمت تغییر های اساسی کرده
package com.tejariapp.mvvmrxjava.data.api; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import com.tejariapp.mvvmrxjava.data.model.Person; import java.util.List; import io.reactivex.Observable; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.http.Body; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.POST; /** * Created by Marjan on 26/09/2017. */ public interface TaskService { @GET("StudentApi/getAll") Observable<List<Person>> getPersons(); @POST("StudentApi/insertObjStu") Observable<Person> addNewPerson(@Body Person person); @FormUrlEncoded @POST("StudentApi/insertObjStu") Call<Person> addNewPerson(@Field("name") String name , @Field("family") String family); @POST("StudentApi/updateStudent") Observable<String> updatePersonInfo(@Body Person person); @FormUrlEncoded @POST("StudentApi/deleteStudent") Observable<String> deletePeronInfo(@Field("id") int id);
همونطور که میبینید به جای Call<> از Observable<> استفاده شده چون میخوایم از RxJava استفاده کنیم و این قسمت طبق گفته ها وظیفه تولید و emit داده ها به سمت Observer رو به عهده داره.
حالا کلاس ServiceGenerator رو مبینیم که وظیفش ایجاد بیس درخواست یا request به سرور بود
package com.tejariapp.mvvmrxjava.data.api; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import java.io.IOException; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import retrofit2.converter.gson.GsonConverterFactory; /** * Created by Marjan on 26/09/2017. */ public class ServiceGenerator { private static final String URL = "http://10.0.2.2:62813/"; private TaskService apiService; public ServiceGenerator() { OkHttpClient okClient = new OkHttpClient.Builder() .addInterceptor( new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request original = chain.request(); // Request customization: add request headers Request.Builder requestBuilder = original.newBuilder() //cues it want to run as local in local server .header("Host", "localhost") .method(original.method(), original.body()); Request request = requestBuilder.build(); return chain.proceed(request); } }) .build(); Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .setLenient() .create(); retrofit2.Retrofit restAdapter = new retrofit2.Retrofit.Builder() .baseUrl(URL) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .client(okClient) .build(); apiService = restAdapter.create(TaskService.class); } public TaskService getService() { return apiService; } }
توی این کلاس تغییرات زیادی نیاز نداریم به جز موارد زیر
.setLenient() که در تعریف gson بکار رفته و درواقع برای صحیح دریافت کردن مقدار ریسپانسی هست که به صورت string از سرور دریافت میشه(که ربطی به RxJava و الگوی MVVM نداره فقط برای بهبود کار اضافه شده)
و.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) که به restAdapter اضافه شده.
خب حالا باید یک interface جدید تحت عنوان PersonRepository ایجاد کنیم با کدهای زیر
package com.tejariapp.mvvmrxjava.data.repository; import com.tejariapp.mvvmrxjava.data.model.Person; import java.util.ArrayList; import java.util.List; import io.reactivex.Observable; public interface PersonsRepository { /* * Get list of popular persons */ Observable&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt; getAllPersons(); /** * Insert new person in server */ Observable&amp;lt;Person&amp;gt; insertNewPerson(Person person); /** * Update person in server */ Observable&amp;lt;String&amp;gt; updatePerson(Person person); /** * Delete person in server */ Observable&amp;lt;String&amp;gt; deletePerson(int id); }
و یک کلاس جدید تحت عنوان PersonRemoteRepository برای کدنویسی متدهای اینترفیس ایجاد میکنیم با کدنویسی زیر
package com.tejariapp.mvvmrxjava.data.repository; import android.util.Log; import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.api.ServiceGenerator; import com.tejariapp.mvvmrxjava.data.api.TaskService; import java.util.List; import io.reactivex.Observable; import io.reactivex.annotations.NonNull; import io.reactivex.functions.Function; public class PersonsRemoteRepository implements PersonsRepository { private TaskService apiService; ServiceGenerator serviceGenerator; public PersonsRemoteRepository(ServiceGenerator serviceGenerator) { this.serviceGenerator = serviceGenerator; } @Override public Observable&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt; getAllPersons() { Observable&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt; observable= serviceGenerator.getService().getPersons(); return observable .flatMap(new Function&amp;lt;List&amp;lt;Person&amp;gt;, Observable&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt;&amp;gt;() { @Override public Observable&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt; apply(@NonNull List&amp;lt;Person&amp;gt; persons) throws Exception { return Observable.fromArray(persons); } }); } @Override public Observable&amp;lt;Person&amp;gt; insertNewPerson(Person person) { Observable&amp;lt;Person&amp;gt; observable = serviceGenerator.getService().addNewPerson(person); return observable .flatMap(new Function&amp;lt;Person, Observable&amp;lt;Person&amp;gt;&amp;gt;() { @Override public Observable&amp;lt;Person&amp;gt; apply(@NonNull Person person) throws Exception { return Observable.just(person); } }); } @Override public Observable&amp;lt;String&amp;gt; updatePerson(Person person) { Observable&amp;lt;String&amp;gt; observable= serviceGenerator.getService().updatePersonInfo(person); return observable .map(new Function&amp;lt;String, String&amp;gt;() { @Override public String apply(@NonNull String s) throws Exception { Log.e("map",s); return s; } }); } @Override public Observable&amp;lt;String&amp;gt; deletePerson(int id) { Observable&amp;lt;String&amp;gt; observable= serviceGenerator.getService().deletePeronInfo(id); return observable .map(new Function&amp;lt;String, String&amp;gt;() { @Override public String apply(@NonNull String s) throws Exception { Log.e("map",s); return s; } }); } }
خب کلا وظیفه این دو کلاس رو بخوایم توضیح بدیم میتونیم اینطور بگیم که این کلاس ها رابط بین ما درخواست های سرور هستند.
بدین صورت که ما از هر کلاسی میتونیم با استفاده از یک شی از نوع اینترفیس PersonRepository یکی از متدهای اون رو بسته به نیاز صدا بزنیم و اون متد هم دستورات رتروفیت داخل اینترفیس TaskService رو اجرا میکنه.
میبینید که متدها با RxJava نوشته شدند برای مثال یکی از متدهارو با هم بررسی میکنیم
@Override public Observable&amp;lt;Person&amp;gt; insertNewPerson(Person person) { Observable&amp;lt;Person&amp;gt; observable = serviceGenerator.getService().addNewPerson(person); return observable .flatMap(new Function&amp;lt;Person, Observable&amp;lt;Person&amp;gt;&amp;gt;() { @Override public Observable&amp;lt;Person&amp;gt; apply(@NonNull Person person) throws Exception { return Observable.just(person); } }); }
خب ابتدا نوع متد رو میبینم که به صورت Observable<Person> هست که یک شی از نوع Person رو برمیگردونه و سپس Observable که ایجاد کردیم که از طریق اون به متد سرور دسترسی پیدا کنیم
serviceGenerator.getService().addNewPerson(person);
دقیقا از نوع متد addNewPerson در اینترفیس TaskService میباشد Observable<Person>
و در اخر هم با استفاده از Operator , (از اینجا ببینید) flatMap() مقدار شی person دریافتی رو دریافت میکنه و با استفاده از just اون رو برمیگردونه
@Override public Observable&amp;lt;Person&amp;gt; insertNewPerson(Person person) { Observable&amp;lt;Person&amp;gt; observable = serviceGenerator.getService().addNewPerson(person); return observable .flatMap(new Function&amp;lt;Person, Observable&amp;lt;Person&amp;gt;&amp;gt;() { @Override public Observable&amp;lt;Person&amp;gt; apply(@NonNull Person person) throws Exception { return Observable.just(person); } }); }
خب حالا قبل از اینکه بریم ViewModel هارو ایجاد کنیم یک اینترفیس دیگه با عنوان Interactor ایجادمیکنیم که رابطی بین ViewModel ها و View است
;package com.tejariapp.mvvmrxjava.ui.persons import com.tejariapp.mvvmrxjava.data.model.Person; /** * Created by Marjan on 24/10/2017. */ public interface Interactor { /* * delete all list item when user click read button */ void deleteAll(); /* * show person info in EditTexts when user click in each list item */ void showPersonInfo(Person person); /* * add new person item to list after added in server */ void addNewPerson(Person person); /* * update person item in list after updated in server */ void updatePerson(String response,Person person); /* * delete person item after deleted in server */ void deletePerson(String response,int id); }
خب حالا باید بریم سراغ viewModel ها که layout ها که در ادامه بررسی شون میکنیم بهش bind شدن
ما یه PersonVM درایم که ViewModel بیس مامحسوب میشه و اشاره کامل داره به مدل Person به صورت زیر
package com.tejariapp.mvvmrxjava.ui.persons; import android.databinding.BaseObservable; import android.databinding.ObservableField; import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.repository.PersonsRepository; /** * Created by Marjan on 25/10/2017. */ public class PersonVM extends BaseObservable { public final ObservableField&amp;lt;Integer&amp;gt; id = new ObservableField&amp;lt;&amp;gt;(); public final ObservableField&amp;lt;String&amp;gt; name = new ObservableField&amp;lt;&amp;gt;(); public final ObservableField&amp;lt;String&amp;gt; family = new ObservableField&amp;lt;&amp;gt;(); private final ObservableField&amp;lt;Person&amp;gt; observablePerson = new ObservableField&amp;lt;&amp;gt;(); public PersonVM(){ observablePerson.addOnPropertyChangedCallback(new OnPropertyChangedCallback() { @Override public void onPropertyChanged(android.databinding.Observable sender, int propertyId) { Person person=observablePerson.get(); if (person != null){ id.set(getId(person)); name.set(getName(person)); family.set(getFamily(person)); } } }); } public int getId(Person person) { return person.getpID(); } public String getFamily(Person person) { return person.getpFamily(); } public String getName(Person person) { return person.getpName(); } public Person getObservablePerson() { return observablePerson.get(); } public void setObservablePerson(Person observablePerson){ this.observablePerson.set(observablePerson); } }
همونطور که میبینید فیلدها به صورت ObservableField تعریف شدند چون داریم با RxJava کار میکنیم.
داخل سازنده این کلاس ما از Callback زیر استفاده کردیم
observablePerson.addOnPropertyChangedCallback( new OnPropertyChangedCallback() { @Override public void onPropertyChanged(android.databinding.Observable sender , int propertyId) { Person person=observablePerson.get(); if (person != null){ id.set(getId(person)); name.set(getName(person)); family.set(getFamily(person)); } } });
این تیکه زمانی اجرا میشه که متد setObservablePerson اجرا شده باشه و فیلد observablePerson مقداردهی شده باشه تا با استفاده از متد get() به شی perosn رو بگیریم و فیلدهای id, name, family رو مقداردهی کنیم.
یک viewmodel دیگه نیاز داریم برای activity_main.xml که کامپوننت های داخل این لایه رو به متدها و یا فیلدهای viewModel متصل یا bind کنیم.
پس کلاس جدید با عنوان personsVM ایجاد میکنیم که از کلاس PersonVM ارث بری میکنه (تا بتونیم به فیلدهای id,name,family در این کلاس دسترسی داشته باشیم)
package com.tejariapp.mvvmrxjava.ui.persons; import android.databinding.ObservableArrayList; import android.databinding.ObservableList; import android.util.Log; import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.repository.PersonsRepository; import com.tejariapp.mvvmrxjava.databinding.ActivityMainBinding; import java.util.List; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.observers.DisposableObserver; import io.reactivex.schedulers.Schedulers; /** * Created by Marjan on 23/10/2017. */ public class personsVM extends PersonVM { //fill with persons info that come from server public final ObservableList&amp;lt;Person&amp;gt; persons = new ObservableArrayList&amp;lt;&amp;gt;(); private final PersonsRepository personsRepository; Interactor interactor; ActivityMainBinding mainBinding; /* * @param personsRepository - execute server mthods * @param interactor - execute methods in MainActivity base of server response * @param mainBinding - for access to activity_main view values */ public personsVM(PersonsRepository personsRepository, Interactor interactor, ActivityMainBinding mainBinding){ this.personsRepository=personsRepository; this.interactor=interactor; this.mainBinding=mainBinding; } /* * execute deletePerson base of his id in server and get String response */ public void deletePerson(){ final int id=Integer.parseInt(mainBinding.id.getText().toString()); personsRepository.deletePerson(id) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new Observer&amp;lt;String&amp;gt;() { @Override public void onSubscribe(@NonNull Disposable d) { Log.e("delete onSubscribe",d.toString()); } @Override public void onNext(@NonNull String s) { if (s !=null) interactor.deletePerson(s,id); Log.e("delete onNext",s.toString()); } @Override public void onError(@NonNull Throwable e) { Log.e("delete onError",e.toString()); } @Override public void onComplete() { Log.e("delete","onComplete"); } }); } /* * update person base of his id in server and get String response */ public void updatePerson(){ int id=Integer.parseInt(mainBinding.id.getText().toString()); String name=mainBinding.name.getText().toString(); String family=mainBinding.family.getText().toString(); final Person person=new Person(id,name,family); personsRepository.updatePerson(person) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribeWith(new DisposableObserver&amp;lt;String&amp;gt;() { @Override public void onNext(@NonNull String s) { if (s != null) interactor.updatePerson(s,person); Log.d("update response is",s); } @Override public void onError(@NonNull Throwable e) { Log.e("onError",e.toString()); } @Override public void onComplete() { Log.e("onComplete","onComplete"); } }); } /* * insert new person in server and return it */ public void insertNewPerson(){ String name=mainBinding.name.getText().toString(); String family=mainBinding.family.getText().toString(); personsRepository.insertNewPerson(new Person(name,family)) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribeWith(new DisposableObserver&amp;lt;Person&amp;gt;() { @Override public void onNext(@NonNull Person person) { if (person != null) interactor.addNewPerson(person); } @Override public void onError(@NonNull Throwable e) { Log.e("onError",e.toString()); } @Override public void onComplete() { Log.e("onComplete","onComplete"); } }); } /* * get all persons info in server */ public void getPersons(){ personsRepository.getAllPersons() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribeWith(new DisposableObserver&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt;() { @Override public void onNext(List&amp;lt;Person&amp;gt; value) { //Log.e("list get and size is",value.getpName() + value.getpFamily()); interactor.deleteAll(); persons.addAll(value); Log.e("persons array size",String.valueOf(persons.size())); } @Override public void onError(Throwable throwable) { Log.e("onError",throwable.toString()); } @Override public void onComplete() { Log.e("onComplete","onComplete"); } }); } }
در ارتباط با پارامترها توضیحات لازم کامنت شده پس بهتره بریم یکی ازمتدهای Observe رو توضیح بدیم برای مثال متد insert
/* * insert new person in server and return it */ public void insertNewPerson(){ String name=mainBinding.name.getText().toString(); String family=mainBinding.family.getText().toString(); personsRepository.insertNewPerson(new Person(name,family)) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribeWith(new DisposableObserver&amp;lt;Person&amp;gt;() { @Override public void onNext(@NonNull Person person) { if (person != null) interactor.addNewPerson(person); } @Override public void onError(@NonNull Throwable e) { Log.e("onError",e.toString()); } @Override public void onComplete() { Log.e("onComplete","onComplete"); } }); }
خب میبینید که با استفاده از شی personRepository متد insertNewPerson رو صدازده و با ObserveOn گفته شده Observe در mainThread اجرا بشه و با استفاده از subscriceOn گفته شده Observable در ترد io اجرا بشه و در نهایت هم با استفاده از subscribeWith گفته شده در هر شرایط چه عملیاتی رو انجام بده.
ما توی فایل xml یک RecyclerView هم داشتیم که حالا Adapterی که براش ساخته بودیم باید کمی ویرایش کنیم اما قبل از اون باید viewModel مربوط به اون رو ایجاد کنیم پس یک کلاس جدید که از کلاس PersonVM ارث بری میکنه ایجاد میکنیم
package com.tejariapp.mvvmrxjava.ui.persons import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.repository.PersonsRepository; /** * Created by Marjan on 24/10/2017. */ public class personsItemVM extends PersonVM { Interactor interactor; public personsItemVM( Interactor interactor) { this.interactor=interactor; } /* * execute when user click on each list item */ public void clickPersonInfo() { Person person = getObservablePerson(); if (person != null) { interactor.showPersonInfo(person); } } }
و در این کلاس یک متد برای اجرا شدن زمانی که کاربر روی هرکدام از آیتم های لیست کلیک کرد قرار میدیم
و حالا هم PersonAdapter مطابق زیر
package com.tejariapp.mvvmrxjava.ui.persons; import android.databinding.DataBindingUtil; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; import com.tejariapp.mvvmrxjava.R; import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.repository.PersonsRepository; import com.tejariapp.mvvmrxjava.databinding.ListItemLayoutBinding; import java.util.ArrayList; import java.util.List; /** * Created by Marjan on 24/10/2017. */ public class PersonsAdapter extends RecyclerView.Adapter&amp;lt;PersonsAdapter.MyViewHolder&amp;gt; { private List&amp;lt;Person&amp;gt; persons; Interactor interactor; public PersonsAdapter(Interactor interactor) { this.persons = new ArrayList&amp;lt;&amp;gt;(); this.interactor = interactor; } /* * delete all items in list(when user click read button */ public void deleteAll() { persons.clear(); notifyDataSetChanged(); } /* * set list of person that come from server for list */ public void setPersons(@NonNull List&amp;lt;Person&amp;gt; persons) { this.persons = persons; notifyDataSetChanged(); } /* * add one person to list when insert */ public void setPerson(Person person) { this.persons.add(person); notifyDataSetChanged(); } /* * update selected item person in list */ public void updatePerson(int id, Person person) { int pos = findPos(id); this.persons.set(pos, person); notifyDataSetChanged(); } /* * delete selected person item base of his id */ public void deletePerson(int id) { int pos = findPos(id); persons.remove(pos); notifyDataSetChanged(); } /* * find position of person id for update or delete */ private int findPos(int id) { int pos = 0; for (int i = 0; i &amp;lt; persons.size(); i++) { if (persons.get(i).getpID() == id) { pos = i; break; } } return pos; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ListItemLayoutBinding listItemLayoutBinding = DataBindingUtil .inflate(LayoutInflater.from(parent.getContext()), R.layout.list_item_layout, parent, false); return new MyViewHolder(listItemLayoutBinding); } @Override public void onBindViewHolder(PersonsAdapter.MyViewHolder holder, int position) { Person person = persons.get(position); //create new PersonItemVM to run interactor method when click on list item final personsItemVM personItemVM = new personsItemVM( interactor); //set this object to ObservableField&amp;lt;Person&amp;gt; and then on // addOnPropertyChangedCallback fill id , name,family for list item views personItemVM.setObservablePerson(person); holder.setPersonItemVM(personItemVM); } @Override public int getItemCount() { return persons.size(); } public class MyViewHolder extends RecyclerView.ViewHolder { ListItemLayoutBinding listItemLayoutBinding; public MyViewHolder(ListItemLayoutBinding listItemLayoutBinding) { super(listItemLayoutBinding.getRoot()); this.listItemLayoutBinding = listItemLayoutBinding; } //fill each list item with Observable items(id,name,family) public void setPersonItemVM(personsItemVM personItemVM) { listItemLayoutBinding.setItemVM(personItemVM); listItemLayoutBinding.executePendingBindings(); } } }
همونطور که میبینید ما لیست personهارو ارسال نکردیم به adapter چون اونو داخل RecyclerView بایند کردیم که در ادامه بررسی میکنیم.
اما میبینید که ما ListItemLayoutBinding رو داریم که اشاره میکنه به فایل xml مربوط به itemهای لیست که قاعدتا اسمش باید list_item_layout باشه پس این فایل رو با همین نام ایجاد میکنیم
&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt; &amp;lt;layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"&amp;gt; &amp;lt;data class="ListItemLayoutBinding"&amp;gt; &amp;lt;variable name="itemVM" type="com.tejariapp.mvvmrxjava.ui.persons.personsItemVM" /&amp;gt; &amp;lt;/data&amp;gt; &amp;lt;android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"&amp;gt; &amp;lt;android.support.v7.widget.CardView android:id="@+id/personCard" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" android:onClick="@{() -&amp;gt; itemVM.clickPersonInfo()}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"&amp;gt; &amp;lt;android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" &amp;gt; &amp;lt;TextView android:id="@+id/id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" android:text="@{String.valueOf(itemVM.id)}" android:textColor="@android:color/background_dark" android:textSize="15sp" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /&amp;gt; &amp;lt;TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" android:text="@{itemVM.name}" android:textColor="@android:color/background_dark" android:textSize="15sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/id" app:layout_constraintVertical_bias="0.0" /&amp;gt; &amp;lt;TextView android:id="@+id/family" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" android:text="@{itemVM.family}" android:textColor="@android:color/background_dark" android:textSize="15sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/name" app:layout_constraintTop_toBottomOf="@+id/id" app:layout_constraintVertical_bias="0.0" /&amp;gt; &amp;lt;/android.support.constraint.ConstraintLayout&amp;gt; &amp;lt;/android.support.v7.widget.CardView&amp;gt; &amp;lt;/android.support.constraint.ConstraintLayout&amp;gt; &amp;lt;/layout&amp;gt;
خب میبیند که به personItemVM هم اشاره میکنه جهت دسترسی به ObservableField های موجود در همین کلاس یا PersonVM که از آن ارث بری میکند.
بعد از ایجاد این فایل xml باید یکبار پروژتون رو build کنید تا کلاس ListItemLayoutBinding ایجاد بشه و خطایی که تاحال از این کلاس میگرفت(که وجود ندارد) از بین برود.
خب حالا باید فایل activity_main.xml رو یه سری تغییرات که برای DataBinding هستش رو بدیم بدین صورت
&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt; &amp;lt;layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"&amp;gt; &amp;lt;data class="ActivityMainBinding"&amp;gt; &amp;lt;variable name="viewModel" type="com.tejariapp.mvvmrxjava.ui.persons.personsVM" /&amp;gt; &amp;lt;/data&amp;gt; &amp;lt;ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"&amp;gt; &amp;lt;android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"&amp;gt; &amp;lt;android.support.design.widget.TextInputLayout android:id="@+id/input_layout_id" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="24dp" android:layout_marginLeft="24dp" android:layout_marginRight="24dp" android:layout_marginStart="24dp" android:layout_marginTop="16dp" app:layout_constraintBottom_toTopOf="@+id/insert" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0"&amp;gt; &amp;lt;EditText android:id="@+id/id" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:text="@{String.valueOf(viewModel.id)}" android:hint="@string/id" /&amp;gt; &amp;lt;/android.support.design.widget.TextInputLayout&amp;gt; &amp;lt;android.support.design.widget.TextInputLayout android:id="@+id/input_layout_name" android:layout_width="0dp" android:layout_height="63dp" android:layout_marginBottom="8dp" android:layout_marginEnd="24dp" android:layout_marginLeft="24dp" android:layout_marginRight="24dp" android:layout_marginStart="24dp" android:layout_marginTop="16dp" app:layout_constraintBottom_toTopOf="@+id/insert" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/input_layout_id" app:layout_constraintVertical_bias="0.0"&amp;gt; &amp;lt;EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:text="@{viewModel.name}" android:hint="@string/name" /&amp;gt; &amp;lt;/android.support.design.widget.TextInputLayout&amp;gt; &amp;lt;android.support.design.widget.TextInputLayout android:id="@+id/input_layout_family" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="24dp" android:layout_marginLeft="24dp" android:layout_marginRight="24dp" android:layout_marginStart="24dp" android:layout_marginTop="16dp" app:layout_constraintBottom_toTopOf="@+id/read" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/input_layout_name" app:layout_constraintVertical_bias="0.0"&amp;gt; &amp;lt;EditText android:id="@+id/family" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:text="@{viewModel.family}" android:hint="@string/family" /&amp;gt; &amp;lt;/android.support.design.widget.TextInputLayout&amp;gt; &amp;lt;Button android:id="@+id/insert" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="@{() -&amp;gt; viewModel.insertNewPerson()}" android:text="insert" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@+id/read" app:layout_constraintRight_toLeftOf="@+id/delete" /&amp;gt; &amp;lt;Button android:id="@+id/read" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="@{() -&amp;gt; viewModel.getPersons()}" android:text="read" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/insert" /&amp;gt; &amp;lt;Button android:id="@+id/update" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="@{() -&amp;gt; viewModel.updatePerson()}" android:text="update" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@+id/delete" app:layout_constraintRight_toRightOf="parent" /&amp;gt; &amp;lt;Button android:id="@+id/delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:onClick="@{() -&amp;gt; viewModel.deletePerson()}" android:text="delete" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@+id/insert" app:layout_constraintRight_toLeftOf="@+id/update" /&amp;gt; &amp;lt;android.support.v7.widget.RecyclerView android:id="@+id/persons" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="24dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="24dp" app:layout_constraintBottom_toTopOf="@+id/insert" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/input_layout_family" bind:persons="@{viewModel.persons}"/&amp;gt; &amp;lt;/android.support.constraint.ConstraintLayout&amp;gt; &amp;lt;/ScrollView&amp;gt; &amp;lt;/layout&amp;gt;
توجه کنید که فایل xml که dataBinding شده باید تمامی کامپوننت ها در تگ layout باشند!
خب ابتدا viewModel رو برای این layout با تگ variable مشخص میکنیم و بعد هر view رو که میخوایم bind کنیم با این ساختار @{viewModel.family}
برای فیلدها و @{() -> viewModel.insertNewPerson()}
برای متدها bind میکنیم تا مثلا با فشردن هر کلید متد bind شده در viewModel اجرا شود.
اگر توجه کنید ما برای RecyclerView از bind:persons=”@{viewModel.persons}”
استفاده کردیم تا لیست داده هایی که باید درون آیتم های خودقرار دهد مشخص شود اما برای این bind هم یک کلاس ایجاد کردیم
package com.tejariapp.mvvmrxjava.ui.persons; import android.databinding.BindingAdapter; import android.support.v7.widget.RecyclerView; import com.tejariapp.mvvmrxjava.data.model.Person; import java.util.List; /** * Created by Marjan on 25/10/2017. */ public class PersonListBinding { /* * bind:persons use in recyclerView * @Parameter recyclerView is that we bind on it * @parameter personList is which ObservableList that we bind on it */ @BindingAdapter("bind:persons") public static void setPersons(RecyclerView recyclerView, List&amp;lt;Person&amp;gt; personList){ RecyclerView.Adapter adapter=recyclerView.getAdapter(); //check if adapter object isn't null and is from PersonAdapter if (adapter != null &amp;amp;&amp;amp; adapter instanceof PersonsAdapter) //set retrieve array in Adapter arrayList ((PersonsAdapter) adapter).setPersons(personList); else throw new IllegalStateException("RecyclerView either" + " has no adapter set or the " + "adapter isn't of type MovieAdapter"); } }
که مشخص میکنه اگر adapter همان PersonAdapter مدنظرما بود لیست Personهارو که داخل RecyclerView تعیین کردیم و به آرایه persons
در personVM اشاره داره ارسال میکنه به PersonAdapter ارسال میکنه
خب حالا هم میرسیم به مرحله آخر و MainActivity که باید متدهای داخل اینترفیس interactor رو اجرا کنه
package com.tejariapp.mvvmrxjava.ui.base; import android.databinding.DataBindingUtil; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import com.tejariapp.mvvmrxjava.R; import com.tejariapp.mvvmrxjava.data.model.Person; import com.tejariapp.mvvmrxjava.data.api.ServiceGenerator; import com.tejariapp.mvvmrxjava.data.repository.PersonsRemoteRepository; import com.tejariapp.mvvmrxjava.databinding.ActivityMainBinding; import com.tejariapp.mvvmrxjava.ui.persons.Interactor; import com.tejariapp.mvvmrxjava.ui.persons.PersonsAdapter; import com.tejariapp.mvvmrxjava.ui.persons.personsVM; public class MainActivity extends AppCompatActivity implements Interactor { private ActivityMainBinding mBinding; private personsVM mVM; PersonsAdapter personsAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding= DataBindingUtil.setContentView(this, R.layout.activity_main); //setup viewmodel PersonsRemoteRepository personsRemoteRepository= new PersonsRemoteRepository(new ServiceGenerator()); mVM=new personsVM(personsRemoteRepository,this,mBinding); //set viewModel to layout mBinding.setViewModel(mVM); //setup PersonAdapter and recyclerView personsAdapter=new PersonsAdapter(this); mBinding.persons.setLayoutManager(new LinearLayoutManager(this)); mBinding.persons.setAdapter(personsAdapter); //get all persons from server mVM.getPersons(); } /* * delete all list items */ @Override public void deleteAll() { personsAdapter.deleteAll(); } /* * @param person - return a selected person item * when execute this method new PersonsVM * created and set as viewModel obj in layout * to fill id ,name, family base person info that user clicked */ @Override public void showPersonInfo(Person person) { mVM.setObservablePerson(person); mBinding.setViewModel(mVM); } /* * @param person - inserted person * here inserted person add to list item */ @Override public void addNewPerson(Person person) { personsAdapter.setPerson(person); } /* * @param person - updated person * @param response - string response that come from server * update selected person item in list */ @Override public void updatePerson(String response,Person person) { if (response.equals("success")) personsAdapter.updatePerson(person.getpID(),person); else mBinding.id.setError(getResources() .getString(R.string.idError)); } /* * @param id - id of deleted person * @param response - string response that come from server * update selected person item in list */ @Override public void deletePerson(String response,int id) { if (response.equals("successful")) personsAdapter.deletePerson(id); else mBinding.id.setError(getResources().getString(R.string.idError)); } }
توضیحات کامل به صورت کامنت ارائه شده.
برای دانلود سورس کدهم میتونید به کانال تلگرام ما مراجعه کنید
موفق و پیروز باشید 🙂
امیدوارم این پست براتون مفید بوده باشه.
7 Comments
سلام آموزشتون عالی بود.
فقط یک سوال بنظر شما mvp بهتره استفاده کنیم برای پروژه هامون یا MvvM ؟؟؟
سلام ,خیلی ممنونم 🙂
ببینید الگوی mvp بعد از mvc ساده ترینه چون به جای اکتیویتی یه کلاس تحت عنوان presenter دارید و تمام دستورات و متداهارو اونجا قرار میگیرند و نتیجه رو به view که اکتیویتی میشه ارسال میکنید ، پس پیچیدگی خاصی نداره
اما mvvm علاوه بر جدا کردن کامل منطق از view این امکان رو به شما میده ک view رو با استفاده از databinding متصل کنی به بانک و از حجم زیادی از کدنویسی جلوگیری کنید
و جدا از اون هم الگوی پیشنهادی اندروید توی آموزشات فعلیش هست
حالا انتخاب با شماست که با کدوم راحتتر هستید و توی پروژتون جوابگو شما میتونه باشه
کلاس ActivityMainBinding v رو فراموش کردین بنویسین. و این که سورس این مثال چطوری دانلود میشه؟؟ تو گروه تلگرامتون هم نبود
با سلام
سورس مورد نظر رو می تونید در کانال تلگرام ما دریافت کنید
آدرس کانال تلگرام : @progrun
برای پیدا کردن سریعتر سورس میتونید کلمه MVVM رو داخل کانال سرچ کنید
با احترام
خیلی لطف کردی عزیزم
سلام و خسته نباشید
مطلبی خیلی خوب هست ممنون از این مقالتون فقط سورس این پروژه توی کانال تلگرامتون پیدا نکردم اگر ممکن هست به ایمیلم ارسال کنید ممنون از لطفتون
سلام
چرا کدها اینطورین؟ انگار نتونستن رو تشخیص بدن.
و اما به درخواست مهم چطوری در کنار Retrofit و Rx و MVVM از Dagger2 هم استفاده کنیم؟ اگه آموزشی در این مورد هم بزارید عالی میشه.
ممنون