Notification System in Duke-CSA App

OK, today is the day my JFLAP program ends, so I have a whole evening to write this big post about how I’m implementing notifications in Duke-CSA app.

First I’ll talk about the functionalities I achieved and then about how I implemented them.

Functionalities:

  1. notification initialization

Notifications can be triggered by two circumstances: 1. A user replies/answers questions by another user in QA, replies/comments Rendezvous by another user etc. 2. A new event is pushed by CSA to all users.

The former kind of notifications is created by client apps which call a function in the cloud code to push. The latter, however, is created by an event poster that I created using Parse JavaScript SDK and calls the same cloud function to push.

2. notification presentation

The following table shows how notifications are handled. Rows are the states of the app and columns are how users open the app.

Click on Notification Click on App icon
not running go to corresponding view show badges and red dots
background go to corresponding view go to corresponding view
foreground N/A show badges and red dots

Red colored entry is undesired behavior that I have not figured out how to fix.

3. notification indication

Whenever there is an unread reply/answer etc. The corresponding tab will have a badge showing how many unread notifications that view and its subviews have. Red dots are also shown on the TableViewCells. The badge number will decrement and the dot will disappear whenever the user clicks on unread posts.

 

Implementations:

  • push notifications

I’m using Parse Cloud Code to perform the pushing tasks to Apple Push Notifications (APN) and call this function from any client app. The cloud code looks like this:

Parse.Cloud.define("push", function (request, response) {
    var query = new Parse.Query(Parse.Installation);
    var targetUser;
    var toUser = request.params.toUser; // this is the id of target user
    if (request.user && request.user.id == toUser) {
        return;
    }
    if (toUser) {
        targetUser = new Parse.User();
        targetUser.id = toUser; 
        query.equalTo('user', targetUser);
    }

    Parse.Push.send({
        where: query,
        data: request.params.data
    }, {
        useMasterKey: true,
        success: function () {
            if (targetUser) {
                saveNotifDataForUser(targetUser, request, response);
            }
            else {
                saveNotifDataForAll(request, response);
            }
        },
        error: function (error) {
            response.error("Error! " + error.message);
        }
    });
});

Basically, whenever there is a “toUser” variable in the request sent to the cloud, I push the notification to the user using a query and save the notification to the database (more about this later). If there is nothing in “toUser”, it means the request wants to push to all users. The program will then save the notifications for all.

  • save notifications

Below’s what “saveNotifDataForUser” looks like. I’m gonna save the code for “saveNotifDataForAll” because it’s very similar.

function saveNotifDataForUser (targetUser, request, response) {
    var NotifData = Parse.Object.extend("NotifData");
    var qry = new Parse.Query(NotifData);
    var type = request.params.data.notifType;
    var instanceID = request.params.data.PFInstanceID;
    qry.equalTo('UserID', targetUser.id);
    qry.find().then( function (results) {
        var notifData;

        if (results.length == 0) {
            notifData = new NotifData();
            for (var i = 0; i < notifTypes.length; i++) {
                notifData.set(notifTypes[i], []);
            }
            notifData.set("UserID", targetUser.id);
        } else {
            notifData = results[0];
        }

        var notifOfType = notifData.get(type);
        if (!notifOfType.includes(instanceID)) {
            notifOfType.push(instanceID);
        }
        return notifData.save();
    }).then( function (notifData) {
        console.log("user notif data saved.");
        response.success("Successfully pushed notification to " + targetUser.id + " with type " + type + " and id " + instanceID);
    });
}

Here I’m creating a new instance of “NotifData” class and save relevant information in there. Let me explain a little bit about this class. I came up with the idea of storing notification data online when I was trying to find a way to show badges on tab bar items when users click on the app icon to open the app instead of the notification. If I store notifications to the online database and retrieve them every time app is not launched from a notification. I can correctly update the notification data.

Besides this online class, I’m also storing notifications locally in persistent storage because I don’t want to lose them when users close the app without reading new notifications. For this storage, I classified notifications in categories and saved the arrays of IDs of PFInstances. Variable List:

var events: [String] = []
var rendezvous: [String] = []
var questions: [String] = []
var answers: [String] = []
var ansQuestions: [String] = []
var newEvents: [String] = []

Whenever a new post/reply is read, I remove its ID from the array and save again.

  • retrieve notifications

I’m retrieving notifications in the method didFinishLaunchingWithOptions. If “launchOption” is not nil, then it means the app is launched by the user clicking on the notification. With this notification I am able to create the local storage notification class and go from there. However, when “launchOption” is nil, I have to retrieve from the database the notifications sent to the user when the app was not active. I wrote a cloud function for this:

