Android APP using RxJava+Retrofit2+Okhttp+MVP practice Project screenshot






This is my directory structure

Step 5: use RxJava+Retrofit2+Okhttp+RxCache
Step 1: Guide Package
compile 'io.reactivex:rxjava:1.1.8' compile 'io.reactivex:rxandroid:1.2.1' compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' compile 'com.github.VictorAlbertos.RxCache:core:1.4.6'
Step 2: create an API interface
public interface GanHuoService { @GET("data/{type}/{number}/{page}") Observable<DataResults> getDataResults( @Path("type") String type, @Path("number") int number, @Path("page") int page ); }
Step 3: create a new cache interface
/** * Cache API interface * * @LifeCache Set cache expiration time If @ LifeCache is not set, the data will be permanently cached unless you use EvictProvider, EvictDynamicKey or EvictDynamicKeyGroup * EvictProvider You can explicitly clean up all cached data * EvictDynamicKey You can explicitly clean up the specified data DynamicKey * EvictDynamicKeyGroup Allows you to explicitly clean up a specific set of data DynamicKeyGroup. * DynamicKey Expel data related to the use of EvictDynamicKey for a specific key. Such as paging, sorting, or filtering requirements * DynamicKeyGroup. Expel a set of data associated with the key and use EvictDynamicKeyGroup. Such as paging, sorting, or filtering requirements */ public interface CacheProviders { //Cache time 1 day @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS) Observable<Reply<List<DataResults>>> getHomeTypes(Observable observable, DynamicKey userName, EvictDynamicKey evictDynamicKey); }
Step 4: create a new retrofit abstract class
public abstract class RetrofitUtils { private static Retrofit mRetrofit; private static OkHttpClient mOkHttpClient; /** * Get Retrofit object * * @return */ protected static Retrofit getRetrofit() { if (null == mRetrofit) { if (null == mOkHttpClient) { mOkHttpClient = OkHttp3Utils.getOkHttpClient(); } //After Retrofit2, use the build design pattern mRetrofit = new Retrofit.Builder() //Set server path .baseUrl(Constant.API_SERVER + "/") //Add a conversion library. The default is Gson .addConverterFactory(GsonConverterFactory.create()) //Add callback library, using RxJava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //Set up network requests using okhttp .client(mOkHttpClient) .build(); } return mRetrofit; } }
Step 5: create a new HttpData class for unified management of requests
/* * All methods of requesting data are centralized * Write appropriate methods according to the definition of MovieService * Where observable is to obtain API data * observableCahce Get cached data * new EvictDynamicKey(false) false Use cache true to load data without cache */ public class HttpData extends RetrofitUtils { private static File cacheDirectory = FileUtil.getcacheDirectory(); private static final CacheProviders providers = new RxCache.Builder() .persistence(cacheDirectory) .using(CacheProviders.class); protected static final GanHuoService service = getRetrofit().create(GanHuoService.class); private static class SingletonHolder { private static final HttpData INSTANCE = new HttpData(); } public static HttpData getInstance() { return SingletonHolder.INSTANCE; } public void getHomeInfo(Observer<DataResults> observer, boolean isUseCache,String type, int number, int page) { Observable observable= service.getDataResults(type,number,page); Observable observableCahce=providers.getHomeTypes(observable,new DynamicKey("home page"),new EvictDynamicKey(!isUseCache)).map(new HttpResultFuncCcche<List<DataResults>>()); setSubscribe(observableCahce,observer); } /** * Insert observer * * @param observable * @param observer * @param <T> */ public static <T> void setSubscribe(Observable<T> observable, Observer<T> observer) { observable.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.newThread())//Sub thread access network .observeOn(AndroidSchedulers.mainThread())//Callback to main thread .subscribe(observer); } /** * Used to uniformly process the results of rxcache */ private class HttpResultFuncCcche<T> implements Func1<Reply<T>, T> { @Override public T call(Reply<T> httpResult) { return httpResult.getData(); } } }
After the setup is completed, you can request data like this, which needs to be written to the Model
public class HomeFragmentModel { public void loadData(final OnLoadDataListListener listener,boolean isUseCache ,String type, int number, int page) { HttpData.getInstance().getHomeInfo(new Observer<DataResults>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { listener.onFailure(e); } @Override public void onNext(DataResults homeDto) { listener.onSuccess(homeDto); } }, isUseCache,type,number,page); } }
MVC (Model-View-Controller)
M is the logical model, V is the view model, and C is the controller. A logical model can be used for multiple view models The purpose of using MVC is to separate the implementation code of M and V, which is convenient for expansion and future management From the perspective of developers, MVC completely separates the logic layer and interface of applications. The biggest advantage is that interface designers can directly participate in interface development, and programmers can focus on the logic layer. Although it can be realized in theory, it still feels that it can not be completely separated in practice It can also be said that the current popular MVC framework is adopted in Android. In Android: 1) View layer: generally, XML files are used to describe the interface, which can be easily introduced when using. However, if it is written in XML, it needs to be declared and instantiated in Acitvity. 2) Controller: the responsibility of Android's control layer usually falls on the shoulders of many acitivities, which should be handled through the Activity delivery Model business logic layer. Another reason for this is that the response time of Android's Acitivity is 5s. If time-consuming operations are placed here, the program can be easily recycled. 3) Model layer: database operations and network operations should be handled in the Model. Of course, business calculations and other operations must also be placed in this layer.
MVP
MVP is an evolutionary version of MVC based pattern. In MVC mode, Activity should belong to the View layer. In essence, it not only undertakes the View, but also contains some Controller things. With the iterative update of the project, it is very unfriendly to development, the coupling degree is higher, and the project is more and more difficult to maintain, and MVP is to solve this pain point. Separate the View and Controller of the Activity to become the View and Presenter.
Advantages of MVP:
- The model is completely separated from the view. We can modify the view without affecting the model
- The model can be used more efficiently because all interactions take place in one place - inside the Presenter
- We can use one Presenter for multiple views without changing the Presenter logic. This feature is very useful because the view changes more frequently than the model.
- If we put the logic in the Presenter, we can test the logic (unit test) away from the user interface
The home page is the Model layer: business logic and entity Model, so I only put business logic in the Model layer
public class HomeFragmentModel { public void loadData(final OnLoadDataListListener listener,boolean isUseCache ,String type, int number, int page) { HttpData.getInstance().getHomeInfo(new Observer<DataResults>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { listener.onFailure(e); } @Override public void onNext(DataResults homeDto) { listener.onSuccess(homeDto); } }, isUseCache,type,number,page); } }
Then there is the View layer: View corresponds to Activity or fragment, which is responsible for drawing the View and interacting with users
public interface HomeFragmentView { //Show load page void showProgress(); //Close load page void hideProgress(); //Load new data void newDatas(DataResults data); //Display load failed void showLoadFailMsg(); }
Then the Presenter is responsible for completing the interaction between the View and the Model
public class HomePresenter implements OnLoadDataListListener<DataResults> { private HomeFragmentView mView; private HomeFragmentModel mModel; public HomePresenter(HomeFragmentView mView) { this.mView = mView; this.mModel=new HomeFragmentModel(); mView.showProgress(); } public void getDataResults(boolean isUseCache,String type, int number, int page) { mModel.loadData(this,isUseCache,type,number,page); } @Override public void onSuccess(DataResults data) { mView.newDatas(data); mView.hideProgress(); } @Override public void onFailure(Throwable e) { Log.e("onFailure",e.toString()); mView.showLoadFailMsg(); } }
Finally return to Activity or fragment
public class DiscoveryFragment extends BaseFragment implements HomeFragmentView { private HomePresenter homePresenter; @Override protected View initView(LayoutInflater inflater, ViewGroup container) { return inflater.inflate(R.layout.fragment_list, container, false); } @Override protected void initData(Bundle savedInstanceState) { homePresenter = new HomePresenter(this); } @Override protected void loadData() { getData(isFirst); } private void getData(boolean isUseCache) { switch (mTitle) { case "home page": if (isTop) { NOW_PAGE_FI = 1; } homePresenter.getDataResults(isUseCache,"all", fi_num, NOW_PAGE_FI); break; } } @Override public void newDatas(DataResults dataResults) { if (dataResults.isError()) { Snackbar.make(recyclerview, "There's something wrong with the server", Snackbar.LENGTH_SHORT).show(); } else { if (mTitle.equals("dried food")) { ganhuo_list = new ArrayList<>(); ganhuo_list.addAll(dataResults.getResults()); } } } private void clearAdapterResults() { switch (mTitle) { case "home page": partAdapter.getResults().clear(); break; case "Sister paper": girlyAdapter.getResults().clear(); break; } } @Override public void showLoadFailMsg() { Snackbar.make(recyclerview, "The network is not smooth,I can't update the data", Snackbar.LENGTH_SHORT).show(); } @Override public void showProgress() { } @Override public void hideProgress() { } }
Project address, welcome to star: