Checking and removing SUSI Username Mentions in SUSI Slackbot

The SUSI Slackbot is a custom integration bot in slack. It responds to the user’s queries in the slack channels. It makes use of Slack Real Time Messaging APIs. In the background, it takes result from SUSI Server and also have some more added features. When the user mentions susi bot in the slack channels (eg. @asksusi), we know that the message is intended for susi. So now we need to remove the susi mention part, extract the rest of the message and send it to susi server. This blog explains how the messages are received from slack real time messaging API and how to remove the SUSI username mention.

Storing the bot’s self id

Before we can detect the susi username mention, we need to actually know the self id of the bot, which is a unique id assigned by the slack to the bot. Later, we use this id to detect the mention of susi.

var Slack = require('@slack/client');
var RtmClient = Slack.RtmClient;
var RTM_EVENTS = Slack.RTM_EVENTS;

var appData={};
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (connectData) => {
    // Cache the data necessary for this app in memory
    appData.selfId = connectData.self.id;
});

Receiving message and processing it

We will be receiving message from RTM API. The code for receiving the message is:

var Slack = require('@slack/client');
var RtmClient = Slack.RtmClient;
var RTM_EVENTS = Slack.RTM_EVENTS;

rtm.on(RTM_EVENTS.MESSAGE, function(message) {
var channel = message.channel;
var text=message.text;
//send reply only when mentioned or in direct message
if(text && message.user!==appData.selfId && (text.indexOf(appData.selfId)!==-1 || channel.startsWith('D'))){
    var susiMention='<@'+appData.selfId+'>';
    text=text.replace(susiMention,'');


The function passed as a callback executes only when we receive a new message. This message could be either a DM (Direct Message) or in a channel. We need to reply when the message mentions susi username or it is a DM. In the above code, we are checking if the message received is not from the bot itself, and is either from a DM or mentions susi username.
Here is a breakdown of the message object we receive in the function:

{ type: 'message',
  channel: 'C9CLRN4M7',
  user: 'U9D7JU15Y',
  text: '<@U9H78R274> hello!',
  ts: '1526413229.000321'}

 

  • Type: This tells the type of the message.
  • Channel: It contains the channel id of the slack channel where the message has been posted.
  • User: It contains the user id of the author.
  • Text: It contains the full text of the message, including mentions. We need to extract the mention part (<@U9H78R274>) and remove it.
  • Ts: ts is the unique (per-channel) timestamp.

Note: In case of DMs, the channel id will start with a “D”. In case of common channels, the channel id will start with “C”. That is how we can differentiate between a direct message and a message in a channel.

Thus we first store the bot’s self id. Then, upon receiving a message we check if the message is not from the bot itself, is a Direct Message or if it is from a channel, its mentions susi. Then only the susi bot replies to the message.

Result:

Resources

 

Continue ReadingChecking and removing SUSI Username Mentions in SUSI Slackbot

Post feedback for SUSI Skills in SUSI iOS

SUSI iOS, web and Android clients allow the user to rate the SUSI Skills in a 5-star rating system. Users can write about how much particular skill is helpful for them or if improvements are needed. Users can rate skills from one to five star as well. Here we will see how to submit feedback for SUSI skills and how it is implemented on SUSI iOS.

How to submit feedback –

  1. Go to Skill Listing Screen < Skill Detail Screen
  2. Scroll to the feedback section
  3. Write feedback about SUSI skill
  4. Click on POST button to post the skill feedback

An anonymous user can not submit skill feedback. You must have to logged-in in order to post skill feedback. If you are not logged-in and click POST button to post skill feedback, an alert is presented with Login option, by clicking Login, the user is directed to Login screen where the user can log in and later can post skill feedback.

Implementation of posting skill feedback –

Google’s Material textfield is used for skill feedback text field. We have assigned TextField class from Material target to skill feedback text field to make it very interactive and give better user experience.

Skill feedback text field in the normal state –

Skill feedback text field in the active state –

When the user clicks POST after writing skill feedback, we check if the user is logged-in or not.

if let delegate = UIApplication.shared.delegate as? AppDelegate, let user = delegate.currentUser {
...
}

We have saved the logged-in user globally using AppDelegate shared method during login and using it here. The AppDelegate is sort of like the entry point for the application. It implements UIApplicationDelegate and contains methods that are called when application launches, when is going to the background (i.e. when the user hit the home key), when it’s opened back up, and more. The AppDelegate object is stored as a property on the UIApplication class and is accessible from anywhere in swift classes.

Case 1: If the user is not logged-in, we show a popup to the user with the login option

By clicking Login, the user is directed to Login screen where the user can log in and later can post skill feedback.

Case 2: If the user is already logged-in, we use the endpoint below for posting skill feedback –

http://api.susi.ai/cms/feedbackSkill.json

ModelWith following parameters –

  • Group
  • Skill
  • Feedback
  • Access token
Client.sharedInstance.postSkillFeedback(postFeedbackParam) { (feedback, success, responseMessage) in
DispatchQueue.main.async {
if success {
self.skillFeedbackTextField.text = ""
self.skillFeedbackTextField.resignFirstResponder()
self.view.makeToast(responseMessage)
} else {
self.view.makeToast(responseMessage)
}
}
}

In return response, we get feedback posted by the user –

{
feedback: "Helpful",
session:
{
...
},
accepted: true,
message: "Skill feedback updated"
}

 

Resources –

  1. Material Design Guidelines for iOS
  2. Apple’s documentation on UIApplicationDelegate API
  3. Apple’s documentation on UIApplication API
  4. ChrisRisner’s article on Singletons and AppDelegate
Continue ReadingPost feedback for SUSI Skills in SUSI iOS

Using a Git repo as a Storage & Managing skills through susi_skill_cms

In this post, I’ll be talking about SUSI’s skill management and the workflow of creating new skills

The SUSI skills are maintained in a separate github repository susi_skill_data which provides the features of version controlling and the ability to rollback to a previous version implemented in SUSI Server.

The workflow is as explained in the featured image of this blog, SUSI CMS provides the user with a GUI through which user can talk to the SUSI Server and using it’s api calls, it can manipulate the susi skills present/stored on the susi_skill_data repository.

When the user opts to create a new skill, a new createSkill component is loaded with an editor to define rules of the skill. Once the form is submitted, an AJAX POST request is made to the server which actually commits the skill data to the repository and thus it is visible in the CMS from that point on.

Grab the skill details within the editor and put them in a form which is to be sent via the POST request.

let form = new FormData();
form.append('model', 'general');
form.append('group', this.state.groupValue);
form.append('language', this.state.languageValue);
form.append('skill', this.state.expertValue.trim().replace(/\s/g,'_'));
form.append('image', this.state.file);
form.append('content', this.state.code);
form.append('image_name', this.state.imageUrl.replace('images/',''));
form.append('access_token', cookies.get('loggedIn'));


Configure POST request settings object

let settings = {
   'async': true,
   'crossDomain': true,
   'url': urls.API_URL + '/cms/createSkill.json',
   'method': 'POST',
   'processData': false,
   'contentType': false,
   'mimeType': 'multipart/form-data',
   'data': form
};


Make an AJAX request using the settings above to upload the skill to the server and send a notification when the request is successful.

$.ajax(settings)
   .done(function (response) {
   self.setState({
          loading:false
   });
notification.open({
    message: 'Accepted',
    description: 'Your Skill has been uploaded to the server',
    icon: <Icon type='check-circle' style={{ color: '#00C853' }}       />,
});


Parse the received response as JSON and if the accept key in the response is true, we push the new skill data to the history API and set relevant states.

let data = JSON.parse(response);
if(data.accepted===true){
  self.props.history.push({
	pathname: '/' + self.state.groupValue  +
  	'/' + self.state.expertValue.trim().replace(/\s/g,'_') +
  	'/' + self.state.languageValue,
	state: {
  	from_upload: true,
  	expertValue:  self.state.expertValue,
  	groupValue: self.state.groupValue ,
  	languageValue: self.state.languageValue,
}});


If the accepted key of the server response is not true, display a notification.

else{
	self.setState({
  		loading:false
	});
	notification.open({
	  	message: 'Error Processing your Request',
	  	description: String(data.message),
	  	icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
	});
}})


Handle cases when AJAX request fails and send a corresponding notification

.fail(function (jqXHR, textStatus) {
 ...
  notification.open({
    message: 'Error Processing your Request',
    description: String(textStatus),
    icon: <Icon type='close-circle' style={{ color: '#f44336' }} />,
  });
});


I hope after reading this post, the objectives of susi_skill_data are more clear and you understood how CMS handles the creation of skills.

Resources

1.AJAX Jquery – AJAX request using Jquery
2. React State – Read about React states and lifecycle hooks.

Continue ReadingUsing a Git repo as a Storage & Managing skills through susi_skill_cms

Chrome Custom Tabs Integration – SUSI.AI Android App

Earlier, we have seen the apps having external links that opens and navigates the user to the phone browser when clicked, then we came up with something called WebView for Android, but nowadays we have shifted to something called In-App browsers. The main drawback of the system/ phone browsers are they caused heavy transition. To overcome this drawback “Chrome Custom Tabs” were invented which allowed users to walk through the web content seamlessly.

SUSI.AI Android App earlier used the system browser to open any link present in the app.

This can be implemented easily by

Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent);

This lead to a huge transition between the context of the app and the web browser.

Then, to reduce all the clutter Chrome Custom tabs by Google was evolved which drastically increased the loading speed and the heavy context switch was also not taking place due to the integration and adaptability of custom tabs within the app.

Chrome custom tabs also are very secured like Chrome Browser and uses the same feature and give developers a more control on the custom actions, user interface within the app.

                                comparing the load time of the above mentioned techniques

Ref : Android Dev – Chrome Custom Tabs

Integration of Chrome Custom Tabs

  • Adding the dependency in build.gradle(app-level) in the project
dependencies {
    //Other dependencies 
    compile 'com.android.support:customtabs:23.4.0'
}
  • Now instantiating a CustomTabsIntent Builder

    String url = “https://www.fossasia.org” // can be any link
    
    CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); //custom tabs intent builder
    
    CustomTabsIntent customTabsIntent = builder.build();
  • We can also add animation or customize the color of the toolbar or add action buttons.

    builder.setColor(Color.RED) //for setting the color of the toolbar 
    builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left); //for start animation
    builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right); //for exit animation
  • Finally, we have have achieved everything with a little code. Final launch the web page

    Uri webpage = Uri.parse(url); //We have to pass an URI
    
    customTabsIntent.launchUrl(context, webpage); //launching through custom tabs

