Integrating Firebase Cloud Functions In Badgeyay

Badgeyay is an open source project developed by FOSSASIA Community for generating badges for conferences and events. The Project is divided into two parts frontend, which is in ember, and backend, which is in flask. Backend uses firebase admin SDK (Python) and Frontend uses firebase javascript client with emberfire wrapper for ember. Whenever an user signs up on the website, database listener that is attached to to the Model gets triggered and uses flask-mail for sending welcome mail to the user and in case of email and password signup, verification mail as well.

Problem is sending mail using libraries is a synchronous process and takes a lot of processing on the server. We can use messaging queues like RabbitMQ and Redis but that will be burden as server cost will increase. The workaround is to remove the code from the server and create a firebase cloud function for the same task.

Firebase cloud functions lets you run backend code on the cloud and can be triggered with HTTP events or listen for the events on the cloud, like user registration.

Procedure

  1. Firebase uses our Gmail ID for login, so make sure to have a Gmail ID and on the first sight we will be greeted with Firebase console, where we can see our created or imported firebase apps.

  1. Create the app by clicking on the Add Project Icon and write the name of the application (e.g. Test Application) and select the region, in my case it is India. Firebase will automatically generated an application ID for the app. Click on Create Project to complete creation of project

  2. After Completion, click on the project to enter into the project. You will be greeted with an overview saying to integrate firebase with your project. We will click on the Add Firebase to web App and save the config as JSON in a file as clientKey.json for later use.

  1. Now we need to install the firebase tools on our local machine so for that execute
npm i -g firebase-tools

 

  1. Now login from the CLI so that firebase gets token for the Gmail ID of the user and can access the firebase account of that Gmail ID.
firebase login

 

  1. After giving permissions to the firebase CLI from your Gmail account in the new tab opened in browser, create a folder named cloud_functions in the project directory and in that execute
firebase init

 

  1. Select only functions from the list of options by pressing space.

  2. After this select the project from the list where you want to use the cloud function. You can skip the step if you later want to add the cloud function to project by selecting don’t setup a default project and can later be used by command
firebase use --add

  1. Choose the language of choice

  2. If you want, you can enforce eslint on the project and after this the cloud function is set up and the directory structure looks as follows.

  3. We will write our cloud function in index.js. So let’s take a look at index.js
const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });

 

As we can see there is a sample function already given, we don’t need that sample function so we will remove it and will write the logic for sending mail. Before that we need to acquire the key for service accounts so that admin functionality can be accessed in the cloud function. So for that go to project settings and then service accounts and click on Generate New Private Key  and save it as serviceKey.json

  1. Now the directory structure will look like this after adding the clientKey.json and serviceKey.json

  2. We will use node-mailer for sending mails in cloud functions and as there is a limitation on the gmail account to send only 500 mails in a day, we can use third party services like sendGrid and others for sending mails with firebase. Configure node-mailer for sending mails as
const nodemailer = require('nodemailer');

const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: gmailEmail,
pass: gmailPassword
}
});

 

Also set the environment variables for the cloud functions like email and password:

firebase functions:config:set gmail.email="Email ID" gmail.password="Password"

 

  1. Logic for sending Greeting Mail on user registration
exports.greetingMail = functions.auth.user().onCreate((user) => {
const email = user.email;
const displayName = user.displayName;

return sendGreetingMail(email, displayName);
});

function sendGreetingMail(email, displayName) {
const mailOptions = {
from: `${APP_NAME}<noreply@firebase.com>`,
to: email,
};

mailOptions.subject = `Welcome to Badgeyay`;
mailOptions.text = `Hey ${displayName || ''}! Welcome to Badgeyay. We welcome you onboard and pleased to offer you service.`;
return mailTransport.sendMail(mailOptions).then(() => {
return console.log('Welcome mail sent to: ', email)
}).catch((err) => {
console.error(err.message);
});
}

 

Function will get triggered on creation of user in firebase and calls the greeting mail function with parameters as the email id of the registered user and the Display name. Then a default template is used to send mail to the recipient and Logged on successful submission.

  1. Currently firebase admin sdk doesn’t support the functionality to send verification mail but the client SDK does. So the approach which is followed in badgeyay is that admin SDK will create a custom token and client sdk will use that custom token to sign in and them send verification mail to the user.
