未加星标

Using Couchbase Lite in a Java Gluon Application

字体大小 | |
[数据库(综合) 所属分类 数据库(综合) | 发布者 店小二04 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

I recently wrote about building desktop applications with Couchbase Lite and JavaFX . As demonstrated Couchbase makes an excellent solution for data storage and sync in a desktop application because of the Java SDK available. However, I realize JavaFX is not for everyone.

There is another, similar framework for building desktop applications in Java. It is called Gluon, and it offers support for Android and iOS applications as well. However, we're strictly looking at desktop in this example.

We're going to see how to create a Gluon desktop application using nearly the same code that was found in our previous JavaFX example.

The Requirements

There are a few requirements towards building a Gluon application that uses Couchbase.

JDK 1.7+ IntelliJ IDEA Couchbase Sync Gateway

I don't typically make this a requirement, but it is far easier to create a Gluon application with an IDE like IntelliJ, thus why it is in the list. There is a plugin for IntelliJ that will construct a Gluon project with Gradle and everything you need.

While Couchbase Sync Gateway isn't truly a requirement, it is necessary if you want to add synchronization support between your application and Couchbase Server / other platforms and devices.

Creating a New Gluon Project

If you decide to use IntelliJ to build your project, make sure you've already downloaded the Gluon plugin as described here .

Using IntelliJ, create a new project, but choose to create a Gluon Desktop - Multiple View Project with FXML project as seen below.


Using Couchbase Lite in a Java Gluon Application

Ultimately, it is up to you where to go from here, but to stay as close as possible to this guide, give your project a com.couchbaselabs package name and gluon main class.


Using Couchbase Lite in a Java Gluon Application

Everything that follows can be left as the default as we're only going to make a two page application with Gluon. When we're done, hopefully we're left with a file and directory structure that looks like the following:

gradle wrapper src main java com couchbaselabs views PrimaryPresenter.java PrimaryView.java SecondaryPresenter.java SecondaryView.java CouchbaseSingleton.java Todo.java gluon.java resources couchbaselabs views primary.fxml primary.css secondary.fxml secondary.css style.css icon.png build.gradle gradlew gradlew.bat

You'll notice that I did create a few extra files in there such as CouchbaseSingleton.java and Todo.java .

Essentially we have XML views and controllers to go with those views. This is very similar to what we saw in aJavaFX application. When it comes to designing those views, we have a few options. We can use raw XML, or we can use SceneBuilder . Now this SceneBuilder is not to be confused with JavaFX SceneBuilder. I made this mistake and was banging my head for quite a bit of time. The version we want will support Gluon applications.

Before we start adding application code, we should add our dependencies to the project Gradle file. If you're unfamiliar with Gradle, it does the same job as Maven or Ant. The syntax is different, but I personally find it a little cleaner. Open the project's build.gradle and include the following code:

buildscript { repositories { jcenter() } dependencies { classpath 'org.javafxports:jfxmobile-plugin:1.0.8' } } apply plugin: 'org.javafxports.jfxmobile' repositories { jcenter() maven { url 'http://nexus.gluonhq.com/nexus/content/repositories/releases' } } mainClassName = 'com.couchbaselabs.gluon' dependencies { compile 'com.gluonhq:charm:3.0.0' compile 'com.couchbase.lite:couchbase-lite-java:1.3.0' desktopRuntime 'com.gluonhq:charm-desktop:3.0.0' }

What's particularly important here are the dependencies:

dependencies { compile 'com.gluonhq:charm:3.0.0' compile 'com.couchbase.lite:couchbase-lite-java:1.3.0' desktopRuntime 'com.gluonhq:charm-desktop:3.0.0' }

This will include the Couchbase Lite library as well as the desktop application runtime for Gluon.

With the project ready to go, we can start developing the application.

Designing the Couchbase Data Layer

When working with Couchbase it is a good idea to create a singleton instance of it. This means we're going to use the same open instance throughout the entire application, until we decide to close it.

Open the project's src/main/java/com/couchbaselabs/CouchbaseSingleton.java file and include the following code:

package com.couchbaselabs; import com.couchbase.lite.*; import com.couchbase.lite.replicator.Replication; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class CouchbaseSingleton { private Manager manager; private Database database; private Replication pushReplication; private Replication pullReplication; private static CouchbaseSingleton instance = null; private CouchbaseSingleton() { try { this.manager = new Manager(new JavaContext("data"), Manager.DEFAULT_OPTIONS); this.database = this.manager.getDatabase("fx-project"); View todoView = database.getView("todos"); todoView.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), document); } }, "1"); } catch (Exception e) { e.printStackTrace(); } } public static CouchbaseSingleton getInstance() { if(instance == null) { instance = new CouchbaseSingleton(); } return instance; } public Database getDatabase() { return this.database; } public void startReplication(URL gateway, boolean continuous) { this.pushReplication = this.database.createPushReplication(gateway); this.pullReplication = this.database.createPullReplication(gateway); this.pushReplication.setContinuous(continuous); this.pullReplication.setContinuous(continuous); this.pushReplication.start(); this.pullReplication.start(); } public void stopReplication() { this.pushReplication.stop(); this.pullReplication.stop(); } public Todo save(Todo todo) { Map<String, Object> properties = new HashMap<String, Object>(); Document document = this.database.createDocument(); properties.put("type", "todo"); properties.put("title", todo.getTitle()); properties.put("description", todo.getDescription()); try { todo.setDocumentId(document.putProperties(properties).getDocument().getId()); } catch (Exception e) { e.printStackTrace(); } return todo; } public ArrayList<Todo> query() { ArrayList<Todo> results = new ArrayList<Todo>(); try { View todoView = this.database.getView("todos"); Query query = todoView.createQuery(); QueryEnumerator result = query.run(); Document document = null; for (Iterator<QueryRow> it = result; it.hasNext(); ) { QueryRow row = it.next(); document = row.getDocument(); results.add(new Todo(document.getId(), (String) document.getProperty("title"), (String) document.getProperty("description"))); } } catch (Exception e) { e.printStackTrace(); } return results; } }