Parse.Cloud.define("getNotifData", function (request, response) {
    var userID = request.params.userID;
    var NotifData = Parse.Object.extend("NotifData");
    var query = new Parse.Query(NotifData);
    query.equalTo("UserID", userID);
    query.find().then( function(results) {
        if (results.length == 0) {
            response.error("No notification data for this user found.");
            return;
        }
        var notifData = results[0];
        var result = [];
        for (var i = 0; i < notifTypes.length; i++) {
            var instances = notifData.get(notifTypes[i]);
            for (var j = 0; j < instances.length; j++) {
                var notification = {"notifType": notifTypes[i], "PFInstanceID": instances[j]};
                result.push(notification);
            }
        }
        response.success(result);
    });
});

So client apps will just call this function to retrieve the notification data for the current users. After each time of retrieving, local storage should take over so I’m calling another cloud function to wipe the notification data. The code is farely similar to “getNotifData”.

This function is VERY tricky to use. This function is called when:

  1. the app is in the foreground and receives a notification, called twice.
  2. the app is in the background and receives a notification, called once. Note that this function is called in background only when the “remote notification” background mode is enabled in the capabilities of the app.
  • Event Poster

This is a tool to push events to the app and also all users. A demo video is below:

Basically it’s a lot of reading docs and playing around with html forms.

 

Seems like only I understand what I wrote….

A little more notes about debugging/coding:

  1. I’m using Postman as a fantastic tool to send post requests to Parse server. I can also export the requests in a json file so my friend Jay can also test the app.
  2. I find that the Parse.Promise class is very useful for getting rid of block structures wrapping each other.
  3. Moving functions to cloud is nice. In this way even if things go wrong after the app ships I have a chance to fix them.
  4. Vim is the best editor.
  5. I’m very excited that back4app (Parse Server group) promised to release a command line tool for their servers. I won’t have to click on many buttons to deploy cloud code any more. They also said we would be able to run local Parse servers and debug locally.
  6. The Event Poster is not using any kind of authentication. Literally anyone can post to the database. I’m hoping no one would be boring enough to go there and post random things.

Class Database in Duke-CSA app

For the July 4th weekend I added the class database collected by CSA to the app. The database contains past students’ comments about classes at Duke. Since people in CSA wanted it in the app, I looked into this yesterday.

The ‘database’ is actually a Google form document. I downloaded as csv and then used an online converter to convert it to JSON, which can be read by iOS. To make the networking of the app consistent, I uploaded the JSON file to Parse instead of my own server. After getting the JSON asynchronously from the internet, I’m saving all of the ‘courses’ into the memory and load the UITableView. This doesn’t fill the memory because all information is just plain text.

I then classified the courses based on the first character of their class numbers and added an index list by the table view so users can go to courses with a certain starting letter by clicking on the index list. I also added a search controller with scope buttons, so users can search by class number, class name, professor of the class, or all of the above. Clicking on a search result or a normal course will bring the user to a detail view of the course, which presents all information we have about the course.

I really want to know how to let users ‘bounce scroll’ to the next class available. This will be very handy when they want to view the next or the earlier comment on the same class. Instead of going back to the table view, using only one gesture (scroll) will be a huge boost of user experience.

Progress Update on Duke-CSA iOS app

Since last time, there are several features that I added to the app:

  1. vote buttons highlight when clicked. They are configured in a way that shows the state of the user’s vote on a certain question/answer. For example, when the user already downvoted a question but then clicks on the upvote button, the downvote button will unhighlight and the vote is increased by 1
  2. in compose question/answer view, text views resize its content when the keyboard appears or disappears, so users can always see what they are typing.
  3. in the main view of the Q&A section, users can now choose to order the questions by vote or by post time. There’s also a nice slider underneath the buttons to show users’ current choice.

Progress on Duke-CSA-iOS

Today I’m gonna take some time to write down my progress for Duke-CSA iOS app development.

This new feature is temporarily named “Q&A”. Duke Chinese students, especially class of 2020 freshmen, can post their questions about Duke student lives, classes, and other stuff on here. Upperclassmen are then able to answer them on the app. Both questions and answers can be voted on, just like Stack Overflow. Users can also comment on answers.

Most of the code for data transfer between the online database and the app was copied from Jay’s earlier work on the app. I also refactored a little bit to make the code cleaner.

Below are some screenshots of the app for now.

Main View: all questions ordered

Detail of a question with all its answers

Detail of an answer with all its comments

compose a new question

The voting buttons are functional but cannot be highlighted for now. I already know the reason is with updating TableViewCell – the button will only update its image when the TableViewCell is reloaded. I will probably be able to fix this tomorrow.