exports.sendVerificationMail = functions.auth.user().onCreate((user) => {
const uid = user.uid;
if (user.emailVerified) {
console.log('User has email already verified: ', user.email);
return 0;
} else {
return admin.auth().createCustomToken(uid)
.then((customToken) => {
return firebase.auth().signInWithCustomToken(customToken)
})
.then((curUser) => {
return firebase.auth().onAuthStateChanged((user_) => {
if (!user.emailVerified) {
user_.sendEmailVerification();
return console.log('Verification mail sent: ', user_.email);
} else {
return console.log('Email is already verified: ', user_.email);
}
})
})
.catch((err) => {
console.error(err.message);
})
}
});

 

  1. Now we need to deploy the functions to firebase.
firebase deploy --only functions

 

Link to the respective PR  : Link

 

Topics Involved

  • Firebase Admin SDK
  • Configuring Gmail for third party apps
  • Token Verification and verification mail by client SDK
  • Nodemailer and Express.js

 

Resources

  • Firebase Cloud functions – Link
  • Extending authentication with cloud function – Link
  • Custom Token Verification – Link
  • Nodemailer message configuration – Link
  • Issue discussion on sending verification mail with admin SDK – Link

Ember Data Integration In Badgeyay Frontend

Badgeyay is an open source utility to develop badges for events and tech conferences. Badgeyay project is divided into two components. Frontend part is designed with ember and backend part is designed with Flask and database as PostgreSQL and Firebase as PaaS.

After refactoring the backend API for generation of badges, now it is time to consume the API in frontend by ember, and the way to consume the api in ember front–end is with the use of in built ember-data library. Ember data behaves in a way similar to server side ORM’s (Object Relational Mappers). It is a very versatile library and can be equipped with variety of backend services. It can be used with REST as well as sockets and other transfer protocols for communication.

For better understanding the working of ember data, let’s see how to use the same to consume the File Upload endpoint in the backend.

Procedure

  1. Enabling CORS on server, to allow cross-domain requests to the API.
from flask_cors import CORS
CORS(app, resources={r"*": {"origins": "*"}})
  1. Creating Adapter for the model in frontend. In our case it is csv-file. In the adapter we need to specify the host and the path, because our backend api is not running on the same port.
import DS from 'ember-data';

const { RESTAdapter } = DS;

export default RESTAdapter.extend({
host : 'http://localhost:5000',
pathForType : () => {
return 'api/upload/file';
}
});
  1. After creating the adapter we need to create the record in the controller of the respective component. The record is like an object of a class, which when pushed to store will make a network request to backend (POST) and fetch the response from the backend. Backend response will provide the id to save in store
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';

export default Controller.extend({
routing : service('-routing'),
actions : {
mutateCSV(csvData) {
let csv_ = this.get('store').createRecord('csv-file', {
csvFile : csvData,
extension : 'csv'
});
csv_.save();
},

mutateText(txtData) {
console.log(txtData);
}
}
});

Model for the csv-file

import DS from 'ember-data';

const { Model, attr } = DS;

export default Model.extend({
csvFile : attr('string'),
extension : attr('string')
});
  1. Next is to create serializers for the model. Serializers gets triggered at two moments, first when the data is sent to the server and second when data is received from the server. Each time an independent function gets executed. As the naming conventions of the functions pretty much explains their role, but for the sake of clarification serialize function gets executed when we send request to the server and normalizeResponse gets executed when we are getting response from the server.
import DS from 'ember-data';

const { JSONAPISerializer } = DS;

export default JSONAPISerializer.extend({

serialize(snapshot, options) {
let json = this._super(...arguments);
json.csvFile = {
'csvFile' : json.data.attributes['csv-file'],
'extension' : json.data.attributes.extension
};

delete json.data;
return json;
},

normalizeResponse(store, primaryModelClass, payload, id, requestType) {
return payload;
}
});
  1. After receiving the response a promise is returned by the push method to save the record in the store and we can see the id is saved in the ember-data object.

Pull Request for the same is at this Link

Topics Involved

Working on the issue involve following topics:

  • Enabling CORS to accept cross-domain requests at server
  • Creating models in ember data
  • Passing action from controller to component
  • Modifying the Params and Response on the network sent by ember-data via serializers

 

Resources

  • Ember data repository – Link
  • Documentation for creating record in ember data – Link
  • API Doc for JSONAPIAdapter – Link
  • API Doc for JSONAPISerializer – Link
  • Property methods for serializer – serialize, normalizeResponse

Database Listener for User Centric Events