If you saw the JavaFX application I built previously, you'll notice that this singleton is the same between the two projects. You can even use a similar version for Android.

In the CouchbaseSingleton constructor method we are creating and opening a local database called fx-project . This database will be used throughout the application. We are also creating our Couchbase Lite view for querying. This todos view will emit a key-value pair of document id and document for every document in the local database.

The constructor method is private, meaning we don't want the user to be able to instantiate an object from it. Instead we want to use a static getInstance method to get the job done.

While we won't worry about replication until later in the guide, we do want to lay the foundation. The startReplication method will allow us to define bi-directional sync with a Sync Gateway and the stopReplication method will allow us to stop replication, maybe when the application closes.

Now we have our functions for saving and loading data.

public Todo save(Todo todo) { Map<String, Object> properties = new HashMap<String, Object>(); Document document = this.database.createDocument(); properties.put("type", "todo"); properties.put("title", todo.getTitle()); properties.put("description", todo.getDescription()); try { todo.setDocumentId(document.putProperties(properties).getDocument().getId()); } catch (Exception e) { e.printStackTrace(); } return todo; }

In the save method we are accepting a custom Todo object. This object really just contains an id, a title, and a description. The class looks something like this:

package com.couchbaselabs; import java.util.*; public class Todo { private String documentId; private String title; private String description; public Todo(String documentId, String title, String description) { this.documentId = documentId; this.title = title; this.description = description; } public Todo(String title, String description) { this.documentId = UUID.randomUUID().toString(); this.title = title; this.description = description; } public void setDocumentId(String documentId) { this.documentId = documentId; } public String getDocumentId() { return this.documentId; } public String getTitle() { return this.title; } public String getDescription() { return this.description; } }

