xemantic / github-users

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.
GNU General Public License v3.0
6 stars 2 forks source link
android cross-compilation cross-platform eventbus frontend gwt ios j2objc java javascript objective-c reactive-programming rxjava transpilation web

Build Status

About github-users

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.

Demo

This project provides only source code of the shared logic. Here is the web version:

https://github-users-web.appspot.com/

See also:

Why I started such a project?

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:

Just to name a few. Usually cross-platform development tools fall into these categories:

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.

Use case

As application user I want to submit query to search for GitHub users so that relevant user list will be displayed.

Architecture

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.

Dependencies

Only minimal set of Java 8 classes is used plus:

These popular dependencies will either have emulation on all the platforms or be transpiled to the native code.

Model-View-Presenter

Basically view is dumb and can be mocked while presenter has testable logic.

See Model-View-Presenter article article on Wikipedia.

Reactive paradigm

RxJava is a crucial component of this solution providing:

Events

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:

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.

Emitting Events

public class FooPresenter {

  @Inject
  public FooPresenter(FooView view, Sink<BarEvent> barSink) {
    super(
      view.click$()
          .onNext(e -> barSink.publish(new BarEvent("foo")))  
    );
  }

}

Note: several Sinks of different event types can be injected.

Receiving Events

public class BuzzPresenter {

  @Inject
  public BuzzPresenter(BuzzView view, Observable<BarEvent> barEvent$) {
    super(
      barEvent$.onNext(e -> view.display(e.getMessage()))  
    );
  }

}

Note: several Observables of different event types can be injected.

Application Events vs Platform UI Events

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.

Presenter Lifecycle and Events

When presenter is started it will usually:

The presenter logic defines how to react to these events, it might:

Most of these operations are easily testable with mocked view.

Service Access Layer

Service

Is provided exclusively by interfaces UserSearchService which returns Observable (technically Single) of SearchResult holding also the list of Users.

Services can be implemented using:

Model

The structure of SearchResult interface reflects JSON structure of GitHub API response.

When implementing these entities various methods might be used like

View

See com.xemantic.githubusers.logic.view package.

Presenter

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.

Testing

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.

User Experience design

The Material Design will be used on all the platforms with help of Material Components.

Versioning

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.