Benefits of Chrome Custom Tabs

  1. UI Customization are easily available and can be implemented with very few lines of code. 
  2. Faster page loading and in-app access of the external link 
  3. Animations for start/exit  
  4. Has security and uses the same permission model as in chrome browser.

Resources

  1. Chrome Custom Tabs:  https://developer.chrome.com/multidevice/android/customtabs
  2. Chrome Custom Tabs Github Repo: GitHub – GoogleChrome/custom-tabs-client: Chrome custom tabs
  3. Android Blog: Android Developers Blog: Chrome custom tabs smooth the transition
  4. Video: Chrome Custom Tabs: Displaying 3rd party content in your Android

 

Continue ReadingChrome Custom Tabs Integration – SUSI.AI Android App

Adding typing animation and messages in SUSI Web bot plugin

SUSI web plugin bot provides the features of SUSI as a chat window which users can plugin to their websites. They just need to copy the generated javascript code into their website and the SUSI bot plugin will be enabled. This blog explains about the process of creating messages from both the user side and the bot side, and adding typing animation in the SUSI web chat plugin. A live demo of the bot can be found at susi-chatbotplugin-demo.surge.sh.

Creating User’s message

The main javascript file of our concern is skills.susi.ai/public/susi-chatbot.js. When the user types their message and press enter, setUserResponse() function is executed:

