Showing skills based on different metrics in SUSI Android App using Nested RecyclerViews
SUSI.AI Android app had an existing skills listing page, which displayed skills under different categories. As a result, there were a number of API calls at almost the same time, which led to slowing down of the app. Thus, the UI of the Skill Listing page has been changed so as to reduce the number of API calls and also to make this page more useful to the user.
API Information
For getting a list of SUSI skills based on various metrics, the endpoint used is /cms/getSkillMetricsData.json
This will give you top ten skills for each metric. Some of the metrics include skill ratings, feedback count, etc. Sample response for top skills based on rating :
"rating": [ { "model": "general", "group": "Knowledge", "language": "en", "developer_privacy_policy": null, "descriptions": "A skill to tell atomic mass and elements of periodic table", "image": "images/atomic.png", "author": "Chetan Kaushik", "author_url": "https://github.com/dynamitechetan", "author_email": null, "skill_name": "Atomic", "protected": false, "reviewed": false, "editable": true, "staffPick": false, "terms_of_use": null, "dynamic_content": true, "examples": ["search for atomic mass of radium"], "skill_rating": { "bookmark_count": 0, "stars": { "one_star": 0, "four_star": 3, "five_star": 8, "total_star": 11, "three_star": 0, "avg_star": 4.73, "two_star": 0 }, "feedback_count": 3 }, "usage_count": 0, "skill_tag": "atomic", "supported_languages": [{ "name": "atomic", "language": "en" }], "creationTime": "2018-07-25T15:12:25Z", "lastAccessTime": "2018-07-30T18:50:41Z", "lastModifiedTime": "2018-07-25T15:12:25Z" }, . . ]
Note : The above response shows only one of the ten objects. There will be ten such skill metadata objects inside the “rating” array. It contains all the details about skills.
Implementation in SUSI.AI Android App
Skill Listing UI of SUSI SKill CMS
Skill Listing UI of SUSI Android App
The UI of skills listing in SUSI Android app displays skills for each metric in a horizontal recyclerview, nested in a vertical recyclerview. Thus, for implementing horizontal recyclerview inside vertical recyclerview, you need two viewholders and two adapters (one each for a recyclerview). Let us go through the implementation.
- Make a query object consisting of the model and language query parameters that shall be passed in the request to the server.
val queryObject = SkillMetricsDataQuery("general", PrefManager.getString(Constant.LANGUAGE,Constant.DEFAULT))
- Fetch the skills based on metrics, by calling fetch in SkillListModel which then makes an API call to fetch groups.
skillListingModel.fetchSkillsMetrics(queryObject, this)
- When the API call is successful, the below mentioned method is called which in turn parses the received response and updates the adapter to display the skills based on different metrics.
override fun onSkillMetricsFetchSuccess(response: Response<ListSkillMetricsResponse>) { skillListingView?.visibilityProgressBar(false) if (response.isSuccessful && response.body() != null) { Timber.d("METRICS FETCHED") metricsData = response.body().metrics if (metricsData != null) { metrics.metricsList.clear() metrics.metricsGroupTitles.clear() if (metricsData?.rating != null) { if (metricsData?.rating?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_rating)) metrics.metricsList.add(metricsData?.rating) skillListingView?.updateAdapter(metrics) } } if (metricsData?.usage != null) { if (metricsData?.usage?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_usage)) metrics.metricsList.add(metricsData?.usage) skillListingView?.updateAdapter(metrics) } } if (metricsData?.newest != null) { val size = metricsData?.newest?.size if (size is Int) { if (size > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_newest)) metrics.metricsList.add(metricsData?.newest) skillListingView?.updateAdapter(metrics) } } } if (metricsData?.latest != null) { if (metricsData?.latest?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_latest)) metrics.metricsList.add(metricsData?.latest) skillListingView?.updateAdapter(metrics) } } if (metricsData?.feedback != null) { if (metricsData?.feedback?.size as Int > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metric_feedback)) metrics.metricsList.add(metricsData?.feedback) skillListingView?.updateAdapter(metrics) } } if (metricsData?.topGames != null) { val size = metricsData?.feedback?.size if (size is Int) { if (size > 0) { metrics.metricsGroupTitles.add(utilModel.getString(R.string.metrics_top_games)) metrics.metricsList.add(metricsData?.topGames) skillListingView?.updateAdapter(metrics) } } } skillListingModel.fetchGroups(this) } } else { Timber.d("METRICS NOT FETCHED") skillListingView?.visibilityProgressBar(false) skillListingView?.displayError() } }
- When skills are fetched, the data in adapter is updated using skillMetricsAdapter.notifyDataSetChanged()
override fun updateAdapter(metrics: SkillsBasedOnMetrics) { swipe_refresh_layout.isRefreshing = false if (errorSkillFetch.visibility == View.VISIBLE) { errorSkillFetch.visibility = View.GONE } skillMetrics.visibility = View.VISIBLE this.metrics.metricsList.clear() this.metrics.metricsGroupTitles.clear() this.metrics.metricsList.addAll(metrics.metricsList) this.metrics.metricsGroupTitles.addAll(metrics.metricsGroupTitles) skillMetricsAdapter.notifyDataSetChanged() }
- The data is set to the layout in two adapters made earlier. The following is the code to set the title for the metric and adapter to horizontal recyclerview. This is the SkillMetricsAdapter to set data to show item in vertical recyclerview.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (metrics != null) { if (metrics.metricsList[position] != null) { holder.groupName?.text = metrics.metricsGroupTitles[position] } skillAdapterSnapHelper = StartSnapHelper() holder.skillList?.setHasFixedSize(true) val mLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) holder.skillList?.layoutManager = mLayoutManager holder.skillList?.adapter = SkillListAdapter(context, metrics.metricsList[position], skillCallback) holder.skillList?.onFlingListener = null skillAdapterSnapHelper.attachToRecyclerView(holder.skillList) } }
- Similarly, the data of each individual element in the horizontal recyclerview is set in the SkillListAdapter. The data set are title, examples, description and image. We have used Picasso library to load images from the URL.
Summary
This blog talked about how the new skills listing feature in SUSI.AI Android app has been implemented to display top ten skills for each metric. This included how a network call is made, logic for making different network calls, making a nested horizontal recyclerview inside vertical recyclerview, etc.
Resources
- Make network calls in android using Retrofit https://guides.codepath.com/android/Consuming-APIs-with-Retrofit
- Implement Horizontal recyclerView inside Vertical recyclerView http://android-pratap.blogspot.in/2015/12/horizontal-recyclerview-in-vertical.html
- Implement custom RecyclerView Adapter https://www.survivingwithandroid.com/2016/09/android-recyclerview-tutorial.html
You must be logged in to post a comment.