Domisy Dev

Theodore Mavrakis - Developer and Musician

Author: flaminSaganaki

Porting a (relatively simple) BlackBerry Cascades app to Android with Qt – Part 1

I’ve been using this BlackBerry KeyOne android device since it was released in 2017. Before that I had tried the Priv (also android) for a few months but eventually went back to my older BB10 devices like the Classic and Passport. Now, it’s 2019. BlackBerry 10 is soon to be extinct, and I think it’s finally time that I try to port my Cascades apps to Android.

I start with the most simple BB10 app I made, Daily Wallpaper. This app automatically updates your phone wallpaper to Bing’s daily homepage image. Back when I first released it on BlackBerry World it was the only available app to provide this sort of functionality. Now, if you search the Google Play Store there are dozens of apps that do exactly this same thing.

So, I’m not doing the android ecosystem any favors by porting over a simple app that already exists in multitudes. However, if I can ever hope to port one of my more complex apps, for instance Reddit in Motion… then I have to start with something basic and see how it goes. Hopefully this series of posts will help any BB10 developers who are thinking about porting over their apps to Android, since I found out very quickly that there aren’t any online resources for this sort of thing. Going from Qt-based Cascades to Qt for Android is relatively easy, but there are plenty of differences between the platforms and here we will highlight the more difficult obstacles. Let’s begin.

Getting started

First thing is to download the Qt Creator IDE from their website. This is a big install (~35gb) since the Qt library is so huge.
Next we need to configure our environment for working with Android. Follow this guide from the Qt Docs about getting all the prerequisite tools installed and configured. The only hiccup I had during this part was with my existing Java install. I had to download a fresh copy of JDK v8 before things started working properly.

Making something functional

The goal for this first blog post is getting to a point where most things are simply functioning properly, then I will write a follow-up post where we polish the UI and round-out all the features. I think the easiest place to start working in the code is the UI with QML. Unfortunately, even with an extremely simple UI like my Daily Wallpaper app, we can’t just copy paste the qml code. The Cascades UI components we’re familiar with are all descendants of the Qt UI components so the properties and functions will be familiar, we just need to find each equivalent object. Below we can compare my original Cascades qml code (only the relevant components) with the new qml for Android

//Original Cascades QML file
import bb.cascades 1.0
import bb.system 1.2
import bb.platform 1.2
import org.domisy 1.0
import bb.cascades.pickers 1.0

Page {
    id: page
    titleBar: TitleBar {
        title: "The Bing Image of the Day"
    }
    
    actions: [
        ActionItem {
            title: "Set Wallpaper"
            imageSource: "asset:///images/setBackgroundIcon.png"
            ActionBar.placement: ActionBarPlacement.OnBar
            onTriggered: {
                app.setImageWallpaper(bingWebImage.url);
            }
        },
        ActionItem {
            title: "Refresh"
            imageSource: "asset:///images/reloadIcon.png"
            ActionBar.placement: ActionBarPlacement.InOverflow
            onTriggered: {
                shareAction.enabled = false;
                infoAction.enabled = false;
                app.initiateRequest();
            }
        },
        ActionItem {
            title: "Info"
            id: infoAction
            enabled: false
            imageSource: "asset:///images/aboutIcon.png"
            ActionBar.placement: ActionBarPlacement.InOverflow
            onTriggered: {
                app.showImageInfoToast();
            }
        }
    ]
    Container {
        layout: DockLayout {
        }
        ActivityIndicator {
            objectName: "indicator"
            id: indicator
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Center
            preferredWidth: 200
            preferredHeight: 200
        }
        WebImageView { // a custom UI component for loading images from URL
            objectName: "bingImageComponent"
            id: bingWebImage
            preferredHeight: maxHeight - 200
            preferredWidth: maxWidth
            scalingMethod: ScalingMethod.AspectFill
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
        }
    }
} 
//NEW qml file for Android
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2

