Sugar-快速開發安卓項目,流行框架封裝mvp + rxjava2 + retrofit2 + rxlifecycle2 + arouter...

?? Sugar

image

最新版本1.0.1.4

需求:新項目只需5分鐘接入,之后直接開擼,不用關心網絡、圖片、模式、穩定等問題,支持mvp一個activity對應多個presenter。
適用自己的才是最好的!

更新日志

  • 2019-06-14 更新 kotlin接入啦,還有一些好用的方法更新,后續更新文檔
  • 2019-05-29 最新,新鮮temeple出爐,請拉到最后看效果
  • 2019-05-31 更新 1.0.1.3升級到androidx
  • ...

To do

  • anko
  • kotlin-mvp temeple
  • kotlin 全面支持 ??
  • 圖片加載庫更換策略,或者為了方便使用直接用一套寫好的放入
  • 常用控件(刷新,標題等等)
  • more...

最新修改

  1. SugarHandleSubscriber 用于接口請求時候僅處理onNext(),因為在SugarRepository里面的customObservable(Observable observable)函數里面統一處理了錯誤異常抓取,一般情況下不需要再處理OnError當然可以重載函數的時候做處理
 .doOnError(throwable -> {
                    LogUtils.i("doOnError------" + throwable);
                    if (mIView != null) {
                        mIView.showLoadFailed();
                    }
                    if (rxErrorHandler != null){
                       //統一異常抓取
                       rxErrorHandler.getHandlerFactory().handleError((Throwable) throwable);
                    }

                });
  1. 使用SugarHandleSubscriber
  • java :
@Override
    public void getFuliDataRepository(String size, String index) {

        mModel.getFuliDataRepository(size, index)
                .subscribe(new SugarHandleSubscriber<List<GirlsData>>() {
                    @Override
                    public void onNext(List<GirlsData> girlsData) {
                        mView.bindData(girlsData);
                    }
                });
    }
  • kotlin
   mModel.getFuliDataRepository(size, index)
                .subscribe({
                    mView.bindData(it)
                })
  1. 1.0.1.4版本之后 Presenter不支持在Activity/Fragment后寫泛型,只支持注解
  • java:
@CreatePresenter(presenter = GankPresenter.class)
public class GankActivity extends BaseActivity implements GankContract.IView{
    @PresenterVariable
    GankPresenter mPresenter;
}
  • kotlin
@CreatePresenter(presenter = [GankPresenter::class])
class KtGankActivity : BaseActivity(), GankContract.IView{
    @PresenterVariable
    internal var mPresenter: GankPresenter? = null
}

實用到的庫(排名不分先后)

Retrofit你懂的

ImmersionBar狀態欄工具

ToastUtils 簡單實用toast

RxErroHandler rxjava異常獲取

RetrofitUrlManager retrofit動態綁定url

EasyMvp個簡單強大且靈活的MVP框架

AndroidUtilCode 強大的工具庫

RxLifecycle 為rxjava而生你懂的

Gloading 深度解耦Android App中全局加載中、加載失敗及空數據視圖

RxJava 不解釋

RxAndroid 不解釋

RxPermissions Android runtime permissions powered by RxJava2

Okhttp 不解釋

Gson 不解釋

Timber JakeWharton大神的日志打印工具

ARouter 阿里出的路由庫

lottie

...以及忘了加上的

使用效果

image

圖片有壓縮,可以下載demo apk進行體驗
demo-debug.apk

安裝和依賴

3種選擇

1、 git clone https://github.com/wobiancao/sugar.git
    implementation project(':sugarlibrary')
    
2、 implementation 'com.wobiancao:sugarlibrary:{version}'

3、 allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
implementation 'com.github.wobiancao:sugar:{version}'

統一配置

創建DemoConfigure 繼承于SugarConfigure 重載相關方法即可:

public class DemoConfigure extends SugarConfigure {


    public DemoConfigure(Application application) {
        super(application);
    }

    @Override
    public ResponseErrorListener getErrorResponse() {
        return new ResponseErrorListener() {
            @Override
            public void handleResponseError(Context context, Throwable t) {
                LogUtils.i("捕獲異常---" + t.getMessage());
                ToastUtils.show("發生異常---" + t.getMessage());
            }
        };
    }

    @Override
    public int getStatusColor() {
        return R.color.colorPrimary;
    }

