Lists GitHub users. Minimal app demonstrating cross-platform app development (Web, Android, iOS) where core logic is shared and transpiled from Java to JavaScript and Objective-C.
This project provides only source code of the shared logic. Here is the web version:
https://github-users-web.appspot.com/
See also:
Google claims that applications like Google Inbox can share up to 70% of client code between all the platforms. It's significant achievement when taking into account:
- reduced Total Cost of Ownership with only one code base to maintain
- naturally synchronized development process across all the teams (backend, presentation logic, frontend, mobile)
- the same features released for all the platforms
- and the time-to-market for new value is shorter
Just to name a few. Usually cross-platform development tools fall into these categories:
- abstraction over native UI components and IO operations - specialized API accessible from programming language of choice which is either interpreted or compiled for specific platform (possibilities are limited by what the API has to offer)
- the use of HTML+CSS on all platforms (discarding the possible advantages offered by native solutions on mobile devices)
But there is a third way, where only presentation logic code is shared and UI rendering and IO stays native. Where iOS, Web and Android developers can customize the app in every detail.
This approach seems to be the most demanding one in terms of software architecture. Common business logic cannot longer be expressed in purely technical terms of low-level events, requests, responses or threads. Mouse clicks or touch events are becoming a stream of semantically defined user intents like "select element". HTTP requests to remote services are becoming a streams of domain data being provided asynchronously in any moment. Concurrency is handled by declaring what to do leaving when and how to reactive framework. Such an approach, even though more challenging conceptually, is worth the effort. Abstracting app logic from specific platform brings much better overall architecture which pays off in the future when the application grows.
As there is no blueprint from Google on how to build applications like Google Inbox, I decided to use my whole experience to "reverse engineer" possible approach and provide such a minimal project. I hope to push it even further in terms of reactive programming on top of RxJava as it is quite popular on Android, there is a GWT port, and apparently it is possible to transpile the whole library to Objective-C.
It does not matter so much what this application is doing and if it is useful at all. I did not want to provide any backend component and struggle with deployment. Therefore I decided to display data loaded from one of public APIs available on the Internet and GitHub users search API will serve as a good example.
As application user I want to submit query to search for GitHub users so that relevant user list will be displayed.
This project provides conceptual presentation logic without actual UI code bound to any platform.
For the platform specific code see:
Technically it is a library containing Java code which will be transpiled either to JavaScript (GWT) or to Objective-C (J2ObjC) code for Web and iOS platform respectively. In case of Android platform the Java code can be used directly.
Only minimal set of Java 8 classes is used plus:
- javax-inject - JSR-330 Dependency Injection
- RxJava - Reactive Extensions for the JVM
- junit - JUnit is a simple framework to write repeatable tests
- mockito - Tasty mocking framework for unit tests in Java
- AssertJ - Fluent assertions for java
These popular dependencies will either have emulation on all the platforms or be transpiled to the native code.
Basically view is dumb and can be mocked while presenter has testable logic.
See Model-View-Presenter article article on Wikipedia.
RxJava is a crucial component of this solution providing:
- event distribution mechanism decoupling presenters and therefore also associated visual components
- handling of asynchronous responses from remote web services
- abstracting the way how UI events are streamed to the presenter logic
Application events are defined in the com.xemantic.githubusers.logic.event package. Thanks to being separated from the rest of application logic, they can be easily used to decouple components (presenter logic).
Event distribution is based on event channels where:
- publisher is injected with Sink
- subscriber is injected with
Observable
Note: the original design was based on the EventBus concept, but @ibaca pointed out that thanks to typed injections, it is possible to eliminate explicit EventBus completely.
public class FooPresenter {
@Inject
public FooPresenter(FooView view, Sink<BarEvent> barSink) {
super(
view.click$()
.onNext(e -> barSink.publish(new BarEvent("foo")))
);
}
}
Note: several Sink
s of different event types can be injected.
public class BuzzPresenter {
@Inject
public BuzzPresenter(BuzzView view, Observable<BarEvent> barEvent$) {
super(
barEvent$.onNext(e -> view.display(e.getMessage()))
);
}
}
Note: several Observable
s of different event types can be injected.
The only events defined in this project are application events. Platform events specific
to UI will always come out of View interfaces and their observe
+ Intent methods.
Many UI events, like specific user intent received via click or touch event, will not carry any payload. They will be just marked with Trigger as an event type.
When presenter is started it will usually:
- subscribe to general application events
- subscribe to events generated by UI actions
The presenter logic defines how to react to these events, it might:
- display something on view
- call external service
- change internal presenter state
- publish general application events to be received by decoupled event consumers
Most of these operations are easily testable with mocked view.
Is provided exclusively by interfaces UserSearchService
which returns Observable
(technically Single
) of SearchResult
holding also the list of Users.
Services can be implemented using:
- Retrofit + RxJava on Android
- AutoREST for GWT
The structure of SearchResult
interface reflects
JSON structure of GitHub API response.
When implementing these entities various methods might be used like
- GSON/jackson json parser for android
@JsInterop
annotations for GWT
See com.xemantic.githubusers.logic.view package.
See com.xemantic.githubusers.logic.presenter package.
Expectations for these presenters are visible in their test cases which account for most code in this project.
By following MVP principles all the views are prepared in the way they can be mocked and assumptions can be made against their state in the unit tests. Ready presenters are coming with full test coverage and test cases can be transpiled as well to be run again on the target platform. See example UserPresenterTest.
Note: end your test cases with:
InOrder inOrder = inOrder(mock1, mock2);
// ...
// method verifications
// ...
verifyNoMoreInteractions(mock1, mock2);
inOrder.verifyNoMoreInteractions();
This verification order gives much more convenient error messages.
The Material Design will be used on all the platforms with help of Material Components.
This project is following Semantic Versioning scheme with 3 decimal numbers separated by dots. All 3 version numbers (major, minor, bugfix) should be always present, which implies that for major and minor releases bugfix version will be set to 0.