ApplicationWindow {
    visible: true
    width: Screen.width
    height: Screen.height
    property alias imageDescription: infoDialog.text

    header: Label {
        text: qsTr("The Bing Image of the Day")
        font.pixelSize: Qt.application.font.pixelSize * 1.5
        padding: 10
    }

    Page1Form {
        id: page1
        objectName: "page1"
        property alias indicator: indicator
        property alias bingImageComponent: bingImageComponent
        width: parent.width
        height: parent.height
    
        BusyIndicator {
            id: indicator
            objectName: "indicator"
            anchors.centerIn: parent
            running: true
            z: 100
        }
        Image {
            id: bingImageComponent
            objectName: "bingImageComponent"
            fillMode: Image.PreserveAspectFit
            width: parent.width
            height: parent.height
        }
    }

    footer: ToolBar {
        id: tabBar

        ToolButton {
            text: qsTr("Refresh")
            icon.source: "images/reloadIcon.png"
            leftPadding: 10
            onClicked: {
                page1.indicator.running = true;
                app.initiateRequest();
            }
        }
        ToolButton {
            text: qsTr("Set Wallpaper")
            icon.source: "images/setBackgroundIcon.png"
            x: parent.width / 2
            onClicked: {
                page1.indicator.running = true;
                app.setImageWallpaper(page1.bingImageComponent.source);
            }
        }

        ToolButton {
            id: infoButton
            objectName: "infoButton"
            text: qsTr("Info")
            icon.source: "images/ic_info.png"
            x: 70
            onClicked: {
                infoDialog.open();
            }
        }
    }
    MessageDialog {
        id: infoDialog
        title: "Image Description"
        text: qsTr("No info!")
        standardButtons: StandardButton.OK
    }
}

Again, this new QML code doesn’t look great. We will need to do some extra work later to make everything look as pretty as it did with Cascades. But, it gives us just enough to work with the functionality of the app. I think that providing both versions of this code side by side is self-explanatory enough. You can see which components correspond to each other and the different names of properties and layout code.

On with the logic

A few quick things to add in our main.cpp file. Lines 10-13 give us access to our C++ functions in DailyWallpaper.cpp from the QML code. Also notice the small difference in the way the main.qml file is loaded, lines 6-9. That code was auto-generated when we created the new project, but then line 10 is useful for grabbing the root object to pass on as the parent QObject parameter of DailyWallpaper constructor. In Cascades land, that same parameter was of type bb::cascades::Application.

#include "dailywallpaper.h"
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    QObject *rootObject = engine.rootObjects().first();
    DailyWallpaper* dailyWallpaper = new DailyWallpaper(rootObject);
    QQmlContext* context = engine.rootContext();
    context->setContextProperty("app", dailyWallpaper);
    
    return app.exec();
}

From here it’s a matter of carefully copying each function from DailyWallpaper.cpp into the new project. Thankfully most of this C++ code transfers very easily, however you don’t want to throw it all in right away since each piece will probably give you some things to deal with before moving on. Here are the two versions of the DailyWallpaper constructor, for comparison.

//Cascades DailyWallpaper.cpp
DailyWallpaper::DailyWallpaper(bb::cascades::Application *app) :
        QObject(app)
{
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    qml->setContextProperty("app", this);
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    mFile = new QFile("data/bingWallpaper.jpg");
    bingImage = root->findChild<WebImageView*>("bingImageComponent");
    activity = root->findChild<ActivityIndicator*>("indicator");

    mNetworkAccessManager = new QNetworkAccessManager(this);
    bool result = connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), this,
            SLOT(requestFinished(QNetworkReply*)));
    Q_ASSERT(result);
    Q_UNUSED(result);

    initiateRequest();
    app->setScene(root);
}
//Android DailyWallpaper.cpp
DailyWallpaper::DailyWallpaper(QObject *parent) : QObject(parent)
{
    QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
    mFile = new QFile(path.append("/bingWallpaper.jpg"));

    rootObject = parent;
    indicatorObject = rootObject->findChild<QObject*>("page1")->findChild<QObject*>("indicator");
    imageObject = rootObject->findChild<QObject*>("bingImageComponent");

    mNetworkAccessManager = new QNetworkAccessManager(this);
    bool result = connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), this,
            SLOT(requestFinished(QNetworkReply*)));
    Q_ASSERT(result);
    Q_UNUSED(result);

    initiateRequest();
}

We see that Cascades was loading up the main.qml file here in the DailyWallpaper constructor instead of from main.cpp. Because of this we establish the root QObject in a different way, and then we use it to grab references of certain important components in our QML file