The reply function is used in three places of the app – Event, Rendezvous, and QA, so I made a new file ReplyController and extended this classes in these individual controllers, which made the code much shorter and easier to debug. However, the code structure for Event is a little different so I’ll work on that later.

The other function that Jay and I would like to implement is showing the class database collected by CSA. A search feature would be awesome, too.

Marble Maze 3D iOS Game

This is another 3D iOS game I completed the other day. Similar to the brick breaking game, it is also using SceneKit as the tool. One thing different is that this game uses game.scn as the main scene and references other scenes as child nodes, such as the ball here. Altering the properties of the scenes referenced will alter their appearance in the main scene as well.

Physical properties are again set with bit mask. The ball is configured with properties such as diffuse, specular, and normal maps. Lights on the ball indicate life. When the light is out, the ball dies. Players have to tilt their phone to get to where pearls are at in order to replenish their lives.

The camera is set to slowly follow the motions of the ball. Its position is also set relatively to the ball so that it follows at all times.

I might add more features to this game when I have time. Just like in the game Ballance, maybe I could add some ramps and springs so players can truly have fun with this game! SceneKit is so fascinating.

3D iOS Brick Breaking Game

Today I made this brick breaking game on iOS with SceneKit following a tutorial. With SceneKit, an SCNView contains an SCNScene in which SCNNodes appear and animate. Nodes are added to SCNScene by adding it as a child node of rootNode. XCode also provides a very nice Scene Editor so 3D elements can be created there.

Physical collisions are represented using bitmask, just like SpriteKit. Delegates are also there to perform different actions at different stages of collisions. Contact nodes are passed in as parameters so developers can do whatever they want with nodes that are colliding.

In SCNScene, there are also very interesting features such as cameras. You can add multiple cameras and switch between them. You can also change camera positions based on how users interact with the game. Besides cameras, lights are there to make it more fun – professional photographers will find things familiar! In this game there are front light, back light and also a gradient light. These three lights are believed to provide the best user experience.

3D is so fun!

Progress of Wheeshare

Wheeshare has finally achieved all of its necessary functionalities. Before it could provide functions such as request, approve request and lend, but it was impossible for user to “step back” from the relationships with cancel request or re-lend item. Now users are able to cancel their requests and owners of items can re-lend their stuff once they have them back from the last borrowers. Besides this breaking relationship improvement, users can now view the status of their items in the main table view, such as “lended to Bill” or “requested by Jason”.

The logic of the relationship is pretty efficient. I used a little bit of binary spirit while designing it. Each item has three variables relevant to the borrow-lend relationship: giver, requester, and connected. Among these three, giver and requester are of type PFUser (user class written by Parse, which tragically is going to close in one year, fml), and connected is a Bool. Giver for an item is the owner and cannot be changed for the lifespan of the item. Whenever a user requests an item, the requester variable of the item will be given the value of this user. Connected means if the relationship is successfully built. If the owner presses “approve request”, connected will be changed to true.

Therefore, two free variables produce three possible outcomes:

  1. requester is not null and connected is false. The item is currently available and no one has requested.
  2. requester is someone and connected is false. The item is requested but not approved yet.
  3. requester is someone and connected is true. The item is in a borrow-lend relationship.

requester is null && connected is true cannot happen because that doesn’t make any sense.

As for deleting the items, owners are not allowed to delete their items on the platform if the items are requested or in a relationship. They are prompted to take care of the requests.

Things to do: improve UI HARD. If I knew I would be developing iOS apps, I would have gone to some art clubs back in high school. Switch to another online database or write one by myself with php and SQL. The latter would be challenging but also very rewarding.

The other day I discovered an app called PartiO, which aims at exactly the same thing as my app – let students share their stuff. The only difference is that they let people share with profits. I have already contacted them and wish to help them improve the product. Hopefully we will achieve something together.

OK these last two paragraphs just made me add “Babbles” to the category of this post.

Thoughts about building an SMS system

What a sunny day. Yesterday I decided to take one more class – Math221 to discover the possibility of math majoring. I don’t know if it’s possible for me to triple major in CS, ECE and math but we’ll see.

Recently I’ve been thinking how I can improve my Wheeshare app. I believe protecting user privacy should be of high priority, so the app could be better with a built-in SMS system like Taobao’s Aliwangwang or WeChat. However building such thing is no easy – from the data structure of chats to UI design to speed enhancement. No one wants a chat screen that lags. I have to think of an efficient architecture. If everything goes perfectly, I will upload the project to GitHub for others to use.

