Adding Filters for Lists of Skills on the SUSI.AI Server

In this blog post, we will learn how to add filters to the API responsible for fetching the list of skills i.e. the endpoint – https://api.susi.ai/cms/getSkillList.json. The purpose of adding filters is to return a list of skills based on some parameters associated with the skill, that would be required to allow the user to get the desired response that s/he may be using to display it on the UI.

Overview of the API

  • API to fetch the list of skills –
    • URL –  https://api.susi.ai/cms/getSkillList.json
    • It takes 5 optional parameters –
      • model – It is the name of the model that user is requesting
      • group – It is the name of the group that user is requesting
      • language – It is the name of the language that user is requesting
      • skill – It is the name of the skill that user is requesting
      • applyFilter – It has true/false values, depending whether filtering is required
    • If the request URL contains the parameter applyFilter as true, in that case the other 2 compulsory parameters are –
      • filter_name – ascending/descending, depending upon the type of sorting the user wants
      • filter_type – lexicographical, rating, etc based on what basis the filtering is going to happen

So, we will now look into adding a new filter_type to the API.

Detailed explanation of the implementation

  • We can add filters based on the key values of the Metadata object of individual skills. The Metadata object for each skill is similar to the following object –

{
  "model": "general",
  "group": "Knowledge",
  "language": "en",
  "developer_privacy_policy": null,
  "descriptions": "A skill that returns the anagrams for a word",
  "image": "images/anagrams.jpg",
  "author": "vivek iyer",
  "author_url": "https://github.com/Remorax",
  "author_email": null,
  "skill_name": "Anagrams",
  "protected": false,
  "terms_of_use": null,
  "dynamic_content": true,
  "examples": ["Anagram for best"],
  "skill_rating": {
    "negative": "0",
    "positive": "0",
    "stars": {
      "one_star": 0,
      "four_star": 0,
      "five_star": 0,
      "total_star": 0,
      "three_star": 0,
      "avg_star": 0,
      "two_star": 0
    },
    "feedback_count": 0
  },
  "creationTime": "2017-12-17T14:32:15Z",
  "lastAccessTime": "2018-06-19T17:50:01Z",
  "lastModifiedTime": "2017-12-17T14:32:15Z"
}

 

  • We will now add provision for URL parameter, filter_type=feedback in the API, which will filter the results based on the feedback_count key, which tells the number of feedback/comments a skill has received.
  • In the serviceImpl method of the ListSkillService class, we can see a code snippet that handles the filtering part, It checks the filter_type parameter received in the URL on if-else block. The code snippet looks like this –

if (filter_type.equals("date")) {
 .
 .
} else if (filter_type.equals("lexicographical")) {
 .
 .
} else if (filter_type.equals("rating")) {
 .
 .
}

 

  • Similarly, we will need to add an else if condition with feedback_type=feedback and write the code block inside it. Here is the code for it, which is explained in detail.

.
.
else if (filter_type.equals("feedback")) {
  if (filter_name.equals("ascending")) {
    Collections.sort(jsonValues, new Comparator<JSONObject>() {

      @Override
      public int compare(JSONObject a, JSONObject b) {
      Integer valA;
      Integer valB;
      int result=0;

      try {
        valA = a.getJSONObject("skill_rating").getInt("feedback_count");
        valB = b.getJSONObject("skill_rating").getInt("feedback_count");
        result = Integer.compare(valA, valB);

      } catch (JSONException e) {
        e.printStackTrace();
      }
      return result;
      }
    });
  }
  else {

    Collections.sort(jsonValues, new Comparator<JSONObject>() {

      @Override
      public int compare(JSONObject a, JSONObject b) {
      Integer valA;
      Integer valB;
      int result=0;

      try {
        valA = a.getJSONObject("skill_rating").getInt("feedback_count");
        valB = b.getJSONObject("skill_rating").getInt("feedback_count");
        result = Integer.compare(valB, valA);
      } catch (JSONException e) {
        e.printStackTrace();
      }
      return result;
    }
  });
  }
}
.
.