This function adds a message box to the chat window. The message box contains the message of the user.
Result:

Adding typing animation

After the user types the message and the message box is displayed, setUserResponse() function is executed. This function sets up a message box from the bot’s side and fills it with a loading gif. The important thing to note is msgNumber variable. For each user message, this variable is incremented by one. So it keeps count of the total number of message from the user or the bot. Each message box from the bot is assigned a unique id: “susiMsg-<msgNumber>”. Thus, when the response from the SUSI server is received, the loading gif is replaced by the message from the server. The corresponding message box is identified by the above id.

This function adds a message box to the chat window containing the loading gif.
Result:

On receiving the response from the server, the following function is executed:

function setBotResponse(val,msgNumber) {
    val = val.replace(new RegExp('\r?\n','g'), '<br />');
    $("#susiMsg-"+msgNumber+" .susi-msg-content-div").text(val);
    scrollToBottomOfResults();
}

 

This function replaces the above loading gif with the server’s response.

Final result:

Resources

Continue ReadingAdding typing animation and messages in SUSI Web bot plugin

Deploying SUSI Zulip bot

Zulip is a popular Real time messaging system, which combines the immediacy of Slack with an email threading model. The SUSI Zulipbot is a custom chatbot for zulip platform which fetches the response from the SUSI Server and will have some additional zulip platform specific features too. Users can install the bot into their zulip workspaces and then interact with the bot. They can either talk to the bot in private message or talk in group channels. This blog walks through the process of deploying SUSI Zulip bot into your workspace.