Badgeyay is an open-source utility developed by FOSSASIA to generate badges for conferences and events. The project is separated into two components to ease maintainability. First is the frontend part which is in ember and second part is backend which is in Flask. The choice of database to support backend is PostgreSQL.

Now comes the problem, whenever a user is registered in the database, he should receive  a verification mail, that he is successfully registered on the platform. For this case we have to listen to the database events on User model. This issue has greater extendibility than only sending greeting or verification mail to the user. We can extend this to trigger services that are dependent on user registration, like subscribing the user to some set of services based on the plan he opted while registration and many more.

These type of issues cannot be handled by normal relationship with tables and other entities, there has to be logic in place to support such functionalities. So the challenges for tackling the problem are as follows:

  • Listen to the insert_action in User model
  • Extracting the details necessary for the logic
  • Execute particular logic

Procedure

  1. Attaching insert_action listener to the User model. This function will get triggered whenever an entity is saved in the User model.

<!– HTML generated using hilite.me –>

@db.event.listens_for(User, "after_insert")
def logic(mapper, connection, target): {
......
}
  1. When the function gets triggered, extract the details of the saved user that is necessary for the logic. As currently we are sending greeting mail to the user,we only need the email of the user. Target is the actual saved user passed as argument to the listening function from the library.

<!– HTML generated using hilite.me –>

msg = {}
msg['subject'] = "Welcome to Badgeyay"
msg['receipent'] = target.email
msg['body'] = "It's good to have you onboard with Badgeyay. Welcome to " \
"FOSSASIA Family."
sendMail(msg)
  1. Now the details are passed to sendMail() function for sending mail which uses flask-mail library to send mail to the recipient.
    def sendMail(message):
    if message and message.receipent:
    try:
    msg = Message(
    subject=message.subject,
    sender=app.config['MAIL_USERNAME'], Response(200).generateMessage(
    recipients=[message.receipent],
    body=message.body)
    Mail(app).send(msg)
    except Exception as e:
    return jsonify(
    Response(500).exceptWithMessage(
    str(e),
    'Unable to send the mail'))
    return jsonify(
    Response(200).generateMessage(
    'Mail Sent'))
    else:
    return jsonify(
    Response(403).generateMessage(
    'No data received')) 'No data received'))
    
  2. This will send mail to the user who has been registered to the application.

Similarly we can use separate logics according to the need of the application.

 

The Pull Request for the above functionality is at this Link

Topics Involved

Working on the issue involve following topics:

  • Configuring mail service to allow insecure apps access.
  • Sending mail from the flask-mail to end user
  • Attaching listener to listen for database change
  • Extraction of data from saved object in database sqlalchemy.

Resources

  • Sending Mails Programmatically –  Link
  • Flask Mail Documentation – Link
  • Listening to database events – Link
  • Enabling access to GMAIL to send mails to recipient – Link

Variable Font Size Badgeyay

Badgeyay is a simple badge generator that aims for promoting an open-source tool for generation of badges in PDF format. The project has options to choose from predefined set of images or upload a background image. User can choose from set of fonts and color of the same. But now Badgeyay also has option to choose custom font-size in generation of badges.

To implement font size feature,  first, the component that is determining the font of the label has to be identified. The label that determines the text on the badge is the <text> label and within it, the label that determines the properties of the text is <tspan>. So mainly we need to alter the properties in the tspan.

The property that determines the font size for the badge is font-size and its default value is set to 31.25 px. If the property in the labels changed, then we can see the corresponding changes in the PDF generated from the svg.

Now the challenges were:

  • To Determine the font value from the frontend.
  • Using the same for the font-config.
  • Changing the built svg accordingly.

Procedure

  1. Firstly frontend component has to be changed to incorporate a slider to give input for the variable font size. So a range input is inserted with range from 15 px to 45 px and default as 30 px. The size_print label gets changed dynamically to show the value selected from the range slider.
<li>
<input type="radio" name="fontsize" id="font-size-picker"> Choose font size
</li>
<section id="font-size-input" style="display:none;">
<label for="inputFile" id="size_print"></label>
<div>
<input type="range" id="font-size" max=45 min=15 step=5 value=30  class="form-control" name="font_size">
</div>
</section>
  1. After adding the component, form script is changed to add toggle behaviour to the button. For adding the toggling behaviour in the component, checkbox is used and the value of the label is updated dynamically as the slider value is changed.