I won’t go through ALL of the code so deliberately. From here let’s pick out the random trouble spots I found. Luckily all the network code using QNetworkAccessManager works a charm. The first issue comes up with how I was handling the QNetworkReply JSON response.

//Cascades DailyWallpaper.cpp
const QByteArray response(reply->readAll());
bb::data::JsonDataAccess jda;
QVariantMap results = jda.loadFromBuffer(response).toMap();
QVariantMap children = results["images"].toList().at(0).toMap();
imageUrl = "http://www.bing.com" + children["url"].toString();
imageInfo = children["copyright"].toString();
//Android DailyWallpaper.cpp
const QByteArray response(reply->readAll());
QJsonDocument jsonDoc = QJsonDocument::fromJson(response);
QVariantMap results = jsonDoc.object().toVariantMap();
//rest is the same

There’s not much to say here since it’s just a small change of syntax. We swap in QJsonDocument instead of using BlackBerry’s JsonDataAccess, and then we get our JSON data into a regular QVariantMap. That’s it.

Using Qt Android Extras to run Java code

Now that we have the network code working and we have the data we need, it’s time to write the code which will set the device wallpaper. This was the first real struggle. We are reminded that Qt is not the real native platform for Android, and that there are some things that can only be accessed via Android’s Java API’s. Android’s WallpaperManager is one of these services that we do not have the ability to control directly from Qt.

Luckily for us, Qt allows us to write and execute native Java code so that we essentially have access to all native API’s. It can be a little tricky at first to get it running though. Here is the documentation. There’s a nice sample application about using notifications to help us get started, but there are some unique things about the WallpaperManager API which this sample does not demonstrate.

With Cascades it was a matter of a few lines of simple C++ code. We could execute this right after downloading the image to the device.

//Cascades C++
bb::platform::HomeScreen homeScreen;
bool result = homeScreen.setWallpaper(QUrl("data/bingWallpaper.jpg"));
if (result)
    //success
else 
    //failed

I will assume you read through the docs I linked above to have an understanding of Qt Android Extras. Now let’s just go through the process of accessing and using the WallpaperManager API for this scenario. We have the URL of the image we want to set as the wallpaper, so we use QNetworkAccessManager to download the image data and QFile to save the image to a local file on the device. Now we have a QString filePath to the location of the local file. I put this helper function updateAndroidWallpaper() into WallpaperControls.cpp where we call on the Java code with QAndroidJniObject.

//Android wallpapercontrols.cpp
#include "wallpapercontrols.h"
#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroid>

WallpaperControls::WallpaperControls(QObject *parent)
    : QObject(parent)
{}

void WallpaperControls::updateAndroidWallpaper(QString filePath) {
    QString message = "Wallpaper set successfully";

    QAndroidJniObject javaNotification = QAndroidJniObject::fromString(filePath);
    jboolean wasSuccessful = QAndroidJniObject::callStaticMethod<jboolean>("com/domisy/wallpaper/WallpaperControls",
                                       "setWallpaper",
                                       "(Ljava/lang/String;Landroid/content/Context;)Z",
                                       javaNotification.object<jstring>(), QtAndroid::androidContext().object());
    if (!wasSuccessful)
        QString message = "Something went wrong!";
}

Unlike the notifications example in the docs, you notice we are passing two parameters to our Java function. The first is the filePath to the image, and the second is the application Context. We need the context in order to obtain an instance of the WallpaperManager. And it’s important that we pass the Context as a parameter from our C++ side, because it isn’t accessible from the Java code. Context can only be obtained from the main application thread which, for us, is on our Qt side. Below is the Java class WallpaperControls which shows the function we’re calling from C++ called setWallpaper().

// Android WallpaperControls.java
package com.domisy.wallpaper;

import java.lang.Object;
import android.app.WallpaperManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.content.Context;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;

public class WallpaperControls extends org.qtproject.qt5.android.bindings.QtActivity {
    private static WallpaperControls m_instance;

    public WallpaperControls()
    {
        m_instance = this;
    }