The above class is found in the src/main/java/com/couchbaselabs/Todo.java file. What we're doing is actually taking the object and adding it as properties to a Couchbase NoSQL document. After we save the document and obtain an id, we return the same document with the id included.

The query function will execute the view that we created earlier and add each of the result items to an array of Todo object, bringing our database singleton to an end.

Creating a View for Listing Data

We are going to be creating an application that uses multiple Gluon views instead of trying to slap everything into the same view. This is not to be confused with Couchbase Lite Views which are on the topic of data, not UI.

The default view will be the first view that comes up when we launch the application. This view will show a list of all our todo elements. If not using SceneBuilder, the XML markup found in src/main/resources/com/couchbaselabs/views/primary.fxml would look like the following:

<?xml version="1.0" encoding="UTF-8"?> <?import com.gluonhq.charm.glisten.mvc.View?> <?import javafx.scene.control.ListView?> <?import javafx.scene.layout.BorderPane?> <View fx:id="primary" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="350.0" stylesheets="@primary.css" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.couchbaselabs.views.PrimaryPresenter"> <center> <ListView fx:id="fxListView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" /> </center> </View>

The view that comes out of this will look like the following:


Using Couchbase Lite in a Java Gluon Application

You'll see in the image that there is a navigation bar with a button, but it doesn't appear in the XML layout. The layout instead only contains the list view. However, the XML does reference our src/main/java/com/couchbaselabs/views/PrimaryPresenter.java file. This is the file where we not only define the navigation bar, but any logic that powers the particular view.

The src/main/java/com/couchbaselabs/views/PrimaryPresenter.java file will hold a lot of resemblence to our JavaFX project, with the differences being in the navigation component.

package com.couchbaselabs.views; import com.couchbaselabs.CouchbaseSingleton; import com.couchbaselabs.Todo; import com.couchbase.lite.Database; import com.couchbase.lite.Document; import com.couchbaselabs.gluon; import com.gluonhq.charm.glisten.application.MobileApplication; import com.gluonhq.charm.glisten.control.AppBar; import com.gluonhq.charm.glisten.mvc.View; import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.util.Callback; public class PrimaryPresenter { private CouchbaseSingleton couchbase; @FXML private View primary; @FXML private ListView fxListView; public void initialize() { try { this.couchbase = CouchbaseSingleton.getInstance(); fxListView.getItems().addAll(this.couchbase.query()); this.couchbase.getDatabase().addChangeListener(new Database.ChangeListener() { @Override public void changed(Database.ChangeEvent event) { for(int i = 0; i < event.getChanges().size(); i++) { final Document retrievedDocument = couchbase.getDatabase().getDocument(event.getChanges().get(i).getDocumentId()); Platform.runLater(new Runnable() { @Override public void run() { int documentIndex = indexOfByDocumentId(retrievedDocument.getId(), fxListView.getItems()); for (int j = 0; j < fxListView.getItems().size(); j++) { if (((Todo) fxListView.getItems().get(j)).getDocumentId().equals(retrievedDocument.getId())) { documentIndex = j; break; } } if (retrievedDocument.isDeleted()) { if (documentIndex > -1) { fxListView.getItems().remove(documentIndex); } } else { if (documentIndex == -1) { fxListView.getItems().add(new Todo(retrievedDocument.getId(), (String) retrievedDocument.getProperty("title"), (String) retrievedDocument.getProperty("description"))); } else { fxListView.getItems().remove(documentIndex); fxListView.getItems().add(new Todo(retrievedDocument.getId(), (String) retrievedDocument.getProperty("title"), (String) retrievedDocument.getProperty("description"))); } } } }); } } }); } catch (Exception e) { e.printStackTrace(); } fxListView.setCellFactory(new Callback<ListView<Todo>, ListCell<Todo>>() { @Override public ListCell<Todo> call(ListView<Todo> p) { ListCell<Todo> cell = new ListCell<Todo>() { @Override protected void updateItem(Todo t, boolean bln) { super.updateItem(t, bln); if (t != null) { setText(t.getTitle()); } } }; return cell; } }); primary.showingProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { AppBar appBar = MobileApplication.getInstance().getAppBar(); appBar.setTitleText("Couchbase Todo - List"); appBar.getActionItems().add(MaterialDesignIcon.ADD.button(e -> MobileApplication.getInstance().switchView(gluon.SECONDARY_VIEW) )); } }); } private int indexOfByDocumentId(String needle, ObservableList<Todo> haystack) { int result = -1; for(int i = 0; i < haystack.size(); i++) { if(haystack.get(i).getDocumentId().equals(needle)) { result = i; break; } } return result; } }

