Jasonette / Jasonette/documentation

Custom Events (push notification, websocket, geolocation, etc)

Open
#77 3 comments 1 reaction 0 assignees View on GitHub
Dominant language
No language data
Stars
120
Forks
72
PR merge metrics
No merged PRs in 30d

Description

Currently on Jasonette, actions can be triggered by:

1. User input (when you touch a button, the `action` attached to the same button is run)
2. Another action (via "trigger")
3. System events ($load, $show, $pull, $foreground)

This post is related to **3**.

# Preface: How Events Work Today

The only way to make use of system events is to define their handlers under `$jason.head.actions`. For example:

```
{
"$jason": {
"head": {
"actions": {
"$load": {
"type": "$network.request",
"options": {
"url": "https://..."
},
"success": {
"type": "$render"
}
}
}
}
}
}
```

Here we are listening for the `$load` system event and have attached an action handler, which makes a `$network.request` and `$render`s its content.

There are limited number of these built-in system events (`$load`, `$show`, `$foreground`, `$pull`) at the moment.

# Going Beyond Default System Events

Being able to listen to events is very useful, but what if we could go further and listen to other types of events?

We could listen to other background events that we aren't capturing at the moment, such as location (geofencing), time (local notifications), and remote events (remote push notifications), etc.

# Custom Events

Unlike events such as `$show` and `$foreground` which are automatically triggered by the system, when it comes to custom events, the system has no prior knowledge, and it may be wasteful to listen to all of those events even when not in use, so we need to manually register to listen to them. Normally a lifecycle for custom events looks like the following:

1. **Register:** We first need to register to listen to some event in the future
2. **Listen:** Then the app goes back to doing its thing, and the event listener just keeps listening for the event in the background.
3. **Trigger:** When some event occurs, the registered handler is triggered and we can call actions or do whatever. We may need to listen to more than one events to cover all aspects of a feature set. For example, push notification involves listening to "registered" event as well as actual notification events after the initial registration.
4. **Unregister:** Unregister the event when not needed anymore. (Not always necessary)