Cloning python-zulip-api

Python-zulip-api is where all the bots being developed for the Zulip platform can be found. The SUSI bot can be found in zulip_bots/bots/susi. susi.py is the main file where the bot’s code resides. test_susi.py is the file where the test cases for the bot are written. To clone and run the bot locally, make sure you have python3, pip and virtualenv installed. Then follow the below steps:

  1. git clone https://github.com/zulip/python-zulip-api.git  – clone the python-zulip-api repository.
  2. cd python-zulip-api  – navigate into your cloned repository.
  3. python3 ./tools/provision  – install all requirements in a Python virtualenv.
  4. The output of provision  will end with a command of the form source …/activate; run that command to enter the new virtualenv.
  5. Finished. You should now see the name of your venv preceding your prompt, e.g. (zulip-api-py3-venv)

For more information about installing the repository, refer https://zulipchat.com/api/writing-bots

Testing the bot’s output locally

For quick testing of your bot’s output, zulip-terminal is a very useful tool. It provides you a testing environment inside your terminal. After installing the above requirements, run  zulip-terminal susi in your terminal to enable testing the bot’s output:

Enter your message: hi
Reply from the bot is printed between the dotted lines:
——-
Hello!
——-
Enter your message: tell me a joke
Reply from the bot is printed between the dotted lines:
——-
It is said that looking into Chuck Norris’ eyes will reveal your future. Unfortunately, everybody’s future is always the same: death by a roundhouse-kick to the face.
——-
Enter your message: who created you?
Reply from the bot is printed between the dotted lines:
——-
The FOSSASIA community created me
——-
Enter your message: ^C
Ok, if you’re happy with your terminal-based testing, try it out with a Zulip server.
You can refer to https://zulipchat.com/api/running-bots#running-a-bot.

Deploying the bot in a Zulip workspace

Now you can deploy the bot in your own workspace or even in chat.zulip.org workspace. Follow these steps:

  1. After logging to your workspace, go to Settings () -> Your bots -> Add a new bot. Select Generic bot for bot type, fill out the form and click on Create bot.
  2. A new bot user should appear in the Active bots panel.
  3. Download the bot’s zuliprc configuration file to your computer.
  4. Run  zulip-run-bot susi –config-file ~/zuliprc-my-bot (using the path to the zuliprc file from above step).
  5. Congrats! Your bot should be running. Talk to your bot with mentioning @susi in a group channel or directly in a private channel.

Resources

Continue ReadingDeploying SUSI Zulip bot

Creating Onboarding Screens for SUSI iOS

Onboarding screens are designed to introduce users to how the application works and what main functions it has, to help them understand how to use it. It can also be helpful for developers who intend to extend the current project.

When you enter in the SUSI iOS app for the first time, you see the onboarding screen displaying information about SUSI iOS features. SUSI iOS is using Material design so the UI of Onboarding screens are following the Material design.