    public static boolean setWallpaper(String s, Context c)
    {
         WallpaperManager wallpaperManager = WallpaperManager.getInstance(c);
         Bitmap b = null;
         try {
             FileInputStream is = new FileInputStream(new File(s));
             BufferedInputStream bis = new BufferedInputStream(is);
             b = BitmapFactory.decodeStream(bis);
         } catch (FileNotFoundException ex) {
             ex.printStackTrace();
         }
         try {
             wallpaperManager.setBitmap(b);
             return true;
         } catch (IOException ex) {
             ex.printStackTrace();
             return false;          
         }
     }
}

One of the most tricky things about working with Java code from Qt is keeping in mind which things can be accessed from each side, and then figuring out how to use both together at the same time. Now we want to display a simple Toast notification to tell the user that the wallpaper was set successfully. Once again, we need to use Java since Qt doesn’t have support for Toast notifications. Right away it seems like an obvious solution to generate the Toast at the end of our setWallpaper() Java function, after we’ve use WallpaperManager to set the image. We’re already in the Java code after all, we can kill two birds with one stone, right?

NOPE. Just like WallpaperManager needed to run from the application Context, the Android Toast notifications need to be generated on the UI thread. Try to instantiate a Toast in the Java code and you quickly find that there’s no access to the UI thread there, at least not in our WallpaperControls class. (It should be noted that you can indeed run Toasts from Java with Qt Android Extras, as demonstrated in this blog post. However, we would need to create another Java class just for that Toast code, and there’s a much more easy way to do it, using an example from this other blog post!). We use QAndroidJniObject again, except this time instead of calling a Java function, we create a Toast object all from the C++ side.

void WallpaperControls::updateAndroidWallpaper(QString image) {
//............
//after our previous use of QAndroidJniObject to call our Java function setWallpaper()
Duration duration = LONG;
QtAndroid::runOnAndroidThread([message, duration] {
        QAndroidJniObject javaString = QAndroidJniObject::fromString("Wallpaper set successfully!");
        QAndroidJniObject toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast", "makeText",
                                                                           "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
                                                                           QtAndroid::androidActivity().object(),
                                                                           javaString.object(),
                                                                           jint(duration));
        toast.callMethod<void>("show");
    });
}

enum Duration {
    SHORT = 0,
    LONG = 1
};

This code is a little convoluted, but if you examine it carefully you see that we are generating the Toast object from the C++ code, passing in the two parameters it requires which are the string message and the duration. Then once we have a handle on our Toast object, we call the show() method to make it display. The whole time we stay in C++ code but manage to manipulate objects that are only accessible from the Java side. Neat!

Conclusion

Okay, I think that is more than enough for one post. Now the app is functional. At this point the app can load the image to view it, we can set the device wallpaper, and we can see the description of the image. And we got a Toast notification to make us feel better! Next up is implementing the auto-update feature, where the app can automatically update the wallpaper from the background each day without any user interaction. That will require creating an Android Service which sounds like a blast and a half. And also polishing up the UI with some QML work!

BAR files now available for download

BlackBerry World is officially shutting down on December 31st, 2019. That’s only a few days away! It’s sad to see the platform lose more and more functionality. Personally I’ve moved on to the new BlackBerry Android devices, currently using a KeyOne. If you’re still on BB10, it will be a little more tedious to install apps from now on. Reddit in Motion, SoundNine and Daily Wallpaper no longer have their source for distribution.

Therefore I’m making the BAR files for all my apps available to download directly from my website. You can find them on the homepage domisydev.com. Or click on the relevant link below. Any further updates that I can manage to push out will also be distributed from this site. If you don’t know how to sideload apps you can check out this guide on CrackBerry. Happy new year!

Sideload tutorial on CrackBerry

Reddit in Motion v2.4.6 BAR download
SoundNine v1.6.1 BAR download
Daily Wallpaper 1.5.2 BAR download

Cinnamon Swirl Scones

When I was in high school I spent my summers in the northern part of Michigan. There was a coffee stand set up in the town plaza which also served some pastries, my favorite of which were the cinnamon scones. Along with my caffeinated beverage I ate one of those scones as a snack nearly every day. It was dense enough to fill you up, but still soft and flaky. And it had the perfect amount of sweetness, so that it wasn’t bland but also didn’t feel like eating a dessert. I have yet to find such a good cinnamon scone anywhere else I’ve gone, so I set out to make them myself. This recipe is my best attempt, after some trial and error, to recreate those perfect scones.

 

