SUSI Skills are rules that are defined in SUSI Skill Data repo which are basically the responses SUSI gives to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which further fetches response from SUSI Skill Data and gives the response to the app. Similarly, when we need to list all skills, an API call is made to server to list all skills. The server then checks the SUSI Skill Data repo for the skills and then return all the required information to the app. Then the app displays all the information about the skill to user. User then can view details of each skill and then interact on the chat interface to use that skill. This process is similar to what SUSI Skill CMS does. The CMS is a skill wiki like interface to view all skills and then edit them. Though the app can not be currently used to edit the skills but it can be used to view them and try them on the chat interface.
API Information
For listing SUSI Skill groups, we have to call on /cms/getGroups.json
This will give you all groups in SUSI model in which skills are present. Current response:
{ "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }}, "accepted": true, "groups": [ "Small Talk", "Entertainment", "Problem Solving", "Knowledge", "Assistants", "Shopping" ], "message": "Success: Fetched group list" }
So, the groups object gives all the groups in which SUSI Skills are located.
Next comes, fetching of skills. For that the endpoint is /cms/getGroups.json?group=GROUP_NAME
Since we want all skills to be fetched, we call this api for every group. So, for example we will be calling http://api.susi.ai/cms/getSkillList.json?group=Entertainment for getting all skills in group “Entertainment”. Similarly for other groups as well.
Sample response of skill:
{ "accepted": true, "model": "general", "group": "Shopping", "language": "en", "skills": {"amazon_shopping": { "image": "images/amazon_shopping.png", "author_url": "https://github.com/meriki", "examples": ["Buy a dress"], "developer_privacy_policy": null, "author": "Y S Ramya", "skill_name": "Shop At Amazon", "dynamic_content": true, "terms_of_use": null, "descriptions": "Searches items on Amazon.com for shopping", "skill_rating": null }}, "message": "Success: Fetched skill list", "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }} }
It gives all details about skills:
- image
- author_url
- examples
- developer_privacy_policy
- author
- skill_name
- dynamic_content
- terms_of_use
- descriptions
- skill_rating
Implementation in SUSI Android App
Skill Listing UI of Google Assistant
Skill Listing UI of SUSI SKill CMS
Skill Listing UI of SUSI Android App
The UI of skill listing in SUSI Android App is the mixture of UI of Skill listing in Google Assistant ap and SUSI Skill CMS. It displays skills in a beautiful manner with horizontal recyclerview nested in vertical recyclerview.
So, for implementing horizontal recyclerview inside vertical recyclerview, you need two viewholders and two adapters (one each for a recyclerview).
Let’s see the implementation.
1. First task is to fetch the information of groups in which skills are located. This line calls method in SkillListModel which then makes an API call to fetch groups.
skillListingModel.fetchGroups(this)
2. When the API call is succeeded, the below mentioned method is called which then calls a skillListingModel.fetchSkills(groups[0], this) which fetches the skills located in group[0] group.
override fun onGroupFetchSuccess(response: Response<ListGroupsResponse>) { if (response.isSuccessful && response.body() != null) { groupsCount = response.body().groups.size groups = response.body().groups skillListingModel.fetchSkills(groups[0], this) } else { skillListingView?.visibilityProgressBar(false) skillListingView?.displayErrorDialog() } }
3. When API call for fetching skills in group[0] succeeds, the count value is increased and then skills in group[1] are fetched and so on.
override fun onSkillFetchSuccess(response: Response<ListSkillsResponse>, group: String) { if (response.isSuccessful && response.body() != null) { skills.add(Pair(group, response.body().skillMap)) count++ if(count == groupsCount) { skillListingView?.visibilityProgressBar(false) skillListingView?.updateAdapter(skills) } else { skillListingModel.fetchSkills(groups[count], this) } } else { skillListingView?.visibilityProgressBar(false) skillListingView?.displayErrorDialog() } }
4. When skills in all groups are fetched, the data in adapter is updated using skillGroupAdapter.notifyDataSetChanged()
override fun updateAdapter(skills: ArrayList<Pair<String, Map<String, SkillData>>>) { this.skills.clear() this.skills.addAll(skills) skillGroupAdapter.notifyDataSetChanged() }
5. The data is set to the layout in two adapters made earlier. The following is the code to set the group name and adapter to horizontal recyclerview. This is the GroupAdapter to set data to row item in vertical recyclerview.
override fun onBindViewHolder(holder: GroupViewHolder?, position: Int) { if(skills[position].first != null) holder?.groupName?.text = skills[position].first holder?.skillList?.setHasFixedSize(true) val mLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) holder?.skillList?.layoutManager = mLayoutManager holder?.skillList?.adapter = SkillListAdapter(context, skills[position]) }
6. Similarly, the data of each individual element in the horizontal recyclerview is set in the skillAdapter. The data set are title, examples, description and image. We have used Picasso library to load images from the URL.
override fun onBindViewHolder(holder: SkillViewHolder?, position: Int) { val skillData = skillDetails.second.values.toTypedArray()[position] if(skillData.skillName == null || skillData.skillName.isEmpty()){ holder?.skillPreviewTitle?.text = context.getString(R.string.no_skill_name) } else { holder?.skillPreviewTitle?.text = skillData.skillName } if( skillData.descriptions == null || skillData.descriptions.isEmpty()){ holder?.skillPreviewDescription?.text = context.getString(R.string.no_skill_description) } else { holder?.skillPreviewDescription?.text = skillData.descriptions } if(skillData.examples == null || skillData.examples.isEmpty()) holder?.skillPreviewExample?.text = StringBuilder("\"").append("\"") else holder?.skillPreviewExample?.text = StringBuilder("\"").append(skillData.examples[0]).append("\"") if(skillData.image == null || skillData.image.isEmpty()){ holder?.previewImageView?.setImageResource(R.drawable.ic_susi) } else { Picasso.with(context.applicationContext).load(StringBuilder(imageLink) .append(skillDetails.first.replace(" ","%20")).append("/en/").append(skillData.image).toString()) .fit().centerCrop() .into(holder?.previewImageView) } }
Summary
So, this blog talked about how the Skill Listing feature in SUSI Android App is implemented. This included how a network call is made, logic for making different network calls, making a nested horizontal recyclerview inside vertical recyclerview, etc. So, If you are looking forward to contribute to SUSI Android App, this can help you a little. But if not so, this may also help you in understanding and how you can implement nested recyclerviews similar to Google Play Store.
References
- To know about servlets https://en.wikipedia.org/wiki/Java_servlet
- To see how to implement one https://www.javatpoint.com/servlet-tutorial
- To see how to make network calls in android using Retrofit https://guides.codepath.com/android/Consuming-APIs-with-Retrofit
- To see how to implement Horizontal recyclerView inside Vertical recyclerView http://android-pratap.blogspot.in/2015/12/horizontal-recyclerview-in-vertical.html
- To see how to implement custom RecyclerView Adapter https://www.survivingwithandroid.com/2016/09/android-recyclerview-tutorial.html