In fact we already have an implementation for this, which is the [push notification](https://github.com/Jasonette/JASONETTE-iOS/issues/7#issuecomment-258688036) module. This feature is only implemented on iOS at the moment and not even documented because I wanted to take some time to decide if this was the best way to handle this, but I think it's time that we establish an official way to implement these custom events and move forward, officially rolling out all the long-overdue features such as push notification.

# Push Notification Example

I'll use the push notification example to describe each step.

Basically We can package all of the four steps into a single `Jason*Action` class.

### 1. Register

`$notification.register`: This is not an event, but an **action**. Sends a token registration request to APNS server.

```
"actions": {
"$load": {
"type": "$notification.register"
}
}
```

We can implement this as a `register()` method under `JasonNotificationAction` class.

### 2. Listen (for register success event)

Then it waits for a response back from APNS.

### 3. Trigger (on register success)

The first thing that happens after the registration is APNS returns with a `"$notification.registered"` event. This is a one time event that only gets triggered right after **$notification.register action** to APNS returns success. It returns the `device token` assigned by APNS. We need to store this token somewhere so we can reuse it later (normally on a server).

```
"actions": {
"$notification.registered": {
"type": "$network.request",
"options": {
"url": "https://myserver.com/register_device.json",
"data": {
"device_token": "{{$jason.device_token}}"
}
}
},
...
}
```

### 4. Listen (for actual notifications)

Now everything is set to go. The app goes back to doing its thing, but this time it keeps listening to APNS for any incoming push notifications.

### 5. Trigger (on every new push notification message)

When someone sends a POST request to APNS pointing to this device's `device_token`, APNS will send a push notification to this device, and a "$notification.remote" event will be triggered on this device.

We can attach any handler action here to handle the notification, as seen below.

**(May want to change the event name to something that's more intuitive, like `$notification.onmessage` or `$notification.message`. Feedback appreciated)**

```
"$notification.remote": {
"type": "$util.banner",
"options": {
"title": "Message",
"description": "{{JSON.stringify($jason)}}"
}
}
```

### 6. Unregister

In case of push notification this is not really necessary so this part is ommitted. But I can imagine some action/events requiring this, such as location tracking.

---

# Example Scenario

Jasonette is a view-oriented framework--we write a JSON markup to describe a view, just like how web browsers work. This means **there currently isn't a way to describe something outside the context of a view**.

So far with push notifications this hasn't been a big problem because we can simply attach different event handlers for different views.

For example if we have a chat app, we may have

1. Chatroom view (chat UI)
2. Lobby view (no chat UI, just displays the chatrooms available)

---

Whenever there's an incoming push notification from APNS or GCM,

A. In the chatroom view we may want to:

1. Look at the notification to see if the message is directed at the current chatroom
2. If it is, make a refresh and call `$render`.
3. If it's not, it means the notification is sent to another chatroom, so the current chatroom content doesn't need to refresh. We just need a notification, so simply display a `$util.banner`

---

B. In the lobby view:

We probably just want to dispaly `$util.banner` all the time, since there is no chat going on in the lobby itself.

---

So far from experience, it looks like **attaching event handlers to the view (instead of globally)** seems to cover all use cases while keeping it consistent with the existing, view-oriented JASON syntax. (But please share feedback if there seems to be a hole)

---

# Implementation Spec

Bringing all this together here's a spec for implementing these custom events:

## A. 4 phases

There are four steps to dealing with custom events:

1. **Register:** Register to listen to some event in the future. This exists as an **action**.
2. **Listen:** The event listener now keeps listening for the event in the background. The event handler can be attached either on an app level (like `$notification.registered`), or on a view level (like `$load`).
3. **Trigger:** When the event occurs, the handler is triggered.
4. **Unregister:** Unregister the event when not needed anymore. (Not always necessary)

## B. Implement as a single Jason*Action class.

Sometimes this may not be 100% feasible but we should strive to put everything into a single class as much as possible in order to keep features modular.

This class would consist of:

1. Register action: (example: `"type": "$notification.register"`, which calls `JasonNotificationAction.register()`)
2. Event handlers that trigger `call`([iOS](https://github.com/Jasonette/JASONETTE-iOS/blob/develop/app/Jasonette/Jason.m#L142) / [Android](https://github.com/Jasonette/JASONETTE-Android/blob/develop/app/src/main/java/com/jasonette/seed/Core/JasonViewActivity.java#L847)). Then all that's left is to just define an event handler under `$jason.head.actions` that will handle these events.

---

# Problem

What I described above kind of works, with a caveat.

I talked about the `$notification.register` action as if it works without any problem, but there is a small issue I didn't mention--"registration" is not something we should be doing all the time.

The code above would register for notification every time this view is loaded. In some cases this may not be desirable because it's redundant.

Using a more relevant case to explain this, let's say we're using this method to implement `websocket`. We may want to start a session once when the app launches and keep that session around throughout the entire lifecycle of the app instead of reinitializing the session everytime a view is loaded.

# Solutions?

We need some way to deal with this problem. (The current iOS push notification implementation doesn't address this problem) Some ideas I have so far involve keeping track of state (registered vs. not registered), probably keep it as a member variable of say, `JasonWebsocketAction`.

### Solution 1. Action based

Maybe implement an action called `$notification.token` which returns as either `success` or `error` callback (if the app already has registered for push, the `$notification.register` action should have stored it somewhere and would return the token through `success` callback, as `$jason`)

```
{
"actions": {
"$load": {
"type": "$notification.token",
"success": {
"type": "$render"
},
"error": {
"type": "$websocket.connect",
"options": {
...
}
}
}
}
}
```

This seems clean because we make use of the `success` and `error` callbacks,

### Solution 2. Variable based

Introduce a new variable for each of these cases (for example `$notification.token`) and use template expression to selectively execute.

```
{
"actions": {
"$load": [{
"{{#if 'token' in $notification}}": {
"type": "$render"
}
}, {
"{{#else}}": {
"type": "$websocket.connect",
"options": {
...
}
}
}]
}
}
```

Implementation-wise this is more complex because now we have to touch variables and templates. Might be challenging to keep it modular.

### Solution 3?

Any ideas or suggestions?

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the referenced event-dispatch entry points in iOS Jason.m and Android JasonViewActivity.java, then review the four-phase custom-event spec and the push-notification examples. The issue is done only after the lifecycle, view/app scope, and registration-state behavior have a settled design and an agreed implementation or documentation plan.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, ios, json
Domain
mobile-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.