The key to making scones (much like with pie crust) is keeping the butter as cold as possible during the preparation of the dough. In order to help with this I usually put my butter in the freezer for a few minutes before I start.

 

Ingredients:

2 cups all-purpose flour
1 tablespoon baking powder
3/4 teaspoon salt
1/2 cup brown sugar
2 tablespoons cinnamon
6 tablespoons unsalted butter
2 large eggs
1/3 cup heavy cream
1 teaspoon vanilla extract

Process:

  1. Preheat your oven to 400 F / 200 C
  2. Cut the butter into small chunks, and then place in the freezer to keep it cold until we add it to the mix
  3. Combine the flour, baking powder and salt in a large bowl
  4. Mix in half of the brown sugar (1/4 cup) and half of the cinnamon (1 tbsp). Combine the other half of brown sugar and cinnamon together in a separate cup or other small bowl. We will use this later for the swirl!
  5. Whisk together the eggs, heavy cream and vanilla in a separate small bowl
  6. Now add the chopped butter to the large bowl. Using a pastry cutter (or your hands), mix the butter with the flour and break-up the butter so that it becomes crumbly. (remember to act quickly on this step, you don’t want to warm the butter too much with your hands)
  7. Make a well in the center of the bowl and pour in the egg mixture. Use a fork to roughly combine and then turn it out onto a lightly floured surface. Knead a few times so that it’s all mixed well.
  8. Pat the dough down so that it’s spread out a bit flat, then take that extra brown sugar and cinnamon we reserved earlier and spread it out on top. Fold the dough on itself a few times so that this sugar gets mixed around. Knead just until the dough comes together again.
  9. Once again, pat the dough down to a thick flat disc, about 8 inches in width. Use a knife to cut like a pie, so that you get 8 triangle slices. Spread them out on a baking sheet and place in the oven for roughly 14-16 minutes.

 

Using Segment Analytics in your Java Servlet

Lately I’ve been working on a project using Apache Tomcat to serve a REST API, which is used by our client iOS application to interact with our back-end. Now that the app is available publicly, we want to gather data about how people use our service in order to analyze trends and make improvements to our product.

After doing some research on available solutions, I decided to use a service called Segment, which acts as a central hub for collecting your data before it’s forwarded to other analytics tools such as Flurry, Mixpanel, Amplitude and more. I needed a service that…

  1. is fast and easy to implement
  2. is not disruptive to the performance of our REST API
  3. allows integration with hundreds of other analytics tools without changing a line of code

… and Segment fits the bill… almost! Segment has a native Java library which uses it’s own internal queue to batch messages and send them asynchronously to their servers. It’s true that their Java library is easy to use… once it’s installed. In my case, it took several hours of trudging through Java Exceptions and Tomcat errors before I finally got it running. This was mainly due to the apparent lack of online resources for using Segment on server-side Java. In this post I will attempt to walk through the installation process and hopefully spare you readers the incredible frustration I faced this past week.
And with that, let’s begin!

Sign up for a Segment Account

First you will want to sign up for a Segment account here. Afterwards you need to create a Workspace and then a Project. Once you create your Project, you will be given a “Write Key.” You will need this key for connecting the Java library to your Segment account, so keep it handy.

Download the Java Library

The Segment Java library documentation can be found here. If you are working with Maven, you can add the following dependency to your pom.xml file.

<dependency>
    <groupId>com.segment.analytics.java</groupId>
    <artifactId>analytics</artifactId>
    <version>2.0.0-RC2</version>
</dependency>

For runtime, there are several JAR files you will need to place in your servlet WEB-INF/lib directory. For convenience, I’ve made a zip folder with all of them which you can download here.

Integrate Segment in your Code

I wrote the following class SegmentHelper.java which I use to maintain a singleton instance of the library’s Analytics class. Because the Java library uses an internal queue and batches messages, I don’t want to create a new instance every time an HTTP request hits my server. Using this helper class, I can reference a single instance from all of my other classes.

package com.myapp.helper;

