Dagger2+Retrofit+RxJava+Butterknife搭建項目

上一篇我們講解了安卓中的MVP模型,這次我們將用查看學(xué)生列表作為例子唁影,搭建一個MVP安卓項目。我們都知道不要重復(fù)造輪子,因此使用一些現(xiàn)成的框架會簡化我們的操作晾剖。因此這篇文章將會重點介紹Dagger2+Retrofit+RxJava+Butterknife環(huán)境的搭建,以及如何使用梯嗽。
項目代碼連接

  • Dagger2 - 依賴注入
  • Retrofit - 發(fā)送http請求齿尽, 一種 type--safe REST client
  • RxJava - 實現(xiàn)異步操作
    (個人覺得還是很復(fù)雜的,需要透徹理解觀察者模式灯节⊙罚可以看這篇博客稍微了解一下這個框架)
  • Butterknife - 用來綁定Android views和方法

添加依賴

目前我使用的gradle版本是2.3.2,引入Dagger2的時候已經(jīng)不再需要配置apt插件炎疆。另外卡骂,配置Retrofit的各種converter和adapter時版本一定要一致。

    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    compile 'com.jakewharton:butterknife:8.4.0'
    compile 'com.google.dagger:dagger:2.4'
    compile 'io.reactivex:rxjava:1.0.14'
    compile 'io.reactivex:rxandroid:1.0.1'
    compile 'io.reactivex:rxjava-joins:0.22.0'
    //偷懶用的現(xiàn)成的底部導(dǎo)航欄形入,很少女很好看
    compile 'com.ashokvarma.android:bottom-navigation-bar:0.9.5' 
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.4'

Model

因為要展示學(xué)生列表全跨,所以首先定義從服務(wù)器端接收過來的學(xué)生信息。
服務(wù)器端提供接口:
/students

[
  {
    username:tj,
    name:"jenny",
    gender:"female",
    email:"test@163.com"
  }
]

Student類:

public class Student implements Serializable {
    @SerializedName("username")
    private String username;
    @SerializedName("name")
    private String name;
    @SerializedName("gender")
    private String gender;
    @SerializedName("email")
    private String email;
}

注意@SerializedName注解中要與服務(wù)器端提供的格式相符
因為要訪問網(wǎng)絡(luò)亿遂,所以記得在AndroidManifest.xml中添加INTERNET permissions

創(chuàng)建Retrofit實例

為了發(fā)送網(wǎng)絡(luò)請求螟蒸,我們需要使用Retrofit Builder 類并配置base URL。為了接收服務(wù)器端的json格式的數(shù)據(jù)并轉(zhuǎn)化成對象崩掘,我們需要添加GsonConverterFactory七嫌。為了支持RxJava,Retrofit本來默認(rèn)返回的是Call<T>苞慢,我們需要修改為Observable<T>诵原,我們需要添加RxJavaCallAdapterFactory。

public class ApiClient {
    public static final String BASE_URL = "http://和諧掉/api/";
    private static Retrofit retrofit = null;


    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

定義接收端口

在接口內(nèi)定義接收端口,使用特殊的Retrofit注解來對請求參數(shù)和請求方法進行編碼绍赛。
另外蔓纠,服務(wù)器端獲取信息的接口需要身份驗證,所以需要在請求的Header中添加token吗蚌。

public interface ApiInterface {
    @GET("students")
    Observable<List<Student>> getStudents(@Header("Authorization") String token);
}

Retrofit的其他注解
@Path - 用來代替API端口中的變量腿倚。如:

@GET("group/{groupId}/students")
Observable<List<User>> getStudents( @Path("groupId") int groupId);

@Query - 使用注解參數(shù)的值指定查詢鍵名稱。
@Body - POST 請求中的playload

在Repository中調(diào)用

public class StudentRepository {
    private static StudentRepository instance;
    protected ApiInterface apiService;

    private StudentRepository() {
        apiService = ApiClient.getClient().create(ApiInterface.class);
    }

    public static StudentRepository getInstance() {
        if (instance == null)
            instance = new TeacherRepository();
        return instance;
    }

    public Observable<List<Student>> getStudents(String username, String password) {
        return apiService.getStudents(getToken(username, password), groupId);
    }
}

Retrofit 會在后臺的線程下載并轉(zhuǎn)換數(shù)據(jù)蚯妇。


Presenter

Presenter需要覆蓋View的生命周期敷燎,如果我們需要跟蹤Activity或者Fragment的生命周期,我們需要調(diào)用Presenter對應(yīng)的方法箩言。因此每個Presenter需要實現(xiàn)以下接口:

public interface BasePresenter<T> {
    void onCreate();

