keenlabs / KeenClient-Java

Official Java client for the Keen IO API. Build analytics features directly into your Java apps.
https://keen.io/docs
MIT License
74 stars 43 forks source link

Inserting data to 1 event in 2 separate functions (which are called at different times)? #42

Closed AlexMarshall12 closed 8 years ago

AlexMarshall12 commented 9 years ago

I have 2 functions which create data that I want to send into my Keen Client.

public class CameraActivity extends Activity implements PictureCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    Map<String, Object> event = new HashMap<String, Object>();

    @Override
    public void onConnected(Bundle connectionHint) {
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                mGoogleApiClient);
        if (mLastLocation != null) {
            mLatitude = String.valueOf(mLastLocation.getLatitude());
            mLongitude = String.valueOf(mLastLocation.getLongitude());
            event.put("Latitude",mLastLocation);
            event.put("longitude",mLongitude);
            KeenClient.client().queueEvent("android-sample-button-clicks", event);                       
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (getIntent().getExtras() != null) {
            bundle = getIntent().getExtras().getBundle("bundle");
            if (bundle != null) {
                int size = bundle.getInt("size", 0);
                Log.d("MyLogs", "From Intent - " + bundle.getString("string"));
                array = new String[size];
                array = bundle.getStringArray("array");
                String hashtag = array[1];
                if (hashtag.indexOf("/") != -1) {
                    String[] parts = hashtag.split("/");
                    if (parts[1] != null) {
                        String part2 = parts[1];       //tracking_id

                        event.put("sticker_ID", part2);
                        event.put("device_ID",android_id);

                    }
                }
            }
        }

    @Override
    protected void onPause() {
        KeenClient.client().queueEvent("android-sample-button-clicks", event);                //this doesn't look right to me. 
        KeenClient.client().sendQueuedEventsAsync();
        if (mAdapter != null)
            mAdapter.disableForegroundDispatch(this);
        super.onPause();
    }
}

You can see that I declare the event in the Activity itself, not in any of the functions. I need to add 4 data points to it. 2 come in onCreate, and the other 2 in onConnected. Hopefully sendQueuedEventsAsync will send whatever it has when onPause is called. This means that I need to queue the event whether or not onConnected has been called yet. Thus the queue event on line 13 is not sufficient. Thus I tried to add another one on line 42 so that it just queue whatever it has so far before it sends it, but somehow I don't feel like this is the correct way to do this. Its important that this is the same event. I don't want to send 2 separate events. How can I do this?

AlexMarshall12 commented 9 years ago

It occurred to me that perhaps this would be a good time to use the setGlobalPropertiesEvaluator. Maybe If I set the location as a dynamic global property at creation time I could kill 2 birds with 1 stone. I could have all 4 properties appear as well as have them be useful for something like the Geo-Explorer?

If I'm trying to have my event look like this:

