Написание кода меня очень увлекает и я люблю это, люблю писать всякие ништячки — программки, утилитки и т.д. Мне это доставляет удовольствие. Но иногда меня очень сильно бесит большое количество объявлений новых объектов типа Object object = new Object();
где Object это какой-то класс объект в котором хранятся данные или еще что-то на подобии.
Как и ButterKnife, я стараюсь везде пихать и Dagger 2 который умеет красиво облегчать код и по красоте убирать лишние объявления создания новых объектов каждый раз когда нам нужно их создавать.
По этой библиотеке есть очень много статей на хабре — раз, два, три, четыре, пять. По ней есть куча статей в интернете в общем — раз, два, три, но я приведу пример того как я вижу и как мне проще подключать эту библиотеку, возможно моя статья окажется лучше чем статьи те которые я нашел в интернете, так как я сходу не смог понять что да как делают в них в свое время.
Основную базовую информацию что это, и как это можно почерпнуть из ссылок выше, там в красках описывается что такое Dagger 2, зачем он нужен, для чего его используют, кто его используют и как, там есть куча примеров как люди изгаляются и делают свой код лучше и сложнее с точки зрения новичка. Я же расскажу то как я использую его и это как по мне достаточно для того что бы заинтересовать использовать эту библиотеку. Она как минимум сокращает код, и добавляет удобность использования нужных объектов в программе. Но иногда Dagger 2 умеет выдавать кучу ошибок из-за какой-то случайно добавленной аннотации, а ошибок оно показывает такое ощущение что проект вообще написан одним местом… Но со временем привыкаешь к этому и уже не обращаешь внимания, так как по сути все проблемы в этих ошибках описываются и их очень легко решить, тем более они повторяются.
Приложение мы будем делать такое как мы уже делали в статье «Создаем бесконечный список с помощью RecyclerView», по сути у нас тут будет код из этой статьи, почти, обернутый в обертку Dagger'a без пагинации что бы небыло лишнего кода, а то его и так дохрена из-за ретрофита будет.
В общем для начала нам нужно подключить библиотеки для работы с Retrofit, OkHttp, RecyclerView, Dagger 2 и ButterKnife. В общем в этом проекте у нас будет дохера либ которые мы будем использовать, но тут мы уже вроде как опытные и использовали эти библиотеки все, по этому можем себе это позволить.
Вот так будет выглядеть gradle файл с зависимостями.
app/build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.project.dajver.dagger2testexample"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.google.dagger:dagger:2.11'
compile 'com.google.dagger:dagger-android:2.11'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
compile 'com.squareup.retrofit2:converter-scalars:2.0.1'
}
Вот тут мы подключили кучу в общем библиотек я думаю для всех кто читает мои статьи это знакомый набор кроме даггера, и картина особо знакомая так как без подключения библиотек ни один проект не начнется :), я лично с этого всегда начинаю.
Дальше нам нужно добавить пермишен для доступа в интернет в AndroidManifest.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.project.dajver.dagger2testexample">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity" />
</application>
</manifest>
Тут мы добавили пермишен для доступа в интернет и добавили файл App который у нас является оберткой для взаимодействия даггера с жизненным циклом приложения. Этот файл мы создадим чуть позже когда у нас будет компонент и модуль для синхронизации зависимостей.
С первой частью мы справились, у нас подключились все нужные библиотеки и подтянулись все нужные файлы. Дальше нам нужно начать создавать магию.
Мы должны создать интерфейс с аннотацией @Compoent, в котором у нас будет объявленны активити которые мы будем использовать для взаимодействия, ну то есть активити в которых мы будем использовать Dagger 2, и будет объявление собственно модуля управляющего инъекциями в проекте.
AppComponent.java
import com.project.dajver.dagger2testexample.App;
import com.project.dajver.dagger2testexample.MainActivity;
import com.project.dajver.dagger2testexample.SecondActivity;
import com.project.dajver.dagger2testexample.modules.AppModule;
import javax.inject.Singleton;
import dagger.Component;
import dagger.android.AndroidInjector;
@Singleton
@Component(modules = { AppModule.class })
public interface AppComponent extends AndroidInjector<App> {
void inject(MainActivity mainActivity);
void inject(SecondActivity secondActivity);
final class Initializer {
private Initializer() { }
public static AppComponent init(App app) {
return DaggerAppComponent.builder()
.appModule(new AppModule(app))
.build();
}
}
}
Вот тут видно как мы создали интерфейс, ему мы подключили модуль который мы создадим дальше, он нам нужен для управления инъекций и хранения данных. Внутри интерфейса мы добавили в инъекции две активити которые у нас будут использова Dagger.
Еще ниже мы создали класс Initializer который нам нужен для инициализации синглтона на весь жизненный цикл программы для Dagger'a, и в методе init проинициализировали наш модуль. Хочу заметить что у нас над @Component есть аннотация @Singlton — он нам нужен для того что бы Dagger понял что этот интерфейс нам не пересоздавать каждый раз когда мы переходим между активити, ну в общем вы поняли, типичное поведение для синглтона — раз и на всю жизнь.
AppModule.java
import android.app.Application;
import com.project.dajver.dagger2testexample.App;
import com.project.dajver.dagger2testexample.api.model.imp.FetchedDataPresenterImpl;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AppModule {
private App app;
public AppModule(App application) {
app = application;
}
@Provides
@Singleton
protected Application provideApplication() {
return app;
}
@Provides
@Singleton
protected FetchedDataPresenterImpl provideFetchedData() {
return new FetchedDataPresenterImpl();
}
}
Этот класс нам нужен для сохранение инстанса данных, для сохранения context'a в приложении что бы мы не теряли данные при переходе между экранами и т.д. Хочу заметить аннотацию @Module — она указывает на то что этот класс мы будем использовать как модуль в котором будет храниться вся хрень касательно нашего приложения.
В начале класса мы создали конструктор с инстансом класса App, мы его передает для сохранения контекста. Дальше мы создаем метод provideApplication() который собственно хранит наш context в приложении, и не пересоздает его каждый раз при переходах. А еще ниже у нас метод provideFetchedData() который хранит инстанс нашего класса с данными который мы создадим позже, в этом классе у нас будут хранитья данные с запроса к GitHub API.
App.java
import android.app.Application;
import com.project.dajver.dagger2testexample.components.AppComponent;
public class App extends Application {
private static AppComponent appComponent;
private static App app;
@Override
public void onCreate() {
super.onCreate();
app = this;
buildComponentGraph();
}
public static AppComponent component() {
return appComponent;
}
public static void buildComponentGraph() {
appComponent = AppComponent.Initializer.init(app);
}
}
В этом классе мы объеденяем наш класс компонент и наш модуль, по сути тут мы создаем объект нашего модуля в компоненте с интансом контекста из Application класса.
В общем что мы тут видим. В onCreate() мы создали инстанс нашего Application класса, и запускаем метод buildComponentGraph() который создает наш AppComponent. Это мы делаем для того что бы дальше в наших Activity могли писать вот такую хрень
App.component().inject(this);
, для того что бы Dagger понимал что в этом классе у нас будут работать инъекции и доступ к сохраненным данным в синглтоне. Дальше желательно сбилдить проект что бы Dagger создал все нужные компоненты и классы для связи всего этого в одну кучу.
Теперь когда у нас есть вот эта конструкция мы можем начать писать смело везде где мы хотим аннотацию @Inject и у нас будет создаваться нужные нам объекты классов без каких либо дополнительных new Object().
API.java
import com.project.dajver.dagger2testexample.api.model.GitHubModel;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface API {
@GET("search/repositories")
Call<GitHubModel> getSearchedRepos(@Query("q") String q,
@Query("page") int page,
@Query("per_page") int perPage);
}
В общем создали мы значит интерфейс, стандартный для работы с Retrofit, в нем прописали адрес куда будем стучаться и параметры которые будем слать. Подетальней про работу с ретрофит можно почитать тут, я особо расписывать детали не буду ибо делал это милион раз уже.
Так же у нас тут не хватает пары классов которые будут парсить респонс с сервера.
GitHubModel.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
public class GitHubModel {
@SerializedName("items")
@Expose
private List<GitHubItemModel> items = new ArrayList<>();
public List<GitHubItemModel> getItems() {
return items;
}
public void setItems(List<GitHubItemModel> items) {
this.items = items;
}
}
GitHubItemModel.java
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GitHubItemModel {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("description")
@Expose
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Возвращать эти два класса нам будут список в котором будет айди, описание и название репозиториия. Этого нам хватит для примера, на деле конечно это API возвращает кучу инфы о репозитории. Дальше создадим клиент для доступа к запросам.
RestClient.java
import javax.inject.Inject;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public class RestClient {
public static final String BASE_URL = "https://api.github.com/";
private final API service;
@Inject
public RestClient() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(API.class);
}
public API getService() {
return service;
}
}
Тут мы указали в BASE_URL ссылку на базовый адрес куда мы будем обращаться для получения данных с сервера. В конструкторе проинициализировали Retrofit и наш интерфейс с параметрами, и дальше ниже создали метод getService() который возвращает нам наш сервис.
Еще нам нужно создать два класса. Один — интерфейс который будет иметь три метода, а второй класс будет имплементацией этого интерфейса, в нем мы будем описывать работу этих методов.
IFetchData.java
import com.project.dajver.dagger2testexample.api.model.GitHubItemModel;
import com.project.dajver.dagger2testexample.api.model.GitHubModel;
import java.util.List;
public interface IFetchedData {
void setGitHubData(GitHubModel data);
List<GitHubItemModel> getAllData();
GitHubItemModel getGitHubData(int position);
}
Первый метод у нас будет сетить данные в GitHubModel, а остальные два будут возвращать нам или все данные или одну пачку по позиции из списка. Это у нас чисто интерфейс, дальше последует имплементация его и описание их функций.
Для того что бы мы могли использовать @Inject в наших активити, нам нужно всегда прописывать над конструктором класса который мы хотим использовать аннотацию @Inject, что бы Dagger понимал что этот класс заинъекчен уже и что бы он мог его использовать.
FetchedDataPresenterImpl.java
import com.project.dajver.dagger2testexample.api.model.GitHubItemModel;
import com.project.dajver.dagger2testexample.api.model.GitHubModel;
import java.util.List;
import javax.inject.Inject;
public class FetchedDataPresenterImpl implements IFetchedData {
private GitHubModel gitHubModel;
@Inject
public FetchedDataPresenterImpl() { }
@Override
public void setGitHubData(GitHubModel data) {
this.gitHubModel = data;
}
@Override
public List<GitHubItemModel> getAllData() {
return gitHubModel.getItems();
}
@Override
public GitHubItemModel getGitHubData(int position) {
return gitHubModel.getItems().get(position);
}
}
Как говорил выше, для того что бы мы могли заинъектить класс в активити нам нужно в конструкторе класса всегда указывать аннотацию @Inject. Ну и как я говорил выше, этот класс имплементация по этому тут мы описываем запись данных в GitHubModel и потом выем этих данных из него же по средством созданных из интерфейса методов.
Дальше нам нужно создать адаптер для работы с RecyclerView, в него засетим данные с нашего респонса и отобразим красоту.
RecycleListAdapter.java
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.project.dajver.dagger2testexample.R;
import com.project.dajver.dagger2testexample.api.model.GitHubItemModel;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
public class RecycleListAdapter extends RecyclerView.Adapter<RecycleListAdapter.ViewHolder>{
private List<GitHubItemModel> searchModels = new ArrayList<>();
private OnItemClickListener onItemClickListener;
@Inject
public RecycleListAdapter() { }
public void addAll(List<GitHubItemModel> searchModels) {
this.searchModels = searchModels;
}
@Override
public RecycleListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_music, parent, false);
RecycleListAdapter.ViewHolder pvh = new RecycleListAdapter.ViewHolder(v);
return pvh;
}
@Override
public void onBindViewHolder(final RecycleListAdapter.ViewHolder holder, final int position) {
holder.title.setText(searchModels.get(position).getName());
}
@Override
public int getItemCount() {
return searchModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.textView)
TextView title;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(getAdapterPosition());
}
});
}
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClick(int position);
}
}
Вот такой адаптер, если в кратце то в методе addAll() мы добавляем данные в адаптер. В методе onCreateViewHolder() мы проинициализировали леяут с которым будем работать. В onBindViewHolder() мы засетапили данные из списка в текствью. Создали вью холдер и интерфейс который по клику кидает колбек в активити. Как-то так.
В активити мы все это объеденим до кучи и получим рабочий код. Надеюсь до теперешного момента все понятно…
MainActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import com.project.dajver.dagger2testexample.adapter.RecycleListAdapter;
import com.project.dajver.dagger2testexample.api.RestClient;
import com.project.dajver.dagger2testexample.api.model.GitHubModel;
import com.project.dajver.dagger2testexample.api.model.imp.FetchedDataPresenterImpl;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static com.project.dajver.dagger2testexample.SecondActivity.EXTRA_POSITION;
public class MainActivity extends AppCompatActivity implements Callback<GitHubModel>,
RecycleListAdapter.OnItemClickListener {
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@Inject
RecycleListAdapter recycleListAdapter;
@Inject
RestClient restClient;
@Inject
FetchedDataPresenterImpl fetchedData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
App.component().inject(this);
recycleViewSetup(recyclerView);
restClient.getService().getSearchedRepos("retrofit", 0, 100).enqueue(this);
}
public void recycleViewSetup(RecyclerView recyclerView) {
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
}
@Override
public void onResponse(Call<GitHubModel> call, Response<GitHubModel> response) {
GitHubModel githubModel = response.body() != null ? response.body() : new GitHubModel();
fetchedData.setGitHubData(githubModel);
recycleListAdapter.addAll(fetchedData.getAllData());
recycleListAdapter.setOnItemClickListener(this);
recyclerView.setAdapter(recycleListAdapter);
}
@Override
public void onFailure(Call<GitHubModel> call, Throwable t) {
t.printStackTrace();
}
@Override
public void onItemClick(int position) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra(EXTRA_POSITION, position);
startActivity(intent);
}
}
В этой активити мы заинъектили все наши нужные классы, теперь их инстанс создастся и не надо ничего создавать дополнительно, просто добавить аннотацию @Inject к названию класса.
Дальше в onCreate() мы прописали что мы будем использовать в этой активити ButterKnife и Dagger с помощью вызова ниже который мы создали в классе App. Так же мы тут вызываем recycleViewSetup() в который передаем RecyclerView что бы стилизовать дизайн этого списка. И делаем запрос на сервер.
Ниже у нас колбек ретрофита onResponse() который возвращает нам GitHubModel который мы передаем дальше в FetchedDataPresenterImpl, что бы потом можно было его использовать в других классах, и потом сетим эти данные в адаптер.
onFailure() у нас выводит ошибку в лог.
onItemClick() — по клику на айтем у нас идет переход на следующую активити, в которой мы будем выводить детали этого айтема по позиции в списке.
SecondActivity.java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import com.project.dajver.dagger2testexample.api.model.imp.FetchedDataPresenterImpl;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SecondActivity extends AppCompatActivity {
public static final String EXTRA_POSITION = "position";
@BindView(R.id.text)
TextView text;
@Inject
FetchedDataPresenterImpl fetchedData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
ButterKnife.bind(this);
App.component().inject(this);
int position = getIntent().getIntExtra(EXTRA_POSITION, 0);
text.setText("Name: " + fetchedData.getGitHubData(position).getName() +
"\nDescription: " + fetchedData.getGitHubData(position).getDescription());
}
}
Ну а во втором классе мы принимаем позицию и по ней с помощью этой позиции мы достаем из FetchedDataPresenterImpl нужные нам данные. Тут как и в предыдущей активити мы прописали что будем использовать ButterKnife и Dagger который у нас проинициализирован в классе App. В общем вы должны запомнить что везде где хотите использовать Dagger вам нужно прописывать зависимость от него в виде
App.component().inject(this);
и в AppComponent добпалят инъекции к нужным экранам.
Исходники:
cпасибо! очень доступно для новичка!
ОтветитьУдалитьА в 2021 это еще актуально?
ОтветитьУдалить