There are four onboarding screens:

  1. Login (Showing the login features of SUSI iOS) – Login to the app using SUSI.AI account or else signup to create a new account or just skip login.
  2. Chat Interface (Showing the chat screen of SUSI iOS) – Interact with SUSI.AI asking queries. Use microphone button for voice interaction.
  3. SUSI Skill (Showing SUSI Skills features) – Browse and try your favorite SUSI.AI Skill.
  4. Chat Settings (SUSI iOS Chat Settings) – Personalize your chat settings for the better experience.

Onboarding Screens User Interface

 

There are three important components of every onboarding screen:

  1. Title – Title of the screen (Login, Chat Interface etc).
  2. Image – Showing the visual presentation of SUSI iOS features.
  3. Description – Small descriptions of features.

Onboarding screen user control:

  • Pagination – Give the ability to the user to go next and previous onboarding screen.
  • Swiping – Left and Right swipe are implemented to enable the user to go to next and previous onboarding screen.
  • Skip Button – Enable users to skip the onboarding instructions and go directly to the login screen.

Implementation of Onboarding Screens:

  • Initializing PaperOnboarding:
override func viewDidLoad() {
super.viewDidLoad()

UIApplication.shared.statusBarStyle = .lightContent
view.accessibilityIdentifier = "onboardingView"

setupPaperOnboardingView()
skipButton.isHidden = false
bottomLoginSkipButton.isHidden = true
view.bringSubview(toFront: skipButton)
view.bringSubview(toFront: bottomLoginSkipButton)
}

private func setupPaperOnboardingView() {
let onboarding = PaperOnboarding()
onboarding.delegate = self
onboarding.dataSource = self
onboarding.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(onboarding)

// Add constraints
for attribute: NSLayoutAttribute in [.left, .right, .top, .bottom] {
let constraint = NSLayoutConstraint(item: onboarding,
attribute: attribute,
relatedBy: .equal,
toItem: view,
attribute: attribute,
multiplier: 1,
constant: 0)
view.addConstraint(constraint)
}
}

 

  • Adding content using dataSource methods:

    let items = [
    OnboardingItemInfo(informationImage: Asset.login.image,
    title: ControllerConstants.Onboarding.login,
    description: ControllerConstants.Onboarding.loginDescription,
    pageIcon: Asset.pageIcon.image,
    color: UIColor.skillOnboardingColor(),
    titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.chat.image,
    title: ControllerConstants.Onboarding.chatInterface,
    description: ControllerConstants.Onboarding.chatInterfaceDescription,
    pageIcon: Asset.pageIcon.image,
    color: UIColor.chatOnboardingColor(),
    titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.skill.image,
    title: ControllerConstants.Onboarding.skillListing,
    description: ControllerConstants.Onboarding.skillListingDescription,
    pageIcon: Asset.pageIcon.image,
    color: UIColor.loginOnboardingColor(),
    titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.skillSettings.image,
    title: ControllerConstants.Onboarding.chatSettings,
    description: ControllerConstants.Onboarding.chatSettingsDescription,
    pageIcon: Asset.pageIcon.image,
    color: UIColor.iOSBlue(),
    titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont)]
    extension OnboardingViewController: PaperOnboardingDelegate, PaperOnboardingDataSource {
    func onboardingItemsCount() -> Int {
    return items.count
    }
    
    func onboardingItem(at index: Int) -> OnboardingItemInfo {
    return items[index]
    }
    }
    

     

  • Hiding/Showing Skip Buttons:
    func onboardingWillTransitonToIndex(_ index: Int) {
    skipButton.isHidden = index == 3 ? true : false
    bottomLoginSkipButton.isHidden = index == 3 ? false : true
    }
    

Resources:

Continue ReadingCreating Onboarding Screens for SUSI iOS

Comparison between SUSI AI with Mycroft AI and Amazon Alexa

Now is the era of Voice User Interface (VUI) devices and they play a very important role as personal assistants. Here we compare the SUSI AI, Mycroft AI and Amazon Alexa based on the number of skills, their availability, easiness to add and edit skills and the provision of the user to modify the skill and add more to it if needed, etc.

Issue: https://github.com/fossasia/labs.fossasia.org/issues/215