{
    "sticker_ID": "alkj3s",
    "keen": {
        "timestamp": "2015-02-13T17:45:48.020Z",
        "created_at": "2015-02-13T17:45:48.533Z",
        "id": "54de384c3bc6963b315ee0b9",
        "location": {
            "coordinates": [ -88.21337, 40.11041 ]
    },
    "device_ID": "d23432kljlskjd94"
}    

Would this work?

   public class CameraActivity extends Activity implements PictureCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    Map<String, Object> event = new HashMap<String, Object>();

    @Override
    public void onConnected(Bundle connectionHint) {
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                mGoogleApiClient);
        if (mLastLocation != null) {
            mLatitude = String.valueOf(mLastLocation.getLatitude());
            mLongitude = String.valueOf(mLastLocation.getLongitude());
            String[] myLocationString = new String[2];
            myLocationString[0]=mLatitude;
            myLocationString[1]=mLongitude;
            GlobalPropertiesEvaluator evaluator = new GlobalPropertiesEvaluator() {
                public Map<String, Object> getGlobalProperties(String eventCollection) {
                   Map<String, Object> map = new HashMap<String, Object>();
                   map.put("location", myLocationString);
                return map;
                }
            }
            KeenClient.client().setGlobalPropertiesEvaluator(evaluator);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (getIntent().getExtras() != null) {
            bundle = getIntent().getExtras().getBundle("bundle");
            if (bundle != null) {
                int size = bundle.getInt("size", 0);
                Log.d("MyLogs", "From Intent - " + bundle.getString("string"));
                array = new String[size];
                array = bundle.getStringArray("array");
                String hashtag = array[1];
                if (hashtag.indexOf("/") != -1) {
                    String[] parts = hashtag.split("/");
                    if (parts[1] != null) {
                        String part2 = parts[1];       //tracking_id

                        event.put("sticker_ID", part2);
                        event.put("device_ID",android_id);
                        KeenClient.client().queueEvent("android-sample-button-clicks", event);
                    }
                }
            }
        }

    @Override
    protected void onPause() {
        KeenClient.client().sendQueuedEventsAsync();
        if (mAdapter != null)
            mAdapter.disableForegroundDispatch(this);
        super.onPause();
    }
}

As you can see I am trying ti queue the event during onCreate with the first 2 data points. However, I would also like to include the location so that the event object looks like the example above. I put the location in the GlobalPropertiesEvaluator. I am not sure though, how I send this/set it so that it is sent along with the other two data points in sendQueuedEventsAsync. Also, is GlobalPropertiesEvaluator even the right thing to use for this desired result?

Geeber commented 9 years ago

Hi Alex, sorry for taking a few days to get back to you on this.

First of all, I would not recommend using the GlobalPropertiesEvaluator this way. It's intended to store properties that are, well, global - meaning that you want the exact same properties/value to be included with EVERY event sent by your app. As such you generally would only use it for static properties that are fixed for the lifetime of the app, such as device model, app version, or Android SDK version.

Second, you are correct that calling queueEvent twice will result in multiple events being sent, which is not what you want. It's also important to understand that calling queueEvent results in a snapshot being taken of the provided event Map object, and it is that snapshot that will eventually get sent during sendQueuedEventsAsync. In other words, changes made to the Map in between queueEvent and sendQueuedEventsAsync will not be reflected in the event that's sent to Keen.

On a similar note, you may want to be careful about using an instance-scoped variable to store the event Map. Doing so means you could potentially re-send old data, because you're not clearing the Map out in between each queueEvent call. It could be OK depending on your exact usage, but if you run into trouble it's something to be sure and validate.

OK, so all that said, let me see if I can suggest a solution that will give you the behavior that you want. From what I gather, your goal is to send a single event each time onPause is called with 3 properties (keen.location.coordinates, sticker_ID, device_ID). If the location hasn't been retrieved yet then you just want to send an event with the other two. Does that sound right?

Assuming I've got the gist, what I'd recommend is storing the values in instance variables and then created and sending the event during onPause. So roughly:

@Override
protected void onPause() {
    Map<String, Object> event = new HashMap<String, Object>();
    event.put("sticker_ID", stickerId);
    event.put("device_ID", deviceId);
    event.put("keen.location.coordinates", coordinates);
    KeenClient.client().queueEvent(event);
    KeenClient.client().sendQueuedEventsAsync();
    if (mAdapter != null)
        mAdapter.disableForegroundDispatch(this);
    super.onPause();
}

and then populate the stickerId/deviceId/coordinates variables at the appropriate points in the code.

Does that make sense?

AlexMarshall12 commented 9 years ago

Yes, it sounds like you grasped my intentions exactly. So with the code you provided, if the coordinates aren't populated by the time onPause is called, this will simply be a Null value?

Geeber commented 9 years ago

Exactly; if you'd rather just omit the properties entirely, then you could wrap that line in an if statement:

if (coordinates != null) {
    event.put("keen.location.coordinates", coordinates);
}
AlexMarshall12 commented 9 years ago

hmm ok that makes sense but for some reason now my event is not sending.

Its initialized in onCreate with:

if (!KeenClient.isInitialized()) {

            // Create a new instance of the client.
            KeenClient client = new AndroidKeenClientBuilder(this).build();

            // Get the project ID and write key from string resources, then create a project and set
            // it as the default for the client.
            String projectId = getString(R.string.keen_project_id);
            String writeKey = getString(R.string.keen_write_key);
            KeenProject project = new KeenProject(projectId, writeKey, null);
            client.setDefaultProject(project);

            // Initialize the KeenClient singleton with the created client.
            KeenClient.initialize(client);
        }

and then

protected void onPause() {
        Map<String, Object> event = new HashMap<String, Object>();
        event.put("sticker_ID", sticker_ID);
        event.put("test","test");                                                  \\ just to fill event with something that I know exists
        event.put("device_ID", device_ID);
        event.put("keen.location.coordinates", coordinates);
        KeenClient.client().queueEvent("android-sample-button-clicks", event);
        KeenClient.client().sendQueuedEventsAsync();
        if (mAdapter != null)
            mAdapter.disableForegroundDispatch(this);
        super.onPause();
    }

but it doesn't register in my collection on my account?

Am I missing something?

Geeber commented 9 years ago

Hmm, tough to say without more info. Can you please add these two lines to your onCreate (e.g. right before the KeenClient.initialize call):

client.setDebugMode(true);
KeenLogging.enableLogging();

The first line will cause the client to throw exceptions rather than silently swallowing them. The second line will add logging to LogCat. Then re-run and let me know what happens. I'm guessing that it's something obvious that we've missed and hopefully will make sense once we see the log/exception :)

Don't forget to remove those lines before shipping though. For more info on them see here: https://github.com/keenlabs/KeenClient-Java#logging

AlexMarshall12 commented 9 years ago

Ah ha. So I added those debugging lines and now it crashes in onPause.

java.lang.RuntimeException: Unable to pause activity {com.snappiesticker.frontcam/com.snappiesticker.frontcam.CameraActivity}: io.keen.client.java.exceptions.InvalidEventException: An event cannot contain a property with the period (.) character in it.^M

I don't think it likes ' event.put("keen.location.coordinates",Arrays.deepToString(coordinates));

what should I use instead of the periods in order to attach it to the keen.location.coordinates property?

Geeber commented 9 years ago

Oh, whoops, sorry - I gave you bad advice because I wasn't thinking. You can't put nested properties in the map using periods; you have to have nested maps. Also you have to specify keen properties in the keepProperties argument to queueEvent (see https://github.com/keenlabs/KeenClient-Java/blob/master/core/src/main/java/io/keen/client/java/KeenClient.java#L239). So it would be something like:

@Override
protected void onPause() {
    Map<String, Object> event = new HashMap<String, Object>();
    event.put("sticker_ID", stickerId);
    event.put("device_ID", deviceId);
    Map<String, Object> keenProperties = new HashMap<String, Object>();
    Map<String, Object> keenLocationProperties = new HashMap<String, Object>();
    keenLocationProperties.put("coordinates", coordinates);
    keenProperties.put("location", keenLocationProperties);
    KeenClient.client().queueEvent("android-sample-button-clicks", event, keenProperties);
    KeenClient.client().sendQueuedEventsAsync();
    if (mAdapter != null)
        mAdapter.disableForegroundDispatch(this);
    super.onPause();
}

You can see here for more help on quickly building nested maps: https://github.com/keenlabs/KeenClient-Java#building-event-maps

Let me know if that helps!

tbarn commented 8 years ago

@AlexMarshall12 Hey! I know it has been awhile. I was wondering if this helped solve your issue. I am working on cleaning up some of the issues and was wondering if it could be closed. Thanks!

AlexMarshall12 commented 8 years ago

Close it, thanks!

On Mon, Dec 21, 2015 at 9:20 AM, Taylor Barnett notifications@github.com wrote:

@AlexMarshall12 https://github.com/AlexMarshall12 Hey! I know it has been awhile. I was wondering if this helped solved your issue. I am working on cleaning up some of the issues and was wondering if it could be closed. Thanks!

— Reply to this email directly or view it on GitHub https://github.com/keenlabs/KeenClient-Java/issues/42#issuecomment-166364135 .