6. Our Goal Code
• Easy to iterate on
• Collaboration-friendly
• Separated concerns
• Easy to test
7. Why does Android
Suck at testing?
• Activities, Services, Broadcast Receivers,
Content Providers, Bundles, Intents
• Fragments, Views, Notifications, Resources
• Databases, IPC, Threads, Storage
• Thousands of APIs related to services and
specific hardware features of devices
• Support libraries and external third party libraries
9. Dependency Injection
What we can easily replace –
we can easily test
By declaring on which other components or abilities
our current component depends, we get:
• Decoupled code
• Better understanding of what this components
does, and what it needs
• Replaceable and extendible code
• Easy to mock and test
Watch out for static class and new
variable declarations
11. public class RateUsManager {
public RateUsManager(Context context) {
this.context = context;
this.prefs = PreferenceManager.getDefaultShared
Preferences(app);
this.analytics = new Analytics(context);
}
public void showIfNeeded {
if (lots of code conditions depending on prefs) {
prefs.edit().putInt(RATE_US_SHOWN,
prefs.getInt(RATE_US_SHOWN,0) + 1);
analytics.report(“rate us shown”);
}
}
private void reportRate(int starts) {
NetworkManager.reportRate(starts);
}
}
Dependency
Injection
12. public class RateUsManager {
public RateUsManager(RateUsPrefrences ratePrefs,
NetworkModule networkManager,
AnalyticsHelper analytics) {
}
public void showIfNeeded {
if (ratePrefs.shouldShow()) {
ratePrefs.increaseShowCount();
analytics.report(“rate us shown”);
}
}
private void reportRate(int starts) {
networkManager.reportRate(starts);
}
}
Dependency
Injection
13. Independent
Business Logic
We want to separate our business logic code
from the framework independent code:
• It will make our code more reusable
• It will make our code better structured and easier
to maintain
• Testing will become much clearer
16. VIPER
View
Interactor
Presenter
Entity
Repository
An interface which wraps our UI components:
• It should allow the presenter to communicate at a
higher level of abstraction, express in terms on its
content.
• It’s passive, it will wait for the presenter to give it
content to display .
• It will not ask the presenter for content itself.
• It’s not a framework specific interface.
17. VIPER
View
Interactor
Presenter
Entity
Repository
• It’s where our business logic is at.
• It contains the business logic to manipulate
model objects (entities) and to carry out a specific
task.
• The work done in an Interactor should be
independent of any UI.
• It will communicate with the repositories, server,
and will convert the data to UI objects before
passing it to the presenter.
• It’s a POJO – it handles only POJO data .
• It’s the main area of our testing.
20. VIPER Abstracts the storage technique used in an
applications:
• It’s called by the interaction.
• It’s feature specific (or entity specific), thus
following the single responsibility principle.
• Allows us to avoid god classes for DB work
and increases our flexibility.
View
Interactor
Presenter
Entity
Repository
22. public interface SomeFeatureVIPER {
interface View {
//some UI abilities, (showProgress, onUsersLoaded)
}
interface Interactor {
//some task which returns some UI data
}
interface Presenter {
// some high level action to be made due
to user input
// or lifecycle event
}
interface Repository {
// some data which is stored in some way
}
}
The
Contract
26. public class EmployeeInteractor implements EmployeesVIP.Interactor {
private final ServerApi serverApi;
private final EmployeesVIP.Repository repository;
@Inject
public EmployeeInteractor(ServerApi serverApi,
EmployeesVIP.Repository repository) {
this.serverApi = serverApi;
this.repository = repository;
}
The
Interactor
@Override
public Observable<EmployeeResponse> loadEmployees() {
//try to get from db first, if it's empty go to server and
//save data to db
Observable<EmployeeResponse> server =
serverApi.getEmployees()
.doOnNext(repository::saveData);
return repository.getResponse().switchIfEmpty(server);
}
@Override
public Observable<List<Employee>> getTopLevelManagement() {
return Observable.create(subscriber -> {
List<Employee> topLevelManagement =
repository.getTopLevelManagement();
subscriber.onNext(topLevelManagement);
subscriber.onCompleted();
});
}
}
27.
The
Presenter
public class EmployeePresenter extends BasePresenter implements EmployeesVIP.Pre
private final EmployeesVIP.Interactor interactor;
private EmployeesVIP.View view;
@Inject
public EmployeePresenter(EmployeesVIP.Interactor interactor,
EmployeesVIP.View view) {
this.interactor = interactor;
this.view = view;
}
@Override
public Observable<EmployeeResponse> loadCompany() {
return interactor.loadEmployees()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(employeeResponse ->
view.setPageTitle(employeeResponse.companyName));
}
@Override
public void showTopLevelManagement() {
Subscription subscribe = interactor.getTopLevelManagement()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(employees -> view.onUsersLoaded(employees),
throwable -> //handle error here);
addSubscription(subscribe);
}
}
28. public class EmployeeView implements EmployeesVIP.View {
private final AdapterEmployees adapter;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
@BindView(R.id.view_switcher)
ViewSwitcher viewSwitcher;
public EmployeeView(AppCompatActivity activity,
AdapterEmployees adapter) {
this.adapter = adapter;
ButterKnife.bind(this, activity);
recyclerView.setLayoutManager(new GridLayoutManager(activity, 3));
recyclerView.setAdapter(adapter);
}
The
View
@Override
public void onUsersLoaded(List<Employee> employees) {
if (viewSwitcher.getCurrentView().getId() == R.id.progress_bar) {
viewSwitcher.showNext();
}
adapter.setData(employees);
}
}
29. public class ActivityTopLevelManagement extends
ActivityBaseEmployee {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_top_managers);
ButterKnife.bind(this);
bindEmployeePresenter();
presenter.loadCompany()
.subscribe(employeeResponse->
presenter.showTopLevelManagement());
}
}
The
Android