Android Dagger 2 +Retrofit+ RecyclerView
这是在我们的Android应用程序中使用Retrofit和RecyclerView实现Dagger 2的一站式完整教程。
Dagger 2是一个依赖项注入库,对于干净的代码和体系结构至关重要。
有关Dependency Injection和Dagger 2的基本概述,请在继续之前参考本教程。
什么是依赖注入?
简而言之,Dependency Injection无需使用new关键字自己创建实例,而是从外部为您提供这些实例。
在最坏的情况下,依赖注入是我们的朋友。
由于没有紧密耦合,因此它有助于快速重构代码并有助于测试单个事物。
它提高了可读性和可维护性。
不管依赖注入的知识如何,在面向对象编程中的某些时候,您肯定已经使用了它。
以下示例肯定会让您想起它。
public class A { public static void main(String[] args){ B b = new B(); C c = new C(b); D d = new D(b, c); E e = new E(d, b); A a = new A(e, b); } }
A类是受抚养人。
E和B类是注入的依赖项。
如您在上面的代码中看到的,我们不是在构造函数中初始化类,而是分别进行处理。
虽然我们上面还有样板代码。
有关Dagger 2的重要注意事项
它在编译时检查并绘制依赖对象图。
我们需要使用注释:@ Module,@ Provides,@ Inject,@ Component,@ Scope。
对于两个冲突的依赖项,例如Application具有不同的Context,而Activity具有不同的。
Dagger 2需要@Qualifier我们的@Named注释来区分。在接口上设置了@Component。
它充当桥梁,用于向Java类提供在@Module中指定的依赖项。
该依赖关系将在我们的Java类中使用@Inject检索。Dagger 2无法注入私有字段。
通过示例可以最好地理解Dagger 2。
已经有一段时间了,我们试图创建一个Android应用程序,使用RecyclerView和Retrofit尽可能最好地解释这些概念。
我们将使用StarWars API。
项目结构
我们为依赖注入组件,模块,作用域和限定符创建了一个单独的软件包di
。
活动进入ui
包。
API方法和POJO类分别位于retrofit
和pojo
包中。
"适配器"包含RecyclerViewAdapter类。
该项目包含两个活动– MainActivity和DetailActivity。
在应用程序的" build.gradle"中添加以下库依赖项。
implementation 'com.android.support:design:27.1.0' implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2' implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.3.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.10.0' implementation group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.9.0' implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2' implementation(group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.3.0') { exclude module: 'okhttp' } implementation 'com.google.dagger:dagger-android:2.11' implementation 'com.google.dagger:dagger-android-support:2.11' annotationProcessor 'com.google.dagger:dagger-android-processor:2.11' annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
确保已在AndroidManifest文件中添加了Internet权限。
依赖注入packagescope
范围定义了所有这些组件的使用位置。
在此应用程序中,它们是两个:ActivityScope和ApplicationScope。
ActivityScope.java
package com.theitroad.dagger2retrofitrecyclerview.di.scopes; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import javax.inject.Scope; @Scope @Retention(RetentionPolicy.CLASS) public @interface ActivityScope { }
ApplicationScope.java
package com.theitroad.dagger2retrofitrecyclerview.di.scopes; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import javax.inject.Scope; @Scope @Retention(RetentionPolicy.CLASS) public @interface ApplicationScope { }
@ActivityScope和@ApplicationScope将在以后的组件上使用。
限定词
因此,上下文可以是活动或者应用程序。
Dagger2如何区分它们?使用限定词。
下面定义了ActivityContext.java和ApplicationContext.java类。
package com.theitroad.dagger2retrofitrecyclerview.di.qualifier; import javax.inject.Qualifier; @Qualifier public @interface ActivityContext { }
package com.theitroad.dagger2retrofitrecyclerview.di.qualifier; import javax.inject.Qualifier; @Qualifier public @interface ApplicationContext { }
现在,无论其中使用Context的模块中,都必须根据用例使用@ApplicationContext或者@ActivityContext对其进行注释。
模组
模块是将通过组件向依赖项提供依赖项的模块。
让我们先计划一下依赖图。
因此," APIInterface"不依赖任何内容。
ApplicationComponent将容纳Retrofit和AppContext模块。
MainActivity将保存适配器和活动上下文模块以及ApplicationComponent的依赖项。
DetailActivityComponent不包含任何其自身的模块。
它仅使用ApplicationComponent中存在的那些。
上下文模块
package com.theitroad.dagger2retrofitrecyclerview.di.module; import android.content.Context; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ApplicationContext; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ApplicationScope; import dagger.Module; import dagger.Provides; @Module public class ContextModule { private Context context; public ContextModule(Context context) { this.context = context; } @Provides @ApplicationScope @ApplicationContext public Context provideContext() { return context; } }
我们为上下文指定了应用程序级别范围和限定符。
改造模块
package com.theitroad.dagger2retrofitrecyclerview.di.module; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ApplicationScope; import com.theitroad.dagger2retrofitrecyclerview.retrofit.APIInterface; import dagger.Module; import dagger.Provides; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; @Module public class RetrofitModule { @Provides @ApplicationScope APIInterface getApiInterface(Retrofit retroFit) { return retroFit.create(APIInterface.class); } @Provides @ApplicationScope Retrofit getRetrofit(OkHttpClient okHttpClient) { return new Retrofit.Builder() .baseUrl("https://swapi.co/api/") .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build(); } @Provides @ApplicationScope OkHttpClient getOkHttpCleint(HttpLoggingInterceptor httpLoggingInterceptor) { return new OkHttpClient.Builder() .addInterceptor(httpLoggingInterceptor) .build(); } @Provides @ApplicationScope HttpLoggingInterceptor getHttpLoggingInterceptor() { HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return httpLoggingInterceptor; } }
@Provides表示将从该方法向其依赖项提供依赖项。
对于翻新,我们使用ApplicationScope。
MainActivityContextModule
package com.theitroad.dagger2retrofitrecyclerview.di.module; import android.content.Context; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ActivityContext; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ActivityScope; import com.theitroad.dagger2retrofitrecyclerview.ui.MainActivity; import dagger.Module; import dagger.Provides; @Module public class MainActivityContextModule { private MainActivity mainActivity; public Context context; public MainActivityContextModule(MainActivity mainActivity) { this.mainActivity = mainActivity; context = mainActivity; } @Provides @ActivityScope public MainActivity providesMainActivity() { return mainActivity; } @Provides @ActivityScope @ActivityContext public Context provideContext() { return context; } }
上面的模块用于提供活动上下文和活动实例。
适配器模块
AdapterModule的代码如下。
package com.theitroad.dagger2retrofitrecyclerview.di.module; import com.theitroad.dagger2retrofitrecyclerview.adapter.RecyclerViewAdapter; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ActivityScope; import com.theitroad.dagger2retrofitrecyclerview.ui.MainActivity; import dagger.Module; import dagger.Provides; @Module(includes = {MainActivityContextModule.class}) public class AdapterModule { @Provides @ActivityScope public RecyclerViewAdapter getStarWarsPeopleLIst(RecyclerViewAdapter.ClickListener clickListener) { return new RecyclerViewAdapter(clickListener); } @Provides @ActivityScope public RecyclerViewAdapter.ClickListener getClickListener(MainActivity mainActivity) { return mainActivity; } }
它用于根据POJO数据创建RecyclerViewAdapter。
另外,ClickListener是RecyclerViewAdapter类中定义的接口,用于从Activity本身触发click listener回调方法。
由于我们在定义中加入了" MainActivityContextModule",因此注入了MainActivity依赖项。
让我们看一下APIInterface和POJO类。
下面给出了APIInterface.java类的代码。
package com.theitroad.dagger2retrofitrecyclerview.retrofit; import com.theitroad.dagger2retrofitrecyclerview.pojo.Film; import com.theitroad.dagger2retrofitrecyclerview.pojo.StarWars; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; import retrofit2.http.Url; public interface APIInterface { @GET("people/?") Call<StarWars> getPeople(@Query("format") String format); @GET Call<Film> getFilmData(@Url String url, @Query("format") String format); }
@Url用于在Retrofit中进行动态URL调用。
该URL将在运行时指定。
POJO类是从.jsonschema2pojo创建的。
StarWars.java
package com.theitroad.dagger2retrofitrecyclerview.pojo; import com.google.gson.annotations.SerializedName; import java.util.List; public class StarWars { @SerializedName("results") public List<People> results = null; public class People { @SerializedName("name") public String name; @SerializedName("height") public String height; @SerializedName("mass") public String mass; @SerializedName("birth_year") public String birthYear; @SerializedName("gender") public String gender; @SerializedName("homeworld") public String homeworld; @SerializedName("films") public List<String> films = null; } }
电影.java
package com.theitroad.dagger2retrofitrecyclerview.pojo; import com.google.gson.annotations.SerializedName; public class Film { @SerializedName("title") public String title; @SerializedName("director") public String director; }
我们只是解析将在应用程序中使用的值。
组件
在组件中,我们将包含模块。
我们需要公开将在"活动/应用程序"中使用的字段。
ApplicationComponent.java
package com.theitroad.dagger2retrofitrecyclerview.di.component; import android.content.Context; import com.theitroad.dagger2retrofitrecyclerview.MyApplication; import com.theitroad.dagger2retrofitrecyclerview.di.module.ContextModule; import com.theitroad.dagger2retrofitrecyclerview.di.module.RetrofitModule; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ApplicationContext; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ApplicationScope; import com.theitroad.dagger2retrofitrecyclerview.retrofit.APIInterface; import dagger.Component; @ApplicationScope @Component(modules = {ContextModule.class, RetrofitModule.class}) public interface ApplicationComponent { public APIInterface getApiInterface(); @ApplicationContext public Context getContext(); public void injectApplication(MyApplication myApplication); }
Dagger2将自动生成一个名为Dagger%ComponentName%的类。
例如。
DaggerApplicationComponent。injectApplication
用于允许在我们的活动/应用程序中使用@Inject字段。
MainActivityComponent.java
package com.theitroad.dagger2retrofitrecyclerview.di.component; import android.content.Context; import com.theitroad.dagger2retrofitrecyclerview.di.module.AdapterModule; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ActivityContext; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ActivityScope; import com.theitroad.dagger2retrofitrecyclerview.ui.MainActivity; import dagger.Component; @ActivityScope @Component(modules = AdapterModule.class, dependencies = ApplicationComponent.class) public interface MainActivityComponent { @ActivityContext Context getContext(); void injectMainActivity(MainActivity mainActivity); }
上面的组件也可以访问ApplicationComponent依赖项。
DetailActivityComponent.java
package com.theitroad.dagger2retrofitrecyclerview.di.component; import com.theitroad.dagger2retrofitrecyclerview.di.scopes.ActivityScope; import com.theitroad.dagger2retrofitrecyclerview.ui.DetailActivity; import dagger.Component; @Component(dependencies = ApplicationComponent.class) @ActivityScope public interface DetailActivityComponent { void inject(DetailActivity detailActivity); }
现在是时候在活动和应用程序中使用di了。
我们先来看一下布局代码。
布局
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" </android.support.constraint.ConstraintLayout>
activity_detail.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.DetailActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Loading..." android:textColor="@android:color/black" android:textSize="28sp" android:gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" </android.support.constraint.ConstraintLayout>
recycler_view_list_row.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" android:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp"> <TextView android:id="@+id/txtName" android:layout_width="wrap_content" android:layout_height="wrap_content" <TextView android:id="@+id/txtBirthYear" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/txtName" </android.support.constraint.ConstraintLayout>
在我们的类中使用依赖注入之前,请重新构建项目。
这样做是为了生成Dagger Component类。
下面给出了MyApplication.java类的代码。
package com.theitroad.dagger2retrofitrecyclerview; import android.app.Activity; import android.app.Application; import com.theitroad.dagger2retrofitrecyclerview.di.component.ApplicationComponent; import com.theitroad.dagger2retrofitrecyclerview.di.component.DaggerApplicationComponent; import com.theitroad.dagger2retrofitrecyclerview.di.module.ContextModule; public class MyApplication extends Application { ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder().contextModule(new ContextModule(this)).build(); applicationComponent.injectApplication(this); } public static MyApplication get(Activity activity){ return (MyApplication) activity.getApplication(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }
DaggerApplicationComponent.builder()。
contextModule(new ContextModule(this))。
build();用于构建组件中存在的模块。
getApplicationComponent将用于返回我们活动中的ApplicationComponent。
不要忘记在我们的AndroidManifest.xml文件中添加上述应用程序。
UI包
下面给出MainActivity.java类的代码。
package com.theitroad.dagger2retrofitrecyclerview.ui; import android.content.Context; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.widget.Toast; import com.theitroad.dagger2retrofitrecyclerview.MyApplication; import com.theitroad.dagger2retrofitrecyclerview.R; import com.theitroad.dagger2retrofitrecyclerview.adapter.RecyclerViewAdapter; import com.theitroad.dagger2retrofitrecyclerview.di.component.ApplicationComponent; import com.theitroad.dagger2retrofitrecyclerview.di.component.DaggerMainActivityComponent; import com.theitroad.dagger2retrofitrecyclerview.di.component.MainActivityComponent; import com.theitroad.dagger2retrofitrecyclerview.di.module.MainActivityContextModule; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ActivityContext; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ApplicationContext; import com.theitroad.dagger2retrofitrecyclerview.pojo.StarWars; import com.theitroad.dagger2retrofitrecyclerview.retrofit.APIInterface; import java.util.List; import javax.inject.Inject; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.ClickListener { private RecyclerView recyclerView; MainActivityComponent mainActivityComponent; @Inject public RecyclerViewAdapter recyclerViewAdapter; @Inject public APIInterface apiInterface; @Inject @ApplicationContext public Context mContext; @Inject @ActivityContext public Context activityContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); ApplicationComponent applicationComponent = MyApplication.get(this).getApplicationComponent(); mainActivityComponent = DaggerMainActivityComponent.builder() .mainActivityContextModule(new MainActivityContextModule(this)) .applicationComponent(applicationComponent) .build(); mainActivityComponent.injectMainActivity(this); recyclerView.setAdapter(recyclerViewAdapter); apiInterface.getPeople("json").enqueue(new Callback<StarWars>() { @Override public void onResponse(Call<StarWars> call, Response<StarWars> response) { populateRecyclerView(response.body().results); } @Override public void onFailure(Call<StarWars> call, Throwable t) { } }); } private void populateRecyclerView(List<StarWars.People> response) { recyclerViewAdapter.setData(response); } @Override public void launchIntent(String url) { Toast.makeText(mContext, "RecyclerView Row selected", Toast.LENGTH_SHORT).show(); startActivity(new Intent(activityContext, DetailActivity.class).putExtra("url", url)); } }
一旦发生这种情况:mainActivityComponent.injectMainActivity(this);
,@ Inject
出现的字段将被自动注入。
其余的工作是在进行Retrofit调用并在RecyclerViewAdapter中设置数据。
该类实现RecyclerViewAdapter.ClickListener
接口回调,该回调将在每单击RecyclerView行时触发launchIntent方法。
请注意,需要使用我们之前定义的相关限定符来指定注入的上下文。
下面给出了RecyclerViewAdapter的代码。
package com.theitroad.dagger2retrofitrecyclerview.adapter; import android.support.constraint.ConstraintLayout; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.theitroad.dagger2retrofitrecyclerview.R; import com.theitroad.dagger2retrofitrecyclerview.pojo.StarWars; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<StarWars.People> data; private RecyclerViewAdapter.ClickListener clickListener; @Inject public RecyclerViewAdapter(ClickListener clickListener) { this.clickListener = clickListener; data = new ArrayList<>(); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view_list_row, parent, false)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { holder.txtName.setText(data.get(position).name); holder.txtBirthYear.setText(data.get(position).birthYear); } @Override public int getItemCount() { return data.size(); } class ViewHolder extends RecyclerView.ViewHolder { private TextView txtName; private TextView txtBirthYear; private ConstraintLayout constraintLayoutContainer; ViewHolder(View itemView) { super(itemView); txtName = itemView.findViewById(R.id.txtName); txtBirthYear = itemView.findViewById(R.id.txtBirthYear); constraintLayoutContainer = itemView.findViewById(R.id.constraintLayout); constraintLayoutContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { clickListener.launchIntent(data.get(getAdapterPosition()).films.get(0)); } }); } } public interface ClickListener { void launchIntent(String filmName); } public void setData(List<StarWars.People> data) { this.data.addAll(data); notifyDataSetChanged(); } }
下面给出了DetailActivity.java类的代码:
package com.theitroad.dagger2retrofitrecyclerview.ui; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import com.theitroad.dagger2retrofitrecyclerview.MyApplication; import com.theitroad.dagger2retrofitrecyclerview.R; import com.theitroad.dagger2retrofitrecyclerview.di.component.ApplicationComponent; import com.theitroad.dagger2retrofitrecyclerview.di.component.DaggerDetailActivityComponent; import com.theitroad.dagger2retrofitrecyclerview.di.component.DetailActivityComponent; import com.theitroad.dagger2retrofitrecyclerview.di.qualifier.ApplicationContext; import com.theitroad.dagger2retrofitrecyclerview.pojo.Film; import com.theitroad.dagger2retrofitrecyclerview.retrofit.APIInterface; import javax.inject.Inject; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class DetailActivity extends AppCompatActivity { DetailActivityComponent detailActivityComponent; @Inject public APIInterface apiInterface; @Inject @ApplicationContext public Context mContext; TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); textView = findViewById(R.id.textView); String url = getIntent().getStringExtra("url"); ApplicationComponent applicationComponent = MyApplication.get(this).getApplicationComponent(); detailActivityComponent = DaggerDetailActivityComponent.builder() .applicationComponent(applicationComponent) .build(); detailActivityComponent.inject(this); apiInterface.getFilmData(url, "json").enqueue(new Callback<Film>() { @Override public void onResponse(Call<Film> call, Response<Film> response) { Film films = response.body(); String text = "Film name:\n" + films.title + "\nDirector:\n" + films.director; textView.setText(text); } @Override public void onFailure(Call<Film> call, Throwable t) { } }); } }
再次使用inject
方法来注入所有依赖项字段。
改型向指定的动态URL发出请求,并在TextView中显示响应。