The Comparison:

  1. Starting with the number of skills, here Amazon Alexa supports way more number of skills as compared to both Mycroft AI and SUSI AI.
  2. Availability: Mycroft AI and SUSI AI are available everywhere and can set up anywhere regardless of the country whereas Alexa is available in U.S., U.K., Germany,  India but they are aggressively expanding.
  3. Adding and editing skills: Mycroft and SUSI are open source and their skills can be added and edited and viewed by the open source community. Issues can be made to enhance the functionality of the skills whereas Alexa skills are not open source and certification and publishing of the skill is done by the Amazon team. Mycroft and SUSI skills can be customized by the user but this fails with Alexa as users have to create that same skill from scratch if they have to customize them.
  4. Platforms supported: Mycroft, SUSI and Alexa all support Linux. Mycroft lacks support for Windows and Mac but supports Raspberry Pi and Android, Alexa provides support for Windows and Mac and Raspberry Pi. SUSI also provides support for Android and iOS and can be integrated with speakers, vehicles, Pi, etc.
  5. Dedicated devices: As of now SUSI AI lacks such device. Mycroft has Mark 1 and Alexa has Echo. These devices are portable and are good candidates for home automation.
  6. Languages used for skill development: Mycroft mostly uses python. Alexa uses python, NodeJS, C#, etc for development of applications. SUSI uses its own language but language like javascript can be included in it. It’s easier to specify patterns using wildcards and variables in SUSI.

Due to different languages used, Mycroft AI skills can’t be directly used in SUSI AI. We need to convert Mycroft skills to SUSI skills if Mycroft skills are to be used for SUSI.

Some suggestions for making a dedicated device for SUSI:

  1. We can use a Raspberry Pi, USB headphones and a microphone to make a basic platform.
  2. We can install Jasper to enable the voice input on the Pi. Jasper is a open source application that enables us to make voice controlled applications.
  3. We can use SUSI server to interact with the device and the home appliances like lights. SUSI server can process the states of the the appliance (lights in this case) and return it as JSON objects to Raspberry Pi and then it may change the state as per user input.

Make a simple Hello World skill with SUSI:

  1. Visit https://github.com/fossasia/susi_skill_cms/blob/master/docs/Skill_Tutorial.md for a basic introduction to SUSI skills syntax and how does it work.
  2. Go to http://dream.susi.ai .
  3. Enter the skill name, say “hello”.
  4. You will be greeted by a welcome message – “roses are red…..”. Delete it and replace it with the following snippet.
::name <Skill_name> #<— Enter skill name. for example hello

::author <author_name>

::author_url <author_url> #<— You can leave this empty as of now.

::description <description> #<— skill description

::dynamic_content No

::developer_privacy_policy <link> #<— you can leave this as of now.

::image <image_url> #<— You can leave this as of now.

::term_of_use <link>

#Intent. Comments are written with a #

hi|hello|what’s up #<— This is what the user says

Hi|I am good|Hello #<— This is what the skill answers

6. Now go to http://susi.ai/chat for the testing.

7. In the SUSI chat dialog box (present at the bottom of the page) enter dream <test application name> where “test application name” is the name you enter when you first visit http://dream.susi.ai. In this case “dream hello”.

8. You can input “what’s up” in the dialog box and it will give you the desired output which you mentioned in the application.

Conclusion:

SUSI has its own good points but it lacks in some department like the number and type of skills. Like Mycroft we can start making various skills and try to make a basic prototype of a dedicated SUSI personal assistant device.

Resources

  1. Jasper
  2. Skill addition to SUSI
  3. Mycroft hello world skill

 

Continue ReadingComparison between SUSI AI with Mycroft AI and Amazon Alexa

Link Preview Holder on SUSI.AI Android Chat

SUSI Android contains several view holders which binds a view based on its type, and one of them is LinkPreviewHolder. As the name suggests it is used for previewing links in the chat window. As soon as it receives an input as of link it inflates a link preview layout. The problem which exists was that whenever a user inputs a link as an input to app, it crashed. It crashed because it tries to inflate component that doesn’t exists in the view that is given to ViewHolder. So it gave a Null pointer Exception, due to which the app crashed. The work around for fixing this bug was that based on the type of user it will inflate the layout and its components. Let’s see how all functionalities were implemented in the LinkPreviewHolder class.

Components of LinkPreviewHolder

