building maintainable app #droidconzg

72
Building maintainable app with MVP and Dagger2 KRISTIJ AN JURKOVIĆ ANDROID TEAM LEAD @ INFINUM

Upload: kristijan-jurkovic

Post on 13-Apr-2017

255 views

Category:

Technology


3 download

TRANSCRIPT

Building maintainable app with MVP and Dagger2KRISTIJAN JURKOVIĆ ANDROID TEAM LEAD @ INFINUM

We're hiring.HTTPS://INFINUM.CO/CAREERS

We're an independent design & development agency.

INFINUM

• 90+ people in 3 offices

• 15 android developers

• hundreds of projects

OUR BIGGEST ISSUES?

“Sometimes when you fill a vacuum, it still sucks.”

― Dennis Ritchie

MVP TO THE RESCUE?

PROGRAM TO INTERFACES NOT IMPLEMENTATIONS

ModelPresenterView

LoginActivity LoginPresenterImpl LoginInteractorImpl

LoginView LoginPresenter LoginInteractor

showLoading() hideLoading() setUsernameError() setPasswordError()

showLoading() hideLoading() setUsernameError() setPasswordError()

login(username, pass)

loginPresenter loginView loginInteractor

login(username, pass) login(username, pass, listener)

login(username, pass, listener)

VIEW PRESENTER MODEL

public interface HomeView { ...}

public interface HomePresenter { ...}

public interface CurrencyInteractor { ...}

VIEW

PRESENTER

MODEL

public interface HomeView { ...}

VIEW

public class HomeActivity extends BaseActivity implements HomeView {

// this is an interface HomePresenter presenter;

...}

public interface HomePresenter { ...}

PRESENTER

public class HomePresenterImpl implements HomePresenter {

// interface private HomeView view;

// and another interface private CurrencyInteractor interactor;

public HomePresenterImpl(HomeView view, CurrencyInteractor interactor) {

this.view = view; this.interactor = interactor; }

... }

public interface CurrencyInteractor { ...}

MODEL

public class CurrencyInteractorImpl implements CurrencyInteractor {

... }

HOW SHOULD I GET MY CONTENT?

public interface HomeView {void showCurrencies(List<Currency> currencies);

}

public interface HomePresenter { void loadCurrencyList();}

public interface CurrencyInteractor { void getCurrencyList(CurrencyListener listener);}

public class HomeActivity extends BaseActivity implements HomeView {

private void init() { presenter = new HomePresenterImpl(this,

new CurrencyInteractorImpl()); presenter.getCurrencyList(); }

@Override public void showCurrencies(List<Currency> currencies) { // display data }

}

public class HomePresenterImpl implements HomePresenter {

...@Override public void loadCurrencyList() { interactor.getCurrencyList(...); }

}

public class CurrencyInteractorImpl implements CurrencyInteractor {

... @Override public void getCurrencyList(

CurrencyListener listener) {

// do API/DB call // return result with listener }

}

VIEW SHOULDN’T CREATE ITS DEPENDENCIES

DEPENDENCY INJECTION

JSR 330

• 5 annotations - @Named, @Inject, @Qualifier, @Scope,

@Singleton

• 1 interface - Provider<T>

DAGGER2 TO THE RESCUE

DAGGER 2

• @Module, @Provides, @Component, @Subcomponent,

ScopedProvider

• Injection into Fields, Constructors, Methods

• Each @Inject has to have its @Provides

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

@Modulepublic class ApiModule {

@Provides @Singleton public ApiService provideApiService(

OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) {

return RestUtils.createApiService(

client, endpoint, converter, ApiService.class);

}}

@Modulepublic class ApiModule {

@Provides @Singleton public ApiService provideApiService(

OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) {

return RestUtils.createApiService(

client, endpoint, converter, ApiService.class);

}}

@Modulepublic class ApiModule {

@Provides @Singleton public ApiService provideApiService(

OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) {

return RestUtils.createApiService(

client, endpoint, converter, ApiService.class);

}}

@Modulepublic class GsonConverterModule {

@Provides @Singleton public Converter.Factory

provideConverter(Gson gson) {

return GsonConverterFactory.create(gson); }}

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

EXECUTORS MODULE

@Component(modules = { HostModule.class, GsonConverterModule.class, ClientModule.class, LoggerModule.class, ExecutorsModule.class, ApiModule.class, GsonModule.class})@Singletonpublic interface AppComponent {}

public class MyApplication extends Application {

protected AppComponent appComponent;

protected void init() { appComponent = DaggerAppComponent.create(); }}

HOW CAN WE REUSE THAT IN OUR ACTIVITIES?

public class HomeActivity extends BaseActivity implements HomeView {

private void init() { presenter = new HomePresenterImpl(this,

new CurrencyInteractorImpl()); presenter.getCurrencyList(); }

@Override public void showCurrencies(List<Currency> currencies) { // display data }

}

• Inject presenter into view

• Inject view and interactor into presenter

@Modulepublic class HomeModule {

private HomeView view; public HomeModule(HomeView view) { this.view = view; }

@Provides public HomeView provideView() { return view; }

@Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; }

@Provides public CurrencyInteractor provideInteractor(

CurrencyInteractorImpl interactor) { return interactor; }}

@Modulepublic class HomeModule {

private HomeView view; public HomeModule(HomeView view) { this.view = view; }

@Provides public HomeView provideView() { return view; }

@Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; }

@Provides public CurrencyInteractor provideInteractor(

CurrencyInteractorImpl interactor) { return interactor; }}

@Modulepublic class HomeModule {

private HomeView view; public HomeModule(HomeView view) { this.view = view; }

@Provides public HomeView provideView() { return view; }

@Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; }

@Provides public CurrencyInteractor provideInteractor(

CurrencyInteractorImpl interactor) { return interactor; }}

