building maintainable app #droidconzg
TRANSCRIPT
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 class HomeActivity extends BaseActivity implements HomeView {
// this is an 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 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 }
}
JSR 330
• 5 annotations - @Named, @Inject, @Qualifier, @Scope,
@Singleton
• 1 interface - Provider<T>
DAGGER 2
• @Module, @Provides, @Component, @Subcomponent,
ScopedProvider
• Injection into Fields, Constructors, Methods
• Each @Inject has to have its @Provides
@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(); }}
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 }
}
@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);}
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
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
• 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
Any questions? KRISTIJ[email protected] @KJURKOVIC
Visit infinum.co or find us on social networks:
infinum.co infinumco infinumco infinum