In the above file we have the list view property bound to the actual list view in the XML. The code that really matters, however, is the code found in the initialize method. In it we do three core things.

fxListView.setCellFactory(new Callback<ListView<Todo>, ListCell<Todo>>() { @Override public ListCell<Todo> call(ListView<Todo> p) { ListCell<Todo> cell = new ListCell<Todo>() { @Override protected void updateItem(Todo t, boolean bln) { super.updateItem(t, bln); if (t != null) { setText(t.getTitle()); } } }; return cell; } });

In the above code we define how the data will appear in the list. By default it only accepts string data, so we override it to take the title from our Todo objects.

primary.showingProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { AppBar appBar = MobileApplication.getInstance().getAppBar(); appBar.setTitleText("Couchbase Todo - List"); appBar.getActionItems().add(MaterialDesignIcon.ADD.button(e -> MobileApplication.getInstance().switchView(gluon.SECONDARY_VIEW) )); } });

In the above listener we set the title of our navigation bar as well as the button. When the button is pressed, the view will change to our secondary view.

Finally it leaves us with running the initial data query and populating the list, as well as listening for new data as it comes in. Should changes come in, they will be iterated over and the indicators will be reviewed on every changed document. If there was a delete indictator, the data will be removed from the list view. If there was a change, the data from the list view will be removed, then replaced. Otherwise the data will only be added. Since the listener operates on a background thread, changes to the UI must be done within the Platform.runLater .

This brings us to the second and final view.

Creating a View for Saving Data

The second view will have a form and is responsible for user input to be added to the database and displayed in the previous view. The XML markup that powers this view will look like the following:

<?xml version="1.0" encoding="UTF-8"?> <?import com.gluonhq.charm.glisten.mvc.View?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.TextArea?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.VBox?> <View fx:id="secondary" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="350.0" stylesheets="@secondary.css" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.couchbaselabs.views.SecondaryPresenter"> <top> <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="10.0" BorderPane.alignment="CENTER"> <children> <TextField fx:id="fxTitle" promptText="Title" /> <TextArea fx:id="fxDescription" prefHeight="200.0" prefWidth="200.0" promptText="Description" /> </children> </VBox> </top> <padding> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> </padding> </View>

The XML above is found in the src/main/resources/com/couchbaselabs/views/secondary.fxml file and the view itself looks like the following:


Using Couchbase Lite in a Java Gluon Application

Notice that there are two TextField inputs. They will be important in the src/main/java/com/couchbaselabs/views/SecondaryPresenter.java file referenced in the XML. This file contains the following code:

