In this tutorial, we’ll be developing an application using MVP, Dagger2, Retrofit and RxJava.
These days cryptocurrency is the latest fad. So we’ll be creating a cryptocurrency application using the above things.
Overview
To proceed further you must have an overview of the Model View Presenter, Dependency Injection, Retrofit and RxJava libraries.
We have covered them individually and in combination with the following tutorial links specified:
- Android MVP
- Android Dagger2
- Android Retrofit
- Android RxJava
- Android Dagger2 + Retrofit
- Android RxJava + Retrofit
- MVP + Dagger2
If you know the basics of the above things, the following example should be an easy ride. Let’s dive right in!
Create a new Android Studio project.
Libaries
Add the following libraries in your app’s build.gradle
file.
RxAndroid is just like RxJava. It contains the schedulers and threads for the Android framework.
Project Structure
The di package contains the dependency injection – Dagger2 related files. The mvp package consists of the MVP related ones and so on.
Where and how do we start?
It can get confusing when we are asked to use so many different things in our project.
The beginning is always tricky. Once we know the process, everything becomes easy.
So let’s list down our requirements and how we’ll approach them.
Our application would contain a single activity. It would host a RecyclerView to display the data from the API call. For the API call we’ll use Retrofit and RxJava.
- Create your activity and
recyclerview
xml layouts first. Lock the Design before you code. - Create the APIInterface.java class. It defines the API calls to be made.
- Create the POJO data class which would parse the response from the API call.
- Start by creating the Interface for the MVP – MainActivityContract.java.
- Create the di modules, components, scopes and qualifiers.
- Now create the PresenterImpl.
- Finally, write the MainActivity and RecyclerViewAdapter classes plugging the Dependencies and the MVP pattern in.
Layout code
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=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
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/txtCoin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/txtCurrentPrice"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/txtCurrentPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtCoin"
/>
<TextView
android:id="@+id/txtOneHourChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/txtOneHour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="1H"
app:layout_constraintEnd_toStartOf="@+id/txtOneHourChange"
app:layout_constraintTop_toTopOf="@+id/txtOneHourChange" />
<TextView
android:id="@+id/txt24HourChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtOneHourChange" />
<TextView
android:id="@+id/txt7Day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="7D"
app:layout_constraintEnd_toStartOf="@+id/txt7DayChange"
app:layout_constraintTop_toTopOf="@+id/txt7DayChange" />
<TextView
android:id="@+id/txt24Hour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="24H"
app:layout_constraintBottom_toTopOf="@+id/txt7DayChange"
app:layout_constraintEnd_toStartOf="@+id/txt24HourChange"
app:layout_constraintTop_toTopOf="@+id/txt24HourChange" />
<TextView
android:id="@+id/txt7DayChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txt24HourChange" />
</android.support.constraint.ConstraintLayout>
APIInterface.java
package com.journaldev.mvpdagger2retroiftrxjava.retrofit;
import com.journaldev.mvpdagger2retroiftrxjava.pojo.CryptoData;
import java.util.List;
import retrofit2.http.GET;
import retrofit2.http.Query;
import rx.Observable;
public interface APIInterface {
@GET("ticker/?")
Observable<List<CryptoData>> getData(@Query("limit") String limit);
}
The base URL would be defined in our RetrofitModule.
We’re using this API.
CryptoData.java
package com.journaldev.mvpdagger2retroiftrxjava.pojo;
import com.google.gson.annotations.SerializedName;
public class CryptoData {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("symbol")
public String symbol;
@SerializedName("rank")
public String rank;
@SerializedName("price_usd")
public String priceUsd;
@SerializedName("price_btc")
public String priceBtc;
@SerializedName("24h_volume_usd")
public String _24hVolumeUsd;
@SerializedName("market_cap_usd")
public String marketCapUsd;
@SerializedName("available_supply")
public String availableSupply;
@SerializedName("total_supply")
public String totalSupply;
@SerializedName("percent_change_1h")
public String percentChange1h;
@SerializedName("percent_change_24h")
public String percentChange24h;
@SerializedName("percent_change_7d")
public String percentChange7d;
@SerializedName("last_updated")
public String lastUpdated;
}
MainActivityContract.java
This is the blueprint of our View-Model-Presenter:
package com.journaldev.mvpdagger2retroiftrxjava.mvp;
import com.journaldev.mvpdagger2retroiftrxjava.pojo.CryptoData;
import java.util.List;
public interface MainActivityContract {
interface View {
void showData(List<CryptoData> data);
void showError(String message);
void showComplete();
void showProgress();
void hideProgress();
}
interface Presenter {
void loadData();
}
}
ActivityScope.java
package com.journaldev.mvpdagger2retroiftrxjava.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.journaldev.mvpdagger2retroiftrxjava.di.scopes;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Scope;
@Scope
@Retention(RetentionPolicy.CLASS)
public @interface ApplicationScope {
}
ActivityContext.java
package com.journaldev.mvpdagger2retroiftrxjava.di.qualifier;
import javax.inject.Qualifier;
@Qualifier
public @interface ActivityContext {
}
ApplicationContext.java
package com.journaldev.mvpdagger2retroiftrxjava.di.qualifier;
import javax.inject.Qualifier;
@Qualifier
public @interface ApplicationContext {
}
ContextModule.java
package com.journaldev.mvpdagger2retroiftrxjava.di.module;
import android.content.Context;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ApplicationContext;
import com.journaldev.mvpdagger2retroiftrxjava.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;
}
}
MainActivityContextModule.java
package com.journaldev.mvpdagger2retroiftrxjava.di.module;
import android.content.Context;
import com.journaldev.mvpdagger2retroiftrxjava.MainActivity;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ActivityContext;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ActivityScope;
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;
}
}
RetrofitModule.java
package com.journaldev.mvpdagger2retroiftrxjava.di.module;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ApplicationScope;
import com.journaldev.mvpdagger2retroiftrxjava.retrofit.APIInterface;
import dagger.Module;
import dagger.Provides;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
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://api.coinmarketcap.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.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;
}
}
MainActivityMvpModule.java
package com.journaldev.mvpdagger2retroiftrxjava.di.module;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ActivityScope;
import com.journaldev.mvpdagger2retroiftrxjava.mvp.MainActivityContract;
import dagger.Module;
import dagger.Provides;
@Module
public class MainActivityMvpModule {
private final MainActivityContract.View mView;
public MainActivityMvpModule(MainActivityContract.View mView) {
this.mView = mView;
}
@Provides
@ActivityScope
MainActivityContract.View provideView() {
return mView;
}
}
AdapterModule.java
package com.journaldev.mvpdagger2retroiftrxjava.di.module;
import com.journaldev.mvpdagger2retroiftrxjava.MainActivity;
import com.journaldev.mvpdagger2retroiftrxjava.RecyclerViewAdapter;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ActivityScope;
import dagger.Module;
import dagger.Provides;
@Module(includes = {MainActivityContextModule.class})
public class AdapterModule {
@Provides
@ActivityScope
public RecyclerViewAdapter getCoinList(RecyclerViewAdapter.ClickListener clickListener) {
return new RecyclerViewAdapter(clickListener);
}
@Provides
@ActivityScope
public RecyclerViewAdapter.ClickListener getClickListener(MainActivity mainActivity) {
return mainActivity;
}
}
Components are what inject the dependencies from the Modules.
ApplicationComponent.java
package com.journaldev.mvpdagger2retroiftrxjava.di.component;
import android.content.Context;
import com.journaldev.mvpdagger2retroiftrxjava.MyApplication;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.ContextModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.RetrofitModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ApplicationContext;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ApplicationScope;
import com.journaldev.mvpdagger2retroiftrxjava.retrofit.APIInterface;
import dagger.Component;
@ApplicationScope
@Component(modules = {ContextModule.class, RetrofitModule.class})
public interface ApplicationComponent {
APIInterface getApiInterface();
@ApplicationContext
Context getContext();
void injectApplication(MyApplication myApplication);
}
MainActivityComponent.java
package com.journaldev.mvpdagger2retroiftrxjava.di.component;
import android.content.Context;
import com.journaldev.mvpdagger2retroiftrxjava.MainActivity;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.AdapterModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.MainActivityMvpModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ActivityContext;
import com.journaldev.mvpdagger2retroiftrxjava.di.scopes.ActivityScope;
import dagger.Component;
@ActivityScope
@Component(modules = {AdapterModule.class, MainActivityMvpModule.class}, dependencies = ApplicationComponent.class)
public interface MainActivityComponent {
@ActivityContext
Context getContext();
void injectMainActivity(MainActivity mainActivity);
}
PresenterImpl.java
package com.journaldev.mvpdagger2retroiftrxjava.mvp;
import com.journaldev.mvpdagger2retroiftrxjava.pojo.CryptoData;
import com.journaldev.mvpdagger2retroiftrxjava.retrofit.APIInterface;
import java.util.List;
import javax.inject.Inject;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class PresenterImpl implements MainActivityContract.Presenter {
APIInterface apiInterface;
MainActivityContract.View mView;
@Inject
public PresenterImpl(APIInterface apiInterface, MainActivityContract.View mView) {
this.apiInterface = apiInterface;
this.mView = mView;
}
@Override
public void loadData() {
mView.showProgress();
apiInterface.getData("10").subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<CryptoData>>() {
@Override
public void onCompleted() {
mView.showComplete();
mView.hideProgress();
}
@Override
public void onError(Throwable e) {
mView.showError("Error occurred");
mView.hideProgress();
}
@Override
public void onNext(List<CryptoData> data) {
mView.showData(data);
}
});
}
}
@Inject
on the constructor says that this class’s object would be injected in the MainActivity.
The Presenter invokes the required View interface’s methods that’ll trigger the actions in the MainActivity.
MyApplication.java
package com.journaldev.mvpdagger2retroiftrxjava;
import android.app.Activity;
import android.app.Application;
import com.journaldev.mvpdagger2retroiftrxjava.di.component.ApplicationComponent;
import com.journaldev.mvpdagger2retroiftrxjava.di.component.DaggerApplicationComponent;
import com.journaldev.mvpdagger2retroiftrxjava.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;
}
}
MainActivity.java
package com.journaldev.mvpdagger2retroiftrxjava;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.journaldev.mvpdagger2retroiftrxjava.di.component.ApplicationComponent;
import com.journaldev.mvpdagger2retroiftrxjava.di.component.DaggerMainActivityComponent;
import com.journaldev.mvpdagger2retroiftrxjava.di.component.MainActivityComponent;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.MainActivityContextModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.module.MainActivityMvpModule;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ActivityContext;
import com.journaldev.mvpdagger2retroiftrxjava.di.qualifier.ApplicationContext;
import com.journaldev.mvpdagger2retroiftrxjava.mvp.MainActivityContract;
import com.journaldev.mvpdagger2retroiftrxjava.mvp.PresenterImpl;
import com.journaldev.mvpdagger2retroiftrxjava.pojo.CryptoData;
import java.util.List;
import javax.inject.Inject;
public class MainActivity extends AppCompatActivity implements MainActivityContract.View, RecyclerViewAdapter.ClickListener {
private RecyclerView recyclerView;
private ProgressBar progressBar;
MainActivityComponent mainActivityComponent;
@Inject
public RecyclerViewAdapter recyclerViewAdapter;
@Inject
@ApplicationContext
public Context mContext;
@Inject
@ActivityContext
public Context activityContext;
@Inject
PresenterImpl presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ApplicationComponent applicationComponent = MyApplication.get(this).getApplicationComponent();
mainActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityContextModule(new MainActivityContextModule(this))
.mainActivityMvpModule(new MainActivityMvpModule(this))
.applicationComponent(applicationComponent)
.build();
mainActivityComponent.injectMainActivity(this);
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(activityContext));
recyclerView.setAdapter(recyclerViewAdapter);
progressBar = findViewById(R.id.progressBar);
presenter.loadData();
}
@Override
public void launchIntent(String name) {
Toast.makeText(mContext, name, Toast.LENGTH_SHORT).show();
// startActivity(new Intent(activityContext, DetailActivity.class).putExtra("name", name));
}
@Override
public void showData(List<CryptoData> data) {
recyclerViewAdapter.setData(data);
}
@Override
public void showError(String message) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
@Override
public void showComplete() {
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
}
Thanks to DI and MVP, we’ve injected everything and separated the network calls from the MainActivity.
The MainActivity.java only populates the views as per the instructions from the Presenter.
The code for the RecyclerViewAdapter.java class is given below:
package com.journaldev.mvpdagger2retroiftrxjava;
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.journaldev.mvpdagger2retroiftrxjava.pojo.CryptoData;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private List<CryptoData> 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.txtCoin.setText(data.get(position).symbol);
holder.txtCurrentPrice.setText(data.get(position).priceUsd);
holder.txt1HourChange.setText(data.get(position).percentChange1h + "%");
holder.txt24HourChange.setText(data.get(position).percentChange24h + "%");
holder.txt7DayChange.setText(data.get(position).percentChange7d + "%");
}
@Override
public int getItemCount() {
return data.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private TextView txtCoin;
private TextView txtCurrentPrice;
private TextView txt1HourChange;
private TextView txt24HourChange;
private TextView txt7DayChange;
private ConstraintLayout constraintLayoutContainer;
ViewHolder(View itemView) {
super(itemView);
txtCoin = itemView.findViewById(R.id.txtCoin);
txtCurrentPrice = itemView.findViewById(R.id.txtCurrentPrice);
txt1HourChange = itemView.findViewById(R.id.txtOneHourChange);
txt24HourChange = itemView.findViewById(R.id.txt24HourChange);
txt7DayChange = itemView.findViewById(R.id.txt7DayChange);
constraintLayoutContainer = itemView.findViewById(R.id.constraintLayout);
constraintLayoutContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickListener.launchIntent(data.get(getAdapterPosition()).name);
}
});
}
}
public interface ClickListener {
void launchIntent(String name);
}
public void setData(List<CryptoData> data) {
this.data.addAll(data);
notifyDataSetChanged();
}
}
REBUILD the Project to create the Dagger files automatically.
The output of the above application in action is given below:
This brings an end to this hands-on tutorial in which we’ve used MVP, Dagger2, Retrofit, RxJava along with a RecyclerView. You can download the project from the link below.
Please explain the use of this application, i didn’t understand why to build this.
DaggerMainActivityComponent not find . getting error
rebuild your project
In first place thanks for the tutorial, it’s really good. I have a couple of questions.
Would it make much difference if we used javarx2?
And
Could you add some example tests?
thanks a lot!
HELP!
I am successfully generating DaggerMainActivityComponent however I cannot call builder on it? FYI I have turned on annotation processing and I have compared all my code. Also, this was not mentioned but under my manifest file I did add a line under application to point to MyApplication.
Any pointers or explanation of why this error is coming up is greatly appreciated.
thank you
Thanks a lote.
Its working proprly,Can u describe all class.
It’s a really good tutorial. Thanks to this tutorial I was able to understand dagger and rxjava
Hi, could you please tell me how to handle json object response with this code as it expecting json array i am getting following error
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
I am new to RxJava and Retrofit concepts and this tutorial is a great help
Nice article that cover major things like MVP, dagger2, retrofit and RxJava all together. Also like the Approach that guide how to start. However in case of multiple module/feature in the project, can you please describe how the project structure looks like?
getting error : android.app.Application cannot be provided without an @Inject constructor or from an @Provides-annotated method.
tried the solution from stackoverflow. nothing worked.
DaggerApplicationComponent is not working.
Clean and Rebuild the project.
Make sure you have added all the appropriate dependencies.
I am new to develop android dagger application, I am trying to solve this application but it gave me error as
Error:(17, 53) error: cannot find symbol class DaggerMainActivityComponent
in your project app file, same error will occur
Try cleaning and rebuilding your project.
not working
try this
https://stackoverflow.com/questions/41843034/dagger-2-does-not-generate-component