$("#size_print").text($("#font-size").val() + " px");

      $("#font-size-picker").click(function () {

          if ($(this).is(":checked")) {

              $("#font-size-input").css("display", "block");

          } else {

              $("#font-size-input").css("display", "none");

          }

      });

      $("#font-size").on('input', function () {

          $("#size_print").text($(this).val() + " px");

      });
  1. After completing the work on the frontend, it is necessary to modify the backend too. The method for choosing custom font has to be refactored. It now checks whether the custom font is set or font size variable is set, and creates a config file for fonts which after use gets deleted.
font_config = {}
   # custom font is specified
   if custom_font != '':
       font_config['font'] = custom_font
   if font_size != '':
       font_config['font_size'] = font_size
   if custom_font != '' or font_size != '':
       json_str = json.dumps(font_config)
       print(json_str)
       f = open(os.path.join(app.config['UPLOAD_FOLDER'], 'fonts.json'), "w+")
       f.write(json_str)
       f.close()
  1. The generator class is modified as well to accommodate the changes, by adding a new class attribute called font_size. We find the keys in the dict object loaded from the file and assign the same to class attribute.
if 'font_size' in self.DATA.keys():
               self.font_size = self.DATA['font_size']
  1. Make the necessary change in the svg, so that font size change can be represented in the generated PDF. Replace the old font size with the new font size specified.
if self.font_size:
           content = content.replace("font-size:31.25px",
                                     "font-size:" + str(self.font_size) + "px")
  1. After all the changes, badge generated will have a different font size.

The Pull request for the above change is at this Link

Topics Involved

Working on this Issue (Link) involve following topics:

  • SVG Label manipulation
  • Sending data from Ember frontend to Backend.
  • Javascript for the toggle radio button.

References

  • Extracting map information from the SVG (Link)
  • Python Documentation for class (Link)
  • About Github Pages- (Link)
  • Ajax Serialize method to serialize the form contents – (Link)

Badgeyay: Integrating EmberJS Frontend with Flask Backend

Badgeyay is a simple badge generator with a simple web UI that generates a printable badge in PDFs. The project had gone through different cycles starting from a Flask server to a CLI application then a python library and now API Interface for generation of badges.

According to latest changes in the project structure, now the frontend and backend are independent components developed in Ember JS and Flask respectively. Now there is a need to connect the frontend to the backend, which means the user should see the response on the same page without refresh, if the badge generated successfully. AJAX would fit right into the spot. Asynchronous Javascript and XML also known as AJAX, will enable us to perform asynchronous operation on the page without refreshing the page.

We can make an API call to the Server running in backend or deployed on heroku, but the server is not suitable for doing CORS(Cross-Origin Resource Sharing), ability to share the resources on server with the client having different domain names, but as the server and the frontend are not hosted on the same host  so there is a need to enable the server to accept CORS request calls.

Now the challenges were:

  • Enabling Flask Server to accept CORS requests.
  • AJAX query for sending request to the Flask server.

Procedure

  1. Giving the form an id and creating an AJAX request to the Flask server (may be localhost or deployed on heroku).
<form id=”form1″ action=”” method=”post” enctype=”multipart/form-data” onsubmit=”return validate()”>

 

When the generate button is clicked, an AJAX request is made to the server to generate badges and at the same time prevent the page from refreshing. In the AJAX request we set the CORS header to allow the domain.

 

<script type=”text/javascript”>
$(document).ready(function () {
$(‘#form1’).submit(function (event) {
event.preventDefault();
$.ajaxSetup({
headers: {“Access-Control-Allow-Origin”: “*”}
});
$.ajax({
url: “http://badgeyay-api.herokuapp.com/api/v1.0/generate_badges”,
data: $(this).serialize(),
type: ‘POST’,
success: function (data) {…},
error: function (error) {…}
})
});
})
</script>

 

  1. Import the library and enable the API endpoint to accept CORS requests.