import com.segment.analytics.Analytics;
import com.segment.analytics.Callback;
import com.segment.analytics.Log;
import com.segment.analytics.messages.Message;
import com.segment.analytics.messages.TrackMessage;
import com.segment.analytics.messages.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class SegmentHelper implements Callback {
    private static final String writeKey = "my_write_key_string";
    private static volatile Segment segment = new Segment();
    private Analytics analytics;

    private SegmentHelper(){
        try{
            this.analytics = Analytics.builder(writeKey).callback(this).build();
        }catch(Exception e){
            System.out.println("exception while creating Analytics : " + e);
        }
    }

    public static SegmentHelper getInstance(){
        return segment;
    }
 
    public Analytics getAnalyticsClient(){
        return segment.analytics;
    }

    public void success(Message message) {
        System.out.println("Successfully uploaded " + message);
    }

    public void failure(Message message, Throwable throwable) {
        System.out.println("Could not upload " + message);
    }

    public void addUser(String user_id, Map<String, Object> properties) {
        try {
            this.analytics.enqueue(IdentifyMessage.builder().userId(user_id).traits(properties));
            trackUser(user_id, "Logged In", properties);
        } catch (Exception e) {
            System.out.println("Exception in addUser() - " + e);
        }
    }

    public void trackUser(String user_id, String description, Map<String, Object> properties) {
        try {
            this.analytics.enqueue(TrackMessage.builder(description).userId(user_id).properties(properties));
        } catch (Exception e) {
           System.out.println("Exception in trackUser() - " + e);
        }
    }

    public void groupUser(String group_id, String user_id) {
        try {
            this.analytics.enqueue(GroupMessage.builder(group_id).userId(user_id));
        } catch (Exception e) {
            System.out.println("Exception in groupUser() - " + e);
        }
    }

    public static void main(String[] args){

    }
} 

I have included implementations for the three most common messages: Identify, Track and Group. In order to use Segment.class, simply grab the instance of Analytics using getInstance() and then call a function and pass in the necessary values. For instance…

package com.myapp.app;

import com.myapp.helper.SegmentHelper;
//other imports

@WebServlet("/Register/*")
public class Register extends HttpServlet {
	
	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response)
				throws IOException, ServletException
	{
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
        JSONObject json = new JSONObject();
		try {
			String name = request.getParameter("name");
			if (name != null ) {
				String user_id = addNewUser(name, json);
				Map<String, Object> properties = new LinkedHashMap<>();
				properties.put("name", name);
				SegmentHelper segment = SegmentHelper.getInstance();
				segment.addUser(user_id, properties);
			} else {
				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
				json.put("Error", "One or more required parameters is missing.");
			}
		} catch (org.json.JSONException e) {
				System.out.println("E in Register - " + e);
		}
		out.print(json.toString());
		out.close();
	}
	
    private String addNewUser(String name, JSONObject json) {
        String user_id;
        //do something here, give user_id a value
        return user_id;
    }
}

You can also just grab the instance of Analytics itself by using Segment.getAnalyticsClient() and use it as necessary in another class.

Something’s Wrong…

Maven: “diamond operator is not supported in -source 1.5”

You may see this error when you try to package your class files with maven. The solution is to use the maven-compiler-plugin to specify the source and target JVM version to use. In this case I am using JVM 1.8. Place the following in your pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Java NullPointerException in SegmentHelper

Make sure that you never assign a null value in your properties Map. Although Map does support null values, sending a Map with null values to Segment’s Java library will result in a NullPointerException.

Integration and Conclusion

Now that you are sending your data to Segment, it’s dead simple to add Integrations. Login to your Segment account dashboard, navigate to your project, and then click on the Integrations tab on the left where you can browse through all the services that are offered. When you find one you’d like to enable, click on it to see what settings are needed. In most cases, all you need to do is sign up for that service on their website, retrieve your API key and place it in the settings in Segment. Just like that, all the analytics data you are sending to Segment will automatically be forwarded to the other services you have enabled.

In retrospect, it IS a simple installation. I only wish that Segment’s documentation was more detailed regarding the Java library’s dependencies. They also don’t provide any examples of the implementation of their library in different contexts. However, all things considered, Segment is a fantastic service for collecting analytics data and integrating with other tools.

If this helped you, or if you have any suggestions, please comment below!

© 2020 Domisy Dev

Theme by Anders NorenUp ↑