package com.couchbaselabs.views; import com.couchbaselabs.CouchbaseSingleton; import com.couchbaselabs.Todo; import com.gluonhq.charm.glisten.animation.BounceInRightTransition; import com.gluonhq.charm.glisten.application.MobileApplication; import com.gluonhq.charm.glisten.control.AppBar; import com.gluonhq.charm.glisten.layout.layer.FloatingActionButton; import com.gluonhq.charm.glisten.mvc.View; import com.gluonhq.charm.glisten.visual.MaterialDesignIcon; import javafx.fxml.FXML; import javafx.scene.control.Alert; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; public class SecondaryPresenter { private CouchbaseSingleton couchbase; @FXML private View secondary; @FXML private TextField fxTitle; @FXML private TextArea fxDescription; public void initialize() { this.couchbase = CouchbaseSingleton.getInstance(); secondary.setShowTransitionFactory(BounceInRightTransition::new); secondary.getLayers().add(new FloatingActionButton(MaterialDesignIcon.SAVE.text, e -> save() )); secondary.showingProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { AppBar appBar = MobileApplication.getInstance().getAppBar(); appBar.setTitleText("Couchbase Todo - Create"); } }); } private void save() { if(!fxTitle.getText().equals("") && !fxDescription.getText().equals("")) { couchbase.save(new Todo(fxTitle.getText(), fxDescription.getText())); fxTitle.setText(""); fxDescription.setText(""); MobileApplication.getInstance().switchToPreviousView(); } else { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setTitle("Missing Information"); alert.setHeaderText(null); alert.setContentText("Both a title and description are required for this example."); alert.showAndWait(); } } }

The input fields are mapped to this controller, but what really matters here is the code for adding the floating action button and setting the navigation bar title.

secondary.getLayers().add(new FloatingActionButton(MaterialDesignIcon.SAVE.text, e -> save() )); secondary.showingProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { AppBar appBar = MobileApplication.getInstance().getAppBar(); appBar.setTitleText("Couchbase Todo - Create"); } });

When the floating action button is clicked, the save method is called. In the save method we check to make sure the input fields are not blank and if they aren't, save the data and navigate backwards in the stack to the previous view.

Syncing Data with Couchbase Sync Gateway

Up until now, every part of our Gluon application was built for offline local use. However, including synchronization support into the mix is not only useful, but incredibly easy.

At this point I'm going to assume you've downloaded and installed Couchbase Sync Gateway. Before we run it, we need to create a configuration file. Create a JSON file with the following:

{ "log":["CRUD+", "REST+", "Changes+", "Attach+"], "databases": { "fx-example": { "server":"walrus:", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } } } } }

The above configuration file is one of the most simplest you can make for Sync Gateway. You're creating a partition called fx-example within the in-memory database walrus and you're accepting all documents from everyone with no read or write permissions.

Running this configuration with Sync Gateway won't get us very far yet because we haven't activated sync support in our application. Open the project's src/main/java/com/couchbaselabs/gluon.java file and include the following:

package com.couchbaselabs; import com.couchbaselabs.views.PrimaryView; import com.couchbaselabs.views.SecondaryView; import com.gluonhq.charm.glisten.application.MobileApplication; import com.gluonhq.charm.glisten.visual.Swatch; import javafx.scene.Scene; public class gluon extends MobileApplication { public static final String PRIMARY_VIEW = HOME_VIEW; public static final String SECONDARY_VIEW = "Secondary View"; public CouchbaseSingleton couchbase; @Override public void init() { addViewFactory(PRIMARY_VIEW, () -> new PrimaryView(PRIMARY_VIEW).getView()); addViewFactory(SECONDARY_VIEW, () -> new SecondaryView(SECONDARY_VIEW).getView()); } @Override public void postInit(Scene scene) { Swatch.BLUE.assignTo(scene); scene.getStylesheets().add(gluon.class.getResource("style.css").toExternalForm()); try { this.couchbase = CouchbaseSingleton.getInstance(); this.couchbase.startReplication(new URL("http://localhost:4984/fx-example/"), true); } catch (Exception e) { e.printStackTrace(); } } }

Really we only care about the startReplication line in the postInit method. Once we call it, replication will happen in both directions, continuously.

Conclusion

You just saw how to create a Java desktop application with Gluon and Couchbase. Using Gradle you can build and run the application and with a few revisions it can be converted to Android as well.

The full source code to this project can be found on GitHub here .

本文数据库(综合)相关术语:系统安全软件

主题: JavaXMLGradleAndroidRESTSQLiOSGitGitHubRY
分页:12
转载请注明
本文标题:Using Couchbase Lite in a Java Gluon Application
本站链接:http://www.codesec.net/view/479685.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 数据库(综合) | 评论(0) | 阅读(56)