Working of the above code snippet

  • The first if condition checks for the filter_type the user has requested and enters the condition if it is equal to feedback
  • The next if-else handles the case of ascending and descending and sorts the list of skills accordingly.
  • The variable jsonValues passed in the Collections.sort function contains a List of JSONObject. Here, each object stands for the metadata object for a skill.
  • Since, the sorting is not a simple linear sort, the sort function is overloaded with a comparator function that specifies the key, based on which the sorting would take place.
  • The feedback_count value is stored in valA and valB for the two variables that is considered at an instance while sorting. For any other key based filtering, we need to replace the feedback_count with the desired key name.
  • The compare() method of Integer class of java.lang package compares two integer values (x, y) given as a parameter and returns the value zero if (x==y), if (x < y) then it returns a value less than zero and if (x > y) then it returns a value greater than zero.
  • The value returned to the sort function determines the order of the sorted array.
  • For, ascending and descending, the parameters of the compare function is swapped, so that we can achieve the exact opposite results from one another.

This was the implementation for the filtering of Skill List based on a key value present in the Skill Metadata object. I hope, you found the blog helpful in making the understanding of the implementation better.

Resources

Continue Reading

Showing only Logged-in Accounts in the Sharing Page of Phimpme Android

In Phimpme Android application, users can edit their pictures and share them to a number of platforms ranging from social networking sites like Facebook, Twitter etc to cloud storage and image hosting sites like Box, Dropbox, Imgur etc.

Desired flow of the application

According to the flow of the application, the user has to add an account first i.e. log in to the particular account that needs to be connected to the application. After that when the user enters the share page for sharing the image, a button corresponding to the connected account is visible in that page which on clicking will share the image to that platform directly.

What was happening previously?

The list of accounts which is present in the account manager of Phimpme Android application is also getting displayed in the share image page. As the list is large, it is difficult for the user to find the connected account from the list. There is not even an indicator whether a particular account is connected or not. On clicking the button corresponding to the non-connected account, an error dialog instructing the user to log in from the account manager first, will get displayed.

How we solved it?

We first thought of just adding an indicator on the buttons in the accounts page to show whether it is connected or not. But this fix solves only a single issue. Find the connected account in that large list will be difficult for the user even then. So we decided to remove the whole list and show only the accounts which are connected previously in account manager. This cleans the flow of the accounts and share in  Phimpme Android application

When a user logins from the account manager, the credentials, tokens and other details corresponding to that accounts gets saved in database. We used realm database for saving the details in our application. As the details are present in this database, the list can be dynamically generated when the user opens share image page. We implemented a function in Utils class for getting the list of logged in accounts. Its implementation is shown below.

public static boolean checkAlreadyExist(AccountDatabase.AccountName s) {

   Realm realm = Realm.getDefaultInstance();

   RealmQuery<AccountDatabase> query = realm.where(AccountDatabase.class);

   query.equalTo("name", s.toString());

   RealmResults<AccountDatabase> result1 = query.findAll();

   return (result1.size() > 0);

}



public static ArrayList<AccountDatabase.AccountName> getLoggedInAccountsList(){

   ArrayList<AccountDatabase.AccountName> list = new ArrayList<>();

   for (AccountDatabase.AccountName account : AccountDatabase.AccountName.values()){

       if (checkAlreadyExist(account))

           list.add(account);

   }

   return list;

}

Additional changes

There are few accounts which don’t need authentication. Those accounts need their respective application to be installed in the user’s device. So for adding those accounts to the list, we added another function which checks whether a particular package is installed in user’s device or not. Using that it adds the account to the list. The implementation for checking whether a package is installed or not is shown below.

public static boolean isAppInstalled(String packageName, PackageManager pm) {

   boolean installed;

   try {

       pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);

       installed = true;

   } catch (PackageManager.NameNotFoundException e) {

       installed = false;

   }

   return installed;

}

                 

Resources:

Continue Reading
Close Menu