Skip to content
This repository has been archived by the owner on Aug 1, 2019. It is now read-only.

Extended README by adding "how to integrate with a network service" instructions #114

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
136 changes: 136 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ![./NMessenger](https://github.com/eBay/NMessenger/blob/master/Assets/nmessenger.png)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/eBay/NMessenger/blob/master/LICENSE)
![ios](https://cocoapod-badges.herokuapp.com/v/NMessenger/badge.png)

NMessenger is a fast, lightweight messenger component built on [AsyncDisplaykit](https://github.com/facebook/AsyncDisplayKit) and written in [Swift](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/). Developers can inherently achieve 60FPS scrolling and smooth transitions with rich content components.

Expand Down Expand Up @@ -146,6 +147,141 @@ Both `textInputAreaView` and `textInputView` must be created in order for `NMess

In order to use your custom InputBar, override `func getInputBar()->InputBarView` in `NMessengerViewController`.


### Integration With Your Chat Service

The main purpose of chat messages is exchanging them over the network. The topics above only cover the message rendering aspect. However, it might be unclear how to acthaully push your messages to the network or how to render the received ones. Let's dive in...

Messages management the following two sub-tasks :
* sending realtime messages
* receivung realtime messages
* history integration (both sent and received messages)


Suppose, our chat service is limited to textin. The service, described below, can use any underlying protocol (such as XMPP, Telegram, etc.). `Disclaimer: your chat service might look differently`.

```swift
public protocol IChatMessage
{
var text: String { get }
var isIncoming: Bool { get }
}

public protocol IChatServiceDelegate
{
func chatServiceDidConnect(_ sender: IChatService)
func chatService(_ sender: IChatService, didSendMessage: IChatMessage)
func chatService(_ sender: IChatService, didReceiveMessage: IChatMessage)
func chatService(_ sender: IChatService, didReceiveHistory: [IChatMessage]])

// TODO: error handling methods are skipped for conciseness
}

public protocol IChatService
{
func connectAsync()
func disconnectAsync()

func sendTextAsync(_ message: String)
func loadHistoryAsync()
}
```

Sending a message involves two phases :
1. Find out that the user has typed something and tapped "send" button. In other words, you have to handle the user's input.
2. Pass the user's input to the networking service. This is achieved as a plain method call.

Intercepting the user's input might be not quite obvious since you do not need any delegate subscriptions. It is done by overriding the `NMessengerViewController.sendText()` instance method.


```swift
public class MyChatMessagingVC: NMessengerViewController, IChatServiceDelegate
{
override func sendText(_ text: String, isIncomingMessage:Bool) -> GeneralMessengerCell
{
let shouldSendToServer = !isIncomingMessage

if (shouldSendToServer)
{
// trigger network service
self.controller?.sendMessageAsync(text)
}

// otherwise - just render
// `super` is critical to avoid recursion and "just render"
return super.sendText(text, isIncomingMessage: isIncomingMessage)
}
}
```

I'd like to highlight the importance of using `super.sendText()` at the end of the function. If `self` is used in this case, you're going to
1. end up with infinite recursion
2. eventually crash due to "stack overflow" reason
3. flood the chat with repeated messages

You can use some other cell contruction code instead. See the ["Content Nodes and Custom Components"](https://github.com/eBay/NMessenger#content-nodes-and-custom-components) section for details.



We can break down the process of "receiving a message" in two phases as well. Here they are:
1. Subscribe to events from your service. Usually it's done by one of iOS mechanics such as delegates, `NSNotification` or closures.
2. Render the message using `NMessenger`

Let's see how it can be done :

```swift
public class MyChatMessagingVC: NMessengerViewController, IChatServiceDelegate
{
func chatService(_ sender: IChatService, didReceiveMessage message: IChatMessage)
{
// Using `super` to avoid side effects.
//
super.sendText(message.text, isIncomingMessage: true)
}
}
```

So, now your app should be ready to process realtime messaging.



When it comes to history handling, one of the approaches might be purging all the messages from `NMessenger` and re-adding them.

```swift
public class MyChatMessagingVC: NMessengerViewController, IChatServiceDelegate
{
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)

// supposing `self._chatService` has been the setup in `viewDidLoad`
// or injected during the screen transition
//
self._chatService.loadHistoryAsync()
}


func chatService(_ sender: IChatService, didReceiveHistory: [IChatMessage]])
{
super.clearALLMessages()

messageList.forEach
{
// using `super`to avoid side effects
//
_ = super.sendText($0.text, isIncomingMessage: $0.isIncoming)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it took me a while to get around to explaining this. It's the perfect use case for head async prefetching.

To use this, you need to set doesBatchFetch: Bool = true on NMessenger. NMessengerViewController should implement the NMessengerDelegate. You will need to add the optionalfunc batchFetchContent() to the ViewController. This function will be called when you start to scroll up in the chat history. Ideally, the client should fetch history rather than the server pushing it to the client. The following code describes this functionality:

func batchFetchContent() { //called when you begin to scroll up and NMessenger decides that messages could be prefetched
  getMessages { (messages) -> Void in  //call your server to get messages
    endBatchFetchWithMessages(messages: messages) //adds messages to the beginning of the NMessenger
  }
}

Calling endBatchFetchWithMessagesclears the lock on the prefetch mechanism so that it can be called again when you continue to scroll up.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atainter , i am sorry but i am still unsure how to extend my PR properly. I would be grateful if you elaborated on adding the missing parts and posted the result to a readme (or a wiki page or some other documentation piece for this library).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some topics to cover:

  1. Add some details of the prefetching usage to the readme.
  2. How to open the chat with some history pre-populated. Meaning, what to do after an async service call from viewWillAppear Notifies the completion.
  3. How to handle the "some past offline messages have been pushed by the server" situation you've described in your comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as How to handle the "some past offline messages have been pushed by the server" situation you've described in your comment. goes, NMessenger is not really setup for that. The client is meant to query for past offline message. Currently, you cannot add messages out of order because of the way referencing was built. I would build your server to send history in order so that you can use the batchFetchContent() callback.

I created a docs-wip branch. Can you resubmit the PR to merge with that branch? I'll help edit the docs to further explain 1 and 2.

}
```

This approach might result in poor performance and some unwanted visual effects.
```
TODO: describe a better approach
```



### NMessenger

NMessenger can be added to any view.
Expand Down