from flask_cors import CORS
cors = CORS(app, resources={r”/api/*”: {“origins”: “*”}})

 

  1. Add Logic for appending the download link by extracting the download link from the response and replacing the static text in the template with the download link, also changing the download variable to the filename, by stripping the base url from the download link.
if (data[“response”][0][“type”] === “success”) {
$(‘#success’).css(‘visibility’, ‘visible’);
let link = data[“response”][0][“download_link”];
link = link.replace(“backend/app/”, “http://badgeyay-api.herokuapp.com/”);
$(‘#badge-link’).attr(“href”, link);
link = link.replace(“static/badges/”, “”);
$(‘#badge-link’).attr(“download”, link);
}

 

  1. Output the success on the page.
<div id=”success” style=”visibility: hidden;”>
<div class=”flash-success”>Your badges have been created successfully.</div>
<div class=”text-center”>
<a id=”badge-link” href=”http://badgeyay-api.herokuapp.com/static/badges/{{msg}}-badges.pdf”
class=”btn btn-success”
download=”{{msg}}-badges.pdf”>Download as
PDF</a>
</div>
</div>

 

  1. Frontend and Backend now are connected to each other.The Server now accepts CORS requests and response is generated after the user requests from Frontend.

 

The Pull Request with the above changes is on this Link

Topics Involved

Working on this issue (Link)  involves following topics :

  • Enabling Flask Server for CORS
  • Request Headers
  • AJAX request for CORS.

References

Badgeyay: Custom Fonts in generation of badges

Badgeyay is an open source project of FOSSASIA. The main idea for this project is to provide an open-source alternative for badge generation process for any event. It can generate badges according to a predefined config or we can also submit our own custom config for the generation of the badges. We can use custom background, text and other things. One thing that is not present is the choice for choosing a custom font for the badge. I have made a contribution for adding this functionality with selection of some common fonts in the code.

Procedure

  1. Add a Button in index.html for the choice of the font and also preview them at the same time. 
    <label>Choose your font</label>
    <ul style=“list-style-type:none”>
     <li>
        <input type=“radio” name=“fontsource” id=“custfont”> Use Custom font
                        </li>
                        <section id=“custom-font” style=“display: none;”>
        <label for=“inputFile”>Select from following fonts</label>
        <div class=“btn-group”>
           <button type=“button” class=“btn btn-default dropdown-toggle” data-toggle=“dropdown” aria-haspopup=“true” aria-expanded=“false”>
              <span class=“placeholder2”>Select a font</span>
              <span class=“glyphicon glyphicon-chevron-down”></span>
           </button>
           <ul class=“dropdown-menu”>
              {% for i in custom_fonts %}
              <li class=“font-options” style=“font-family:'{{i}}'” data-item=“{{i}}”>{{i}}</li>
              {% endfor %}
           </ul>
        </div>
     </section>
     <input type=“hidden” name=“custfont” value=“”>
    </ul>

     

     

  2. Add javascript for the toggle in the check button and CSS for the Font option button.
.$(“.font-options”).click(function () {
  var i = $(this).data(“item”);
  $(“.placeholder2”).text(i);
  $(“input[name=’custfont’]”).val(i);
});

 

.font-options {
border-bottom: 1px solid darkgray;
padding: 9px;
}

 

  1. Font list is passed in the index page.
CUSTOM_FONTS = [‘monospace’, ‘sans-serif’, ‘sans’, ‘Courier 10 Pitch’, ‘Source Code Pro’]

 

render_template(‘index.html’, default_background=default_background, custom_fonts=CUSTOM_FONTS)

 

  1. Config file for font has been created, so that it can be used by different files.
custom_font = request.form[‘custfont’]
# Custom font is selected for the text
if custom_font != :
  json_str = json.dumps({
      ‘font’: custom_font
  })
  f = open(os.path.join(app.config[‘UPLOAD_FOLDER’], ‘fonts.json’), “w+”)
  f.write(json_str)
  f.close()

 

  1. Font preference is taken from the file at the time of generation of the badge (once only for all the badges in a single run).
font_choice = None
if os.path.isfile(os.path.join(UPLOAD_FOLDER, ‘fonts.json’)):
  DATA = json.load(open(os.path.join(UPLOAD_FOLDER, “fonts.json”)))
  font_choice = DATA[‘font’]

 

  1. Changes in the SVG are made according to the preference for the PDF generation. If the user wants a custom font then it updates the svg using the config else not.
content = CONTENT
if font_choice:
  content = content.replace(“font-family:sans-serif”,
                            “font-family:” + font_choice)
  content = content.replace(“inkscape-font-specification:sans-serif”,
                            “inkscape-font-specification:” + font_choice)
  content = content.replace(“font-family:ubuntu”,
                            “font-family:” + font_choice)
  content = content.replace(“inkscape-font-specification:ubuntu”,
                            “inkscape-font-specification:” + font_choice)

 

  1. Finally the Updated SVG is used for Badge Generation with custom fonts embedded.

Resources

Resources utilised for adding this functionality

  • Fonts in SVG – Link
  • Embed fonts in Inkscape SVG – Link
  • Embed fonts in PDF and SVG – Link

 

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