public class CurrencyInteractorImpl implements CurrencyInteractor {

@Injectpublic CurrencyInteractorImpl(ApiService service) {

}}

public class CurrencyInteractorImpl implements CurrencyInteractor {

@Injectpublic CurrencyInteractorImpl(ApiService service) {

}}

@Modulepublic class HomeModule {

private HomeView view; public HomeModule(HomeView view) { this.view = view; }

@Provides public HomeView provideView() { return view; }

@Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; }

@Provides public CurrencyInteractor provideInteractor(

CurrencyInteractorImpl interactor) { return interactor; }}

public class HomePresenterImpl implements HomePresenter {

@Inject public HomePresenterImpl(HomeView view,

CurrencyInteractor interactor) {

this.view = view; this.interactor = interactor; }}

public class HomeActivity extends BaseActivity implements HomeView {

@Inject HomePresenter presenter;

}

@Subcomponent(modules = HomeModule.class)public interface HomeComponent { void inject(HomeActivity activity);}

WHAT’S THAT “SUBCOMPONENT” THING

YOU MENTIONED?

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

HOME MODULE

HOMECOMPONENT

EXECUTORS MODULE

@Component(modules = { ...})@Singletonpublic interface AppComponent {

HomeComponent plus(HomeModule module);}

public abstract class BaseActivity extends AppCompatActivity {

Override protected void onCreate(Bundle savedInstanceState) { ... injectDependencies(MyApplication.getAppComponent()); }

protected abstract void injectDependencies(AppComponent appComponent);

}

public class HomeActivity extends BaseActivity implements HomeView {

protected void injectDependencies(AppComponent appComponent) {

appComponent.plus(new HomeModule(this)).inject(this);

}}

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

HOME MODULE

HOMECOMPONENTSESSIONCOMPONENT

SESSION MODULE

EXECUTORS MODULE

SATISFACTION LEVEL 9001

“If you don’t like testing your product, most likely your customers won’t like to test it

either.”

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

EXECUTORS MODULE

APP COMPONENT

HOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

EXECUTORS MODULE

APPTESTCOMPONENT

MOCKHOST MODULE

CONVERTER MODULE

CLIENT MODULE

LOGGER MODULE

API MODULE

GSON MODULE

SYNC EXECUTORS

MODULE

@Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ...})@Singletonpublic interface AppTestComponent extends AppComponent {

void inject(MyTestApplication app);}

@Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ...})@Singletonpublic interface AppTestComponent extends AppComponent {

void inject(MyTestApplication app);}

@Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ...})@Singletonpublic interface AppTestComponent extends AppComponent {

void inject(MyTestApplication app);}

public class MyTestApplication extends MyApplication implements TestLifecycleApplication {

@Override protected void init() { appComponent = DaggerAppTestComponent.create(); }}

protected void enqueueResponse(String filename) { String body = ResourceUtils.readFromFile(filename); MockResponse mockResponse =

new MockResponse().setBody(body).setResponseCode(HttpURLConnection.HTTP_OK);

mockWebServer.enqueue(mockResponse);}

@Overridepublic void setup() throws Exception { super.setup(); controller = Robolectric

.buildActivity(MockActivity.class)

.create()

.start() .resume() .visible();

fragment = DashboardDrivingModeFragment.newInstance();

controller.get().getSupportFragmentManager() .beginTransaction() .replace(R.id.container, fragment, null) .commit(); ButterKnife.bind(this, fragment.getView());}

@Testpublic void testEmptyStateNotVisible() { enqueueResponse(“rest-currency-response.json”); btnCurrencyList.performClick(); assertThat(emptyView).isNotVisible();}

THINGS TO REMEMBER

• Orientation change

THINGS TO REMEMBER

• Dagger2 is a powerful tool - make good use of it

• Save yourselves from regression bugs

REFERENCES

• http://antonioleiva.com/mvp-android/

• https://medium.com/@czyrux/presenter-surviving-

orientation-changes-with-

loaders-6da6d86ffbbf#.xou7c71uz

• http://frogermcs.github.io/dependency-injection-with-

dagger-2-custom-scopes/

• https://www.youtube.com/watch?v=oK_XtfXPkqw

We're hiring.HTTPS://INFINUM.CO/CAREERS

Any questions? KRISTIJ[email protected] @KJURKOVIC

Visit infinum.co or find us on social networks:

infinum.co infinumco infinumco infinum