For the chat screen, I plan to use a UITableView to hold every chat message, with user thumbnail profile picture on each side. I will make a NIB file for the chat bubble and add a UILabel in it to display chat. One technical challenge would be how to update messages if the user received a new one. I will look into Parse API documentation to look for a notification API so that my app can observe the database on the user’s phone.

As for the general structure, I would like to create and store an object for each pair of users (if they started chatting, of course) and label it with two users’ IDs. When the app inits it tries to retrieve chats from online database with the current user ID. Inside each object I store an array of chat messages, each labeled with its sender.

Another technical challenge: how to cache all messages locally? For now, my tentative solution is to use CoreData so that I can directly push any updated chat object into the Core Data Stack. It sounds so simple right now but I am 100% sure it will give me a headache when I try to implement it.

One other problem to think about is whether to keep the connection between two users after they ended their relationship as borrower/lender. Again this is a privacy issue that I need to think about.

Other than this SMS system plan, I need to somehow restrict the app’s area. I probably need to get users’ locations and limit the access to Duke University for now to prevent possible malicious data attack. Thank you for watching, that’s it for today.

Grand Central Dispatch with Swift

When I’m building my LOL helper app, there’s one thing that I noticed: I had to deal with asynchronous networking requests properly so that the UI is only updated after multiple requests have been completed. I am using Alamofire to complete such requests, which does provide a callback function that is only called after getting a response. However, it is unable to do so with multiple requests. After searching online, I found the problem could be solved with Grand Central Dispatch in Swift. GCD Tutorial

The idea is that to put the requests into a “dispatch group” which can notify or be monitored. Tasks in the group are run on different threads. Before the start of a HTTP request, we need to enter the group, and after it finishes (in the callback function), we leave the group. Then we execute some methods only after the group is clean, which means no task is going on.

Here’s what we could do:

func downloadThings() {
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { // 1
    var downloadGroup = dispatch_group_create() // 2
 
    for address in someURLStringGroup {
      dispatch_group_enter(downloadGroup) // 3
      download(address) { _ in
        dispatch_group_leave(downloadGroup) // 4
      }
    }
 
    dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // 5
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) { 
        // some cool stuff here
    }
  }
}

In this example, we first get on the user initiated queue, which has a relatively high priority. In 2, before anything happens, we need to create a group to “store” our tasks. Then before each task we enter the group, and after they complete we leave. dispatch_group_wait waits for the group to be empty and continue. After all that we get back on the main queue and update UI and stuff.

One problem with this implementation is that it blocks the main queue by waiting on the user interactive queue. The solution is thinking from the other direction: instead of waiting for the group, how about letting the group notify us? Luckily, Apple thought of that:

func downloadThings() {
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { // 1
    var downloadGroup = dispatch_group_create() // 2
 
    for address in someURLStringGroup {
      dispatch_group_enter(downloadGroup) // 3
      download(address) { _ in
        dispatch_group_leave(downloadGroup) // 4
      }
    }
 
    dispatch_group_notify(downloadGroup, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) 
    { // 2
      // some cool stuff here
    }
  }

Here we let the group notify us when everything is done. The UI is responsive while the data is being loaded. Everyone is happy.

The app is now able to retrieve current game information of a player, including who she or he is playing with. It is also able to get those players’ information with requests. However, Riot prevents “too many” requests in a second, which now seems that 10 requests in one second is still too many!! I’m working on a way to set a delay between requests, but since they are operating on different threads it is kind of difficult.

Also, I’ve been reading chapters and chapters of algorithms lately for my class. I should update what I learn urgently, maybe sometime tonight.

 

League of Legends API

Today is the first day of class this semester. I still have forty minutes before the section that I TA for starts, so I’m writing about my new iOS project.

The other day I came across with League of Legends API. As a LOL summoner and an iOS developer, I felt obligated to do something with it. The app let users search players with their summoner names and view their stats. In the near future, it will also provide a function like LOL nexus, which shows the summoners a user is playing with before entering the game.

Intuitively, I used a TabViewController as the initial view controller for the app since I have several “categories” of functions. These categories include the following:

  1. basic data about LOL: maps, champions and spells etc.
  2. search for a player by summoner name
  3. view players the user is against

The API is very handy. It also let developers try them out on the webpage, but they could add a search function. I am using Alamofire as the networking tool. I followed the instructions written by Ray Wenderlich to set it up.

There are several things that I noticed when writing the code:

  1. summoner names can contain spaces, I escaped that with
    name.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!

    which translates spaces into “%20″s.

  2. a lot of things returned by the server are optional. I have to unwrap them as soon as possible, otherwise when they are cast into String, “optional” will be there and that is not good to hear.
  3. the status code returned has to be 200 for the app to continue.

Here’s the GitHub Project Page