@BindView(R.id.text)
public TextView text;
@BindView(R.id.background_layout)
public LinearLayout backgroundLayout;
@BindView(R.id.link_preview_image)
public ImageView previewImageView;
@BindView(R.id.link_preview_title)
public TextView titleTextView;
@BindView(R.id.link_preview_description)
public TextView descriptionTextView;
@BindView(R.id.timestamp)
public TextView timestampTextView;
@BindView(R.id.preview_layout)
public LinearLayout previewLayout;
@Nullable @BindView(R.id.received_tick)
public ImageView receivedTick;
@Nullable
@BindView(R.id.thumbs_up)
protected ImageView thumbsUp;
@Nullable
@BindView(R.id.thumbs_down)
protected ImageView thumbsDown;

Currently in this it binds the view components with the associated id using declarator @BindView(id)

Instantiates the class with a constructor

public LinkPreviewViewHolder(View itemView , ClickListener listener) {
   super(itemView, listener);
   realm = Realm.getDefaultInstance();
   ButterKnife.bind(this,itemView);
}

Here it binds the current class with the view passed in the constructor using ButterKnife and initiates the ClickListener.

Now it is to set the components described above in the setView function:

Spanned answerText;
text.setLinksClickable(true);
text.setMovementMethod(LinkMovementMethod.getInstance());
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
answerText = Html.fromHtml(model.getContent(), Html.FROM_HTML_MODE_COMPACT);
} else {
answerText = Html.fromHtml(model.getContent());
}

Sets the textView inside the view with a clickable link. Version checking also has been put for checking the version of Android (Above Nougat or not) and implement the function accordingly.

This ViewHolder will inflate different components based on the thing that who has requested the output. If the query wants to inflate the LinkPreviewHolder them some extra set of components will get inflated which need not be inflated for the response apart from the basic layout.

if (viewType == USER_WITHLINK) {
   if (model.getIsDelivered())
       receivedTick.setImageResource(R.drawable.ic_check);
   else
       receivedTick.setImageResource(R.drawable.ic_clock);
}

In the above code  received tick image resource is set according to the attribute of message is delivered or not for the Query sent by the user. These components will only get initialised when the user has sent some links.

Now comes the configuration for the result obtained from the query.  Every skill has some rating associated to it. To mark the ratings there needs to be a counter set for rating the skills, positive or negative. This code should only execute for the response and not for the query part. This is the reason for crashing of the app because the logic tries to inflate the contents of the part of response but the view that is passed belongs to query. So it gives NullPointerException there, so there is a need to separate the logic of Response from the Query.

if (viewType != USER_WITHLINK) {
   if(model.getSkillLocation().isEmpty()){
       thumbsUp.setVisibility(View.GONE);
       thumbsDown.setVisibility(View.GONE);
   } else {
       thumbsUp.setVisibility(View.VISIBLE);
       thumbsDown.setVisibility(View.VISIBLE);
   }

   if(model.isPositiveRated()){
       thumbsUp.setImageResource(R.drawable.thumbs_up_solid);
   } else {
       thumbsUp.setImageResource(R.drawable.thumbs_up_outline);
   }

   if(model.isNegativeRated()){
       thumbsDown.setImageResource(R.drawable.thumbs_down_solid);
   } else {
       thumbsDown.setImageResource(R.drawable.thumbs_down_outline);
   }

   thumbsUp.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) { . . . }
   });



   thumbsDown.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) { . . . }
   });

}

As you can see in the above code  it inflates the rating components (thumbsUp and thumbsDown) for the view of the SUSI.AI response and set on the clickListeners for the rating buttons. Them in the below code it previews the link and commit the data using Realm in the database through WebLink class.

LinkPreviewCallback linkPreviewCallback = new LinkPreviewCallback() {
   @Override
   public void onPre() { . . . }

   @Override
   public void onPos(final SourceContent sourceContent, boolean b) { . . . }
}

This method calls the api and set the rating of that skill on the server. On successful result it made the thumb Icon change and alter the rating method and commit those changes in the databases using Realm.

private void rateSusiSkill(final String polarity, String locationUrl, final Context context) {..}

References

Continue ReadingLink Preview Holder on SUSI.AI Android Chat