    @Override
    public AppHttpSetting getHttpSetting() {
        return AppHttpSetting
                .builder()
                .with(mApplication)
                //設置初始的baseUrl host
                .setBaseUrl(Gank.HOST)
                //動態修改baseUrl 具體看https://github.com/JessYanCoding/RetrofitUrlManager
                .putDomain(Wan.DOMAN, Wan.HOST)
                //是否打印網絡請求日志 默認否
                .setHttpLog(true)
                //百度Stetho即可 網絡監測等 默認否
                .setHttpMoniter(true)
                //設置緩存時間 默認60s
                .setCacheMaxTime(65)
                //設置連接超時 默認20s
                .connectTimeout(20)
                //設置讀取超時 默認20s
                .readTimeout(20)
                //設置寫入超時 默認20s
                .writeTimeout(20)
                //請求header
                .addHeaderInterceptor(getHeader())
                //添加請求明文公共參數
                .addCustomHeaderInterceptor(getCustomHeader())
                //token過期等請求成功處理 一般不需要處理
//                .addExceptionInterceptor(getExceptionInterceptor())
                //其它攔截
//                .addInterceptor(xx)
//                .addNetworkInterceptor(xxx)
//                配置自己的緩存
//                .cache(xx)
                //甚至另外寫一套自己的okhttp builder 也行
//                .setOkHttpBuilder(xxx)
                .build();
    }

    @Override
    public IToastStyle getToastStyle() {
        return new ToastStyle();
    }
}

  • 創建DemoApplication繼承于 LibApplication < S extends SugarConfigure > 重寫initConfigure()初始化配置即可,詳見DemoApplication
public class DemoApplication extends LibApplication<DemoConfigure> {

    @Override
    protected void initConfigure() {
        mConfigure = new DemoConfigure(this);
    }

    @Override
    protected void init() {

    }

}

網絡請求統一配置

  • 問題:我們使用RetrofitUrlManager 解決了retorfit動態配置baseUrl的問題,但是每個域名或者說每個接口返回參數封裝等的可能不統一(這種情況一般不會出現在公司項目)比如我這個app要展示Gank.ioWanAndroid的界面,這樣就是兩個網絡請求封裝,使用sugar可以快速解決此類問題;
  • 使用之前先看源碼SugarRepository
/**
 * @author wobiancao
 * @date 2019/5/20
 * desc :
 */
public class SugarRepository {
    /**
     * 0 沒loading 1 dialog形式  2page形式
     */
    protected final static int LOADING_TYPE_NULL = 0;
    /**
     * 0 沒loading 1 dialog形式  2page形式
     */
    protected final static int LOADING_TYPE_DIALOG = 1;
    /**
     * 0 沒loading 1 dialog形式  2page形式
     */
    protected final static int LOADING_TYPE_PAGE = 2;
    protected BaseIView mIView;

    public SugarRepository(BaseIView IView) {
        mIView = IView;
    }

    protected Observable addObservable(Observable observable) {
        if (mIView == null) {
            return null;
        }
        return customObservable(observable);
    }

    protected Observable addObservable(Observable observable, int loadingType) {
        if (mIView == null) {
            return null;
        }
        return customObservable(observable)
                .doOnSubscribe(disposable -> {
                    if (loadingType > 0) {
                        if (loadingType == LOADING_TYPE_DIALOG) {
                            mIView.showDialogLoading();
                        } else {
                            mIView.showLoading();
                        }
                    }
                });
    }

    private Observable customObservable(Observable observable) {
        return observable
                .compose(mIView.getProvider().bindToLifecycle())
                .retryWhen(new RetryWithDelay(2, 2))
                .subscribeOn(Schedulers.io())
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doFinally(() -> {
                    if (mIView != null) {
                        mIView.hideDialogLoading();
                    }
                })
                .doOnNext(o -> {
                    LogUtils.e("doOnNext------" + o);
                    if (mIView != null) {
                        mIView.showLoadSuccess();
                    }
                })
                .doOnError(throwable -> {
                    LogUtils.e("doOnError------" + throwable);
                    if (mIView != null) {
                        mIView.showLoadFailed();
                    }
                });
    }
}
  • addObservable(Observable observable)不會使用任何loading效果,
    addObservable(Observable observable, int loadingType) loadingType : 0 沒loading 、1 dialog形式 、2 page形式

  • Repository首先有個契約類,RepositoryContract
    xxxModel為需要增加的一個域名接口,統一配置apiService、請求函數、相應的transformer

/**
* @author wobiancao
* @date 2019-05-21
* desc :
*/
public class RepositoryContract {

   /**
    * gank.io
    */
   public interface GankModel  {
       Gank getService();
       /**
        * Transformer 需要處理api返回值包裝的加上即可
        * @param <T>
        * @return
        */
       <T> ObservableTransformer<GirlsResult<T>, T> gankTransformer();

       Observable<List<GirlsData>> getFuliDataRepository(String size, String index);
   }

   /**
    * wanandroid
    */
   public interface WanModel{
       Wan getService();
       /**
        * Transformer 需要處理api返回值包裝的加上即可
        * @param <T>
        * @return
        */
       <T> ObservableTransformer<WanResult<T>, T> wanTransformer();


       Observable<WanData> getWanArticleList(String index);
   }
}
/**
* @author wobiancao
* @date 2019/5/20
* desc :
*/
public class GankRepository extends SugarRepository implements RepositoryContract.GankModel {


   public GankRepository(BaseIView IView) {
       super(IView);
   }

   @Override
   public Gank getService() {
       return AppHttpClient.getInstance().initService(Gank.class);
   }


   @Override
   public <T> ObservableTransformer<GirlsResult<T>, T> gankTransformer() {

       return upstream -> upstream
               .flatMap((Function<GirlsResult<T>, ObservableSource<T>>) tGirlsResult -> {
                   if (tGirlsResult == null) {
                       return Observable.error(new HttpException("返回值為null"));
                   }
                   if (!tGirlsResult.error) {
                       return Observable.just(tGirlsResult.results);
                   } else {
                       return Observable.error(new HttpException("接口異常"));
                   }
               });

   }

   @Override
   public Observable<List<GirlsData>> getFuliDataRepository(String size, String index) {
       return addObservable(getService()
               .getFuliData(size, index)
               .compose(gankTransformer()), LOADING_TYPE_PAGE);
   }

}

最后mvp創建(之后會寫相應的Template ??已寫好)

image
  • =。=假的,接下來看代碼

WanContract

/**
 * @author wobiancao
 * @date 2019-05-21
 * desc :
 */
public class WanContract {
    public interface PView{

        void getWanArticleList(String index);
    }

    public interface IView extends BaseIView {
        /**
         * 綁定列表數據
         * @param data
         */
        void bindData(WanData data);
    }
}

WanPresenter


/**
 * @author wobiancao
 * @date 2019-05-21
 * desc :
 */
public class WanPresenter extends BasePresenter<WanContract.IView, WanRepository> implements WanContract.PView {

    @Override
    protected void initRepository() {
        mModel = new WanRepository(mView);
    }

    @Override
    public void getWanArticleList(String index) {
        mModel.getWanArticleList(index)
                .subscribe(new ErrorHandleSubscriber<WanData>(rxErrorHandler) {
                    @Override
                    public void onNext(WanData wanData) {
                        mView.bindData(wanData);
                    }

                });
    }


}

  • 就是這么簡單,最后就是在view層如何使用了,老規矩先看代碼WanActivity
/**
 * @author wobiancao
 * @date 2019-05-21
 * desc :
 */
@CreatePresenter(presenter = WanPresenter.class)
public class WanActivity extends BaseActivity<WanPresenter> implements WanContract.IView {
    @PresenterVariable
    WanPresenter mPresenter;
    TextView mInfoView;
    Toolbar mToolbar;
    @Override
    protected int getContentView() {
        return R.layout.gank_activity_list;
    }

    @Override
    public void init(Bundle savedInstanceState) {
        mInfoView = findViewById(R.id.tv_info);
        mToolbar = findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setTitle("WanAndroid");
        }
    }

    @Override
    public void loadData() {
        mPresenter.getWanArticleList("1");
    }

    @Override
    public void bindData(WanData data) {
        String jsonStr = new Gson().toJson(data);
        mInfoView.setText(jsonStr);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case android.R.id.home:
                finish();
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

是的Presenter創建只需要注解即可,并且支持多個presenter

這里要萬分感謝EasyMvp一個簡單強大且靈活的MVP框架

  • 首先,單個presenter
@CreatePresenter(presenter = WanPresenter.class)
public class WanActivity extends BaseActivity<WanPresenter> implements WanContract.IView 

獲取presenter變量兩種方式

1、通過注解

@PresenterVariable
WanPresenter mPresenter;

2、通過getPresenter()函數1.0.1.4之后不支持

xxActivity extends BaseActivity<xxPresenter>...

xxPresenter getPresenter()
  • 多個prenenter
    就只有通過注解獲得變量了
@CreatePresenter(presenter = {xxPresenter1.class, xxPresenter2.class})
xxActivity extends BaseActivity...

@PresenterVariable
xxPresenter1 mPresenter1;

@PresenterVariable
xxPresenter2 mPresenter2;

本庫github地址 sugar 簡單便捷 快速開發Android項目,集合流行框架封裝mvp + rxjava2 + retrofit2 + rxlifecycle2 + arouter...

mvp Template完成

  • 效果圖
image
image
  • 使用見圖解


    image

    image

使用步驟:

  1. 下載源碼目錄在 Sugar/SugarMvpTemplate
image
  1. 把兩個文件夾放入{Android Studio installation dir}\plugins\android\lib\templates\activities\路徑下

  2. 重啟Android studio即可使用

About me

License

Copyright 2019, wobiancao       
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at 
 
       http://www.apache.org/licenses/LICENSE-2.0 

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
   
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372