As we announced early this week here, we have been working for the past four months on the Gluon application for JavaOne.
This project makes use of Gluon Mobile, Gluon CloudLink and Gluon Charm Down.
This is the first of a series of blog posts about how we built the application. We’ll explain with a little bit of detail how we used Gluon Mobile to create the UI.
Gluon JavaOne App: Building the UI
While using the Gluon Plugin on your favorite IDE gives you a head start on creating a mobile project, when dealing with complex projects (in terms of number of views, services, back-end connections,…) there are things that require a few iterations to ease the way to the developers team, so they can start putting all the pieces together in perfect harmony.
In this post, we’ll cover a few of them.
Easing View creation
With the View concept in mind, and using the afterburner.fx framework, each view is defined by an fxml file and its presenter class. Each view usually requires access to a service (i.e. to access data from the backend using Gluon CloudLink), which can be done via injection, and probably access to other views.
We define an abstract GluonPresenter
, that will be extended by all the presenters:
public abstract class GluonPresenter{ private final T app = (T) MobileApplication.getInstance(); protected T getApp() { return app; } }
And it also comes in handy defining a GluonView
, that avoids defining an empty view class for each view:
public class GluonView extends FXMLView { private final Class presenter; public GluonView(Class extends GluonPresenter>> presenter) { this.presenter = presenter; } @Override public View getView() { return (View) super.getView(); } }
Doing so, a view like the speakers view can be now created easily, with its fxml file, that includes just the View container and a CharmListView control, and its presenter, where the list items are retrieved from the injected service:
public class SpeakersPresenter extends GluonPresenter{ @FXML private View speakers; @FXML private CharmListView speakersListView; @Inject private Service service; public void initialize() { speakersListView.setItems(service.retrieveSpeakers()); } }
Managing all the views
The OTNView
, our views manager, is an enum that collects all the view presenters, along with some icons and titles for the drawer, and some flags. A bunch of useful methods for each view are added, like registerView
, switchView
or getMenuItem
.
public enum OTNView { ACTIVITY_FEED (ActivityFeedPresenter.class, MaterialDesignIcon.ANNOUNCEMENT, SHOW_IN_DRAWER, HOME_VIEW, SKIP_VIEW_STACK), SESSIONS (SessionsPresenter.class, MaterialDesignIcon.DASHBOARD, SHOW_IN_DRAWER), SESSION (SessionPresenter.class, MaterialDesignIcon.RECORD_VOICE_OVER), SPEAKERS (SpeakersPresenter.class, MaterialDesignIcon.SPEAKER, SHOW_IN_DRAWER), SPEAKER (SpeakerPresenter.class, MaterialDesignIcon.SPEAKER), ... OTNView(Class extends GluonPresenter>> presenterClass, MaterialDesignIcon menuIcon, OTNViewFlag... flags ) { ... } public void registerView(MobileApplication app) { app.addViewFactory(id, () -> { GluonView view = new GluonView(presenterClass); view.getView().setName(id); return view.getView(); }); } public Optional
When we launch the application, we can easily register all the views:
public class OTNApplication extends MobileApplication { @Override public void init() { for (OTNView view : OTNView.values()) { view.registerView(this); } ... } }
Or if we want to access a given Speaker’s view from the Speakers View when a speaker is selected, we need to switch the view, get its presenter instance, and set the data from the given speaker. Thanks to all those methods mentioned above, it just take a line of code:
public class SpeakersPresenter extends GluonPresenter{ public void initialize() { speakersListView.setItems(service.retrieveSpeakers()); speakersListView.selectedItemProperty().addListener((observable, oldSpeaker, newSpeaker) -> { OTNView.SPEAKER.switchView() .ifPresent(presenter -> ((SpeakerPresenter) presenter).setSpeaker(newSpeaker)); }); } }
Or when we create the navigation drawer, we can add the all the items from views that are flagged as to be shown in the drawer:
public class OTNDrawer { private final NavigationDrawer drawer; public OTNDrawerPresenter() { drawer = new NavigationDrawer(); for (OTNView view : OTNView.values()) { if (view.isShownInDrawer()) { drawer.getItems().add(view.getMenuItem()); } } ... } }
New controls
While developing the OTN app, we’ve been eating our own dog food, making extensive use of Gluon Mobile. This has allowed us to test almost all the features that the library provides, fixing some bugs here and there, or realizing that some controls required some API improvements.
The next release of Gluon Mobile will benefit from all that work.
And we also started the development of new controls. Some of them will be included in that release too, others are for now kept in our labs, and will be released in future versions.
BottomNavigation Control
One of the controls already included in the library is the BottomNavigation
control, providing quick navigation between top-level views of an app, with two to five top-level destinations of similar importance referred to as action items.
The control is built by providing a string, an icon and an event handler to each of the buttons.
For instance, the Speaker view uses this control to give access to either the speaker’s information or the speaker’s sessions:
public class SpeakerPresenter extends GluonPresenter{ private BottomNavigation createBottomNavigation(final Speaker activeSpeaker) { BottomNavigation bottomNavigation = new BottomNavigation(); final ToggleButton infoButton = bottomNavigation.createButton("Info", MaterialDesignIcon.INFO.graphic(), e -> speakerView.setCenter(createScrollPane(new Label(activeSpeaker.getSummary()))); final ToggleButton sessionsButton = bottomNavigation.createButton("Sessions", MaterialDesignIcon.EVENT_NOTE.graphic(), e -> speakerView.setCenter(createSessionsListView(activeSpeaker))); bottomNavigation.getActionItems().addAll(infoButton, sessionsButton); return bottomNavigation; }
AvatarPane
While this control is still in our labs under development, we have developed an animated control that shows one to several images embedded in Avatar controls, while allows selection on each of them, displaying different content.
private AvatarPanecreateSpeakerAvatarPane(ObservableList speakers) { AvatarPane avatarPane = new AvatarPane<>(speakers); avatarPane.setExpanded(true); avatarPane.setCollapsible(false); avatarPane.setAvatarFactory(Util::getSpeakerAvatar); avatarPane.setContentFactory(speaker -> { Button speakerBtn = MaterialDesignIcon.CHEVRON_RIGHT.button(e -> OTNView.SPEAKER.switchView() .ifPresent(presenter -> ((SpeakerPresenter) presenter).setSpeaker(speaker))); GridPane gridPane = new GridPane(); gridPane.getStyleClass().add("content-box"); gridPane.add(new Label(speaker.getFullName()), 0, 0); gridPane.add(new Label(speaker.getJobTitle()), 0, 1); gridPane.add(new Label(speaker.getCompany()), 0, 2); gridPane.add(speakerBtn, 1, 0, 1, 3); gridPane.add(new Label(speaker.getSummary()), 0, 3, 2, 1); return createScrollPane(gridPane); }); return avatarPane; }
Other controls
Other controls have been developed like Toasts (available) or an ImageGallery control (labs):
Summary
It has been a great time working on UI development with Gluon Mobile, and certainly the next releases will benefit from it. If you’re an existing Gluon Mobile customer, you will have access to these releases as per usual. If you’re not yet a customer, head on over to the Gluon Mobile product page to learn more!
Stay tuned for the next series of blog posts, where we will cover how we integrated all platform-specific requirements by making use of Gluon Charm Down, and how we managed to synchronize our front-end with the backend, making use of Gluon CloudLink.
If you haven’t done it yet, download the application from the Google Play Store or the Apple App Store, and give it try, either if you are attending JavaOne or not.
And if you are at JavaOne, don’t miss the opportunity to attend our sessions and meet those of us there.