    void onStart();

    void onStop();

    void onPause();
}

StudentListPresenter

public class StudentListPresenter implements BasePresenter<BasicListView> {
    private Subscription subscription;
    private BasicListView view;
    private StudentRepository repository;

    public StudentListPresenter(BasicListView view, StudentRepository repository) {
        this.view = view;
        this.repository = repository;
    }

    public void getStudents(String username, String password) {
         subscription = repository.getStudents(username, password)
                .subscribeOn(Schedulers.io())
                .onErrorReturn(new Func1<Throwable, List<User>>() {
                    @Override
                    public List<User> call(Throwable throwable) {
                        throwable.printStackTrace();
                        view.showError();
                        return null;
                    }
                })
                .subscribe(new Action1<List<User>>() {
                    @Override
                    public void call(List<User> groups) {
                        if (groups != null) {
                            view.addStudents(groups);
                        }
                    }
                });
    }

    @Override
    public void onStop() {
        if (subscription != null && !subscription.isUnsubscribed()) {
            subscription.unsubscribe();
        }
    }
}

我們使用StudentRepository來獲得學(xué)生列表硬贯,Presenter并不在意它是如何獲得的,它只是簡單地訂閱Repository返回的Observable陨收,更新view饭豹。為了避免后臺任務(wù)完成前Activity就被銷毀,Presenter需持有一個Subscription的引用务漩,在Activity調(diào)用onStop()方法時unsubscribe.


View

根據(jù)依賴倒置原則拄衰,在View和Presenter之間定義一個接口,Activity或者Fragment實現(xiàn)該接口饵骨,Presenter持有接口的引用翘悉。

public interface BasicListView {
    void showError();

    void addStudents(List<User> list);
}

定義Activity或者Fragment實現(xiàn)該接口

public class StudentsListFragment extends BaseFragment implements BasicListView {
    @BindView(R.id.list)
    ListView listView;
    @Inject
    StudentListPresenter presenter;
    private View view;
    private UserInfoActivity activity;
    private List<Map<String, Object>> studentMap;

    @Override
    public void showError() {
        Runnable myRunnable = new Runnable() {
            @Override
            public void run() {
                Toast.makeText(activity, R.string.network_error, Toast.LENGTH_LONG).show();
            }
        };
        activity.runOnUiThread(myRunnable);
    }

    @Override
    public void addStudents(List<User> list) {
         for (User user : list) {
              Map<String, Object> map = new HashMap<>();
              map.put("title", user.getName());
              map.put("content", user.getNumber());
              studentMap.add(map);
          }
          showList();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        activity = (UserInfoActivity) getActivity();
        view = inflater.inflate(R.layout.student_list, container, false);
        ButterKnife.bind(this, view);
        DaggerActivityComponent.builder()
                .mainModule(new MainModule(this))
                .build()
                .inject(this);
        presenter.getStudents(username, password);
        return view;
    }

    @Override
    public void onStop() {
        super.onStop();
        presenter.onStop();
    }

    private void showList() {
        Runnable myRunnable = new Runnable() {
            @Override
            public void run() {
                String[] from = {"title", "content"};
                int[] to = {R.id.title, R.id.content};
                SimpleAdapter adapter = new SimpleAdapter(view.getContext(), studentMap, R.layout.short_student_info, from, to);
                listView.setAdapter(adapter);
            }
        };
        activity.runOnUiThread(myRunnable);
    }

}

在StudentsListFragment中,我們使用到了@BindView注解宏悦,該注解可以通過id找到對應(yīng)的view組件。Butterknife的功能不止于此包吝,其官方文檔寫的非常清楚饼煞,感興趣請戳Butter Knife.

在MVP模型中,Activity或者Fragment持有Presenter的引用诗越,并在Activity中實例化這個Presenter砖瞧,即Activity依賴Presenter,Presenter又需要依賴View接口嚷狞,從而更新UI块促。這樣Activity與Presenter就耦合在了一起,因此我們這里使用到了Dagger2的依賴注入床未,來降低耦合竭翠。

讓我們來看一下代碼。首先我們聲明了一個StudentListPresenter薇搁,在聲明的基礎(chǔ)上加了一個注解@Inject斋扰,表明Presenter是需要注入到Fragment中。這里要注意的是,使用@Inject時传货,不能用private修飾符修飾類的成員屬性屎鳍。

@Component(modules = MainModule.class)
public interface ActivityComponent {
    void inject(StudentsListFragment fragment);
}

然而,StudentListPresenter的構(gòu)造函數(shù)和StudentListFragment中聲明的presenter并不會憑空建立起聯(lián)系问裕。Component是一個接口或者抽象類逮壁,用來將它們連接在一起。
@Component注解標(biāo)注粮宛,我們在這個接口中定義了一個inject()方法,參數(shù)是StudentListFragment窥淆。然后rebuild一下項目,會生成一個以Dagger為前綴的Component類窟勃,這里是DaggerActivityComponent.
注意StudentListFragment中的這段代碼:

 DaggerActivityComponent.builder()
                .mainModule(new MainModule(this))
                .build()
                .inject(this);

此時Component就將@Inject注解的presenter與其構(gòu)造函數(shù)聯(lián)系了起來祖乳。

有時候,有的類沒有構(gòu)造函數(shù)秉氧,這些類無法用@Inject標(biāo)注眷昆,比如第三方類庫,系統(tǒng)類汁咏,以及上面示例的View接口亚斋。此時我們需要@Module標(biāo)記的MainModlue類來提供依賴。
MainModule

@Module
public class MainModule {
    private StudentsListFragment fragment;

    public MainModule(StudentsListFragment fragment) {
        this.fragment = fragment;
    }

    @Provides
    public StudentsListFragment provideFragment() {
        return fragment;
    }

    @Provides
    public StudentRepository provideRepository() {
        return StudentRepository.getInstance();
    }

    @Provides
    public StudentListPresenter getStuListPresenter(StudentsListFragment fragment, TeacherRepository repository) {
        return new StudentListPresenter(fragment, repository);
    }
}

Module要發(fā)揮作用攘滩,還是要依靠于Component類帅刊,一個Component類可以包含多個Module類,用來提供依賴漂问。

我們接著來看上面這段代碼赖瞒。這里通過new MainModule(this)將view傳遞到MainModule里。當(dāng)去實例化StudentListPresenter時蚤假,發(fā)現(xiàn)構(gòu)造函數(shù)有兩個參數(shù)栏饮,此時會在Module里查找提供這個依賴的方法,即用@Provides標(biāo)注的方法磷仰,將該View和Repository傳遞進去袍嬉,這樣就完成了presenter里View的注入。


講到這里項目就搭建完畢啦灶平,也實現(xiàn)了一個小功能伺通。這里就略去layout的編寫了畢竟我也不會。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逢享,一起剝皮案震驚了整個濱河市罐监,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞒爬,老刑警劉巖笑诅,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调缨,死亡現(xiàn)場離奇詭異,居然都是意外死亡吆你,警方通過查閱死者的電腦和手機弦叶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妇多,“玉大人伤哺,你說我怎么就攤上這事≌咦妫” “怎么了立莉?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長七问。 經(jīng)常有香客問我蜓耻,道長,這世上最難降的妖魔是什么械巡? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任刹淌,我火速辦了婚禮,結(jié)果婚禮上讥耗,老公的妹妹穿的比我還像新娘有勾。我一直安慰自己,他們只是感情好古程,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布蔼卡。 她就那樣靜靜地躺著,像睡著了一般挣磨。 火紅的嫁衣襯著肌膚如雪雇逞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天茁裙,我揣著相機與錄音塘砸,去河邊找鬼。 笑死呜达,一個胖子當(dāng)著我的面吹牛谣蠢,可吹牛的內(nèi)容都是我干的粟耻。 我是一名探鬼主播查近,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挤忙!你這毒婦竟也來了霜威?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤册烈,失蹤者是張志新(化名)和其女友劉穎戈泼,沒想到半個月后婿禽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡大猛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年扭倾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挽绩。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膛壹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唉堪,到底是詐尸還是另有隱情模聋,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布唠亚,位于F島的核電站链方,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灶搜。R本人自食惡果不足惜祟蚀,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望占调。 院中可真熱鬧暂题,春花似錦、人聲如沸究珊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剿涮。三九已至言津,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間取试,已是汗流浹背悬槽。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞬浓,地道東北人初婆。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像猿棉,于是被迫代替她去往敵國和親磅叛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容