Displaying Skills Feedback on SUSI.AI Android App

SUSI.AI has a feedback system where the user can post feedback for a skill using Android, iOS, and web clients. In skill details screen, the feedback posted by different users is displayed. This blog shows how the feedback from different users can be displayed in the skill details screen under feedback section. Three of the items from the feedback list are displayed in the skill details screen. To see the entire list of feedback, the user can tap the ‘See All Reviews’ option at the bottom of the list. The API endpoint that has been used to get skill feedback from the server is https://api.susi.ai/cms/getSkillFeedback.json The following query params are attached to the above URL to get the specific feedback list : Model Group Language Skill Name The list received is an array of `Feedback` objects, which hold three values : Feedback String (feedback) - Feedback string posted by a user Email (email) - Email address of the user who posted the feedback Time Stamp - Time of posting feedback To display feedback, use the RecyclerView. There can be three possible cases: Case - 1: Size of the feedback list is greater than three In this case, set the size of the list to three explicitly in the FeedbackAdapter so that only three view holders are inflated. Inflate the fourth view holder with “See All Reviews” text view and make it clickable if the size of the received feedback list is greater than three. Also, when the user taps “See All Reviews”, launch an explicit intent to open the Feedback Activity. Set the AllReviewsAdapter for this activity. The size of the list will not be altered here because this activity must show all feedback. Case - 2: Size of the feedback list is less than or equal to three In this case simply display the feedback list in the SkillDetailsFragment and there is no need to launch any intent here. Also, “See All Reviews” will not be displayed here. Case - 3: Size of the feedback list is zero In this case simply display a message that says no feedback has been submitted yet.Here is an example of how a “See All Reviews” screen looks like : Implementation First of all, define an XML layout for a feedback item and then create a data class for storing the query params. data class FetchFeedbackQuery( val model: String, val group: String, val language: String, val skill: String ) Now, make the GET request using Retrofit from the model (M in MVP). override fun fetchFeedback(query: FetchFeedbackQuery, listener: ISkillDetailsModel.OnFetchFeedbackFinishedListener) { fetchFeedbackResponseCall = ClientBuilder.fetchFeedbackCall(query) fetchFeedbackResponseCall.enqueue(object : Callback<GetSkillFeedbackResponse> { override fun onResponse(call: Call<GetSkillFeedbackResponse>, response: Response<GetSkillFeedbackResponse>) { listener.onFetchFeedbackModelSuccess(response) } override fun onFailure(call: Call<GetSkillFeedbackResponse>, t: Throwable) { Timber.e(t) listener.onFetchFeedbackError(t) } }) } override fun cancelFetchFeedback() { fetchFeedbackResponseCall.cancel() } The feedback list received in the JSON response can now be used to display the user reviews with the help of custom adapters, keeping in mind the three cases already discussed above. Resources Check out the GetSkillFeedbackService.java to know more about…

Continue ReadingDisplaying Skills Feedback on SUSI.AI Android App

Add Unit Test in SUSI.AI Android App

Unit testing is an integral part of software development. Hence, this blog focuses on adding unit tests to SUSI.AI Android app. To keep things simple, take a very basic example of anonymize feedback section. In this section the email of the user is truncated after ‘@’ symbol in order to maintain the anonymity of the user. Here is the function that takes ‘email’ as a parameter and returns the truncated email that had to be displayed in the feedback section : fun truncateEmailAtEnd(email: String?): String? { if (!email.isNullOrEmpty()) { val truncateAt = email?.indexOf('@') if (truncateAt is Int && truncateAt != -1) { return email.substring(0, truncateAt.plus(1)) + " ..." } } return null }   The unit test has to be written for the above function. Step - 1 : Add the following dependencies to your build.gradle file. //unit test testImplementation "junit:junit:4.12" testImplementation "org.mockito:mockito-core:1.10.19"   Step - 2 : Add a file in the correct package (same as the file to be tested) in the test package. The function above is present in the Utils.kt file. Thus create a file, called UtilsTest.kt, in the test folder in the package ‘org.fossasia.susi.ai.helper’. Step - 3 : Add a method, called testTruncateEmailAtEnd(), to the UtilsTest.kt and add ‘@Test’ annotation to before this method. Step - 4 : Now add tests for various cases, including all possible corner cases that might occur. This can be using assertEquals() which takes in two paramters - expected value and actual value. For example, consider an email ‘testuser@example.com’. This email is passed as a parameter to the truncateAtEnd() method. The expected returned string would be ‘testuser@ …’. So, add a test for this case using assertEquals() as : assertEquals("testuser@ ...", Utils.truncateEmailAtEnd("testuser@example.com"))   Similary, add other cases, like empty email string, null string, email with numbers and symbols and so on. Here is how the UtilsTest.kt class looks like. package org.fossasia.susi.ai.helper import junit.framework.Assert.assertEquals import org.junit.Test class UtilsTest { @Test fun testTruncateEmailAtEnd() { assertEquals("testuser@ ...", Utils.truncateEmailAtEnd("testuser@example.com")) assertEquals(null, Utils.truncateEmailAtEnd("testuser")) assertEquals(null, Utils.truncateEmailAtEnd("")) assertEquals(null, Utils.truncateEmailAtEnd(" ")) assertEquals(null, Utils.truncateEmailAtEnd(null)) assertEquals("test.user@ ...", Utils.truncateEmailAtEnd("test.user@example.com")) assertEquals("test_user@ ...", Utils.truncateEmailAtEnd("test_user@example.com")) assertEquals("test123@ ...", Utils.truncateEmailAtEnd("test123@example.com")) assertEquals(null, Utils.truncateEmailAtEnd("test user@example.com")) } }   Note: You can add more tests to check for other general and corner cases. Step - 5 : Run the tests in UtilsTest.kt. If all the test cases pass, then the tests pass. But, if the tests fail, try to figure out the cause of failure of the tests and add/modify the code in the Utils.kt accordingly. This approach helps recognize flaws in the existing code thereby reducing the risk of bugs and failures. Resources Build effective unit tests | Android Developers https://developer.android.com/training/testing/unit-testing/ Read about JUnit https://junit.org/junit5/ Read about Mockito https://site.mockito.org

Continue ReadingAdd Unit Test in SUSI.AI Android App

Display skills sorted in different orders in SUSI.AI Android App

Skills in SUSI.AI were displayed in a random order earlier as per the response received from the server. To provide more flexibility to the users, the skills can be sorted by various orders like top-rated, lexicographical, recently updated and so on. This blog shows how to get sorted skills from the server using the getSkillList.json API. API Information For requesting a list of SUSI skills, the endpoint used is /cms/getSkillList.json. This will give you the sorted skills as per the applied filter. Some of the filters include top rated skills, recently updated skills, newly created skills, etc. Base URL : https://api.susi.ai/cms/getSkillList.json Parameters to be passed : group - This is the group to which a skill belongs to. language - The language in which the skill is needed. applyFilter - This parameter tells if the filtering needs to be enabled. filter_type - This is the order in which the skills need to be sorted and is applicable if applyFilter is true. filter_name - This tells whether the order of sorting needs to be ascending or descending and is applicable if applyFilter is true. Currently, there are following filters available : Top Rated : The skills will be sorted based on the skills ratings by users. filter_type : rating filter_name : ascending or descending (based on the requirement).   Lexicographical : The skills will be sorted in alphabetical order. filter_type : lexicographical filter_name : ascending (to show skills in the order A-Z) or (descending to show skills in the order Z-A).   Newly Created : The skills will be displayed based on the date of creation. filter_type : creation_date filter_name : ascending to show newly created skills first and descending to show the oldest created skills first.   Recently Updated : The skills will be sorted based on the date when they were last updated. filter_type : modified_date filter_name : ascending or descending as per requirement.   Feedback Count : The skills will be sorted as per the feedback count. filter_type : feedback filter_name : ascending to show skills with the most number of feedbacks first and descending to show skills with the least number of feedbacks first.   This Week Usage : The skills will be sorted as per the usage analytics of the week. filter_type : usage duration : 7 filter_name : descending to show the most used skill first and vice-versa.   This Week Usage : The skills will be sorted as per the usage analytics for the last 30 days. filter_type : usage duration : 30 filter_name : descending to show the most used skill first and vice-versa.   Note: In all the above cases, the ‘applyFilter’ param will be passed with the value ‘true’ otherwise the skills will not be sorted. Here is an example of a URL for displaying the top rated skills: https://api.susi.ai/cms/getSkillList.json?group=All&language=en&applyFilter=true&filter_name=descending&filter_type=rating   To make a request to the getSkillList.json API, make a GET request as follows : @GET("/cms/getSkillList.json") Call<ListSkillsResponse> fetchListSkills(@QueryMap Map<String, String> query);   Here the query map contains all the aforementioned params.…

Continue ReadingDisplay skills sorted in different orders in SUSI.AI Android App

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 !=…

Continue ReadingShowing skills based on different metrics in SUSI Android App using Nested RecyclerViews

Show skills image in Circular Image View in SUSI.AI Android app

Each SUSI.AI skill has some data like skill name, skill image, skill rating and so on. Some of the skills image have a square appearance while others have a circular appearance and so on. This blog shows how to transform all images to circular image view while setting the skill image in the appropriate view holder in the skills card using Picasso. Step - 1 : Create a new helper class called CircleTransform.java that implements the Transformation interface from Picasso. Step -2 : Override the transform and key methods. Step - 3 : Create a Bitmap and perform the following steps inside the transform() method, as mentioned in the code below : @Override public Bitmap transform(Bitmap source) { int size = Math.min(source.getWidth(), source.getHeight()); int x = (source.getWidth() - size) / 2; int y = (source.getHeight() - size) / 2; Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); if (!squaredBitmap.equals(source)) { source.recycle(); } Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); paint.setShader(shader); paint.setAntiAlias(true); float radius = size / 2f; canvas.drawCircle(radius, radius, radius, paint); squaredBitmap.recycle(); return bitmap; }   This method returns a bitmap that we shall use to add to the appropriate view holder. Step - 4 : Also return a string called “circle” from the key() method. @Override public String key() { return "circle"; }   Step - 5 : Now, add this transformation to the code, where the skill image is set into the appropriate view holder using Picasso. fun setSkillsImage(skillData: SkillData, imageView: ImageView) { Picasso.with(imageView.context) .load(getImageLink(skillData)) .error(R.drawable.ic_susi) .transform(CircleTransform()) .fit() .centerCrop() .into(imageView) }   Now, all skill images will be circular, as can be seen in the following screenshot :  .      The first image shows the skills image before applying CircleTransform while the second image shows the same after applying it. Resources CircleTransform for Picasso https://gist.github.com/julianshen/5829333 Check out this blog on ‘AvatarView’ https://android.jlelse.eu/avatarview-custom-implementation-of-imageview-4bcf0714d09d Picasso Image Rotation and Transformation https://futurestud.io/tutorials/picasso-image-rotation-and-transformation

Continue ReadingShow skills image in Circular Image View in SUSI.AI Android app

Displaying Avatar of Users using Gravatar on SUSI.AI Android App

This blog post shows how the avatar image of the user is displayed in the feedback section using the Gravatar service on SUSI.AI Android app. A Gravatar is a Globally Recognized Avatar. Your Gravatar is an image that follows you from site to site appearing beside your name when you do things like comment or post on a blog. So, it was decided to integrate the service to SUSI.AI, so that it helps to  identify the user via the avatar image too. Implementation The aim is to use the user’s email address to get his/her avatar. For this purpose, Gravatar exposes a publicly available avatar of the user, which can be accessed via following steps : Creating the Hash of the email Sending the image request to Gravatar For the purpose of creating the MD5 hash of the email, use the MessageDigest class. The function takes an algorithm such as SHA-1, MD5, etc. as input and returns a MessageDigest object that implements the specified digest algorithm. Perform a final update on the digest using the specified array of bytes, which then completes the digest computation. That is, this method first calls update(input), passing the input array to the update method, then calls digest(). fun toMd5Hash(email: String?): String? { try { val md5 = MessageDigest.getInstance("MD5") val md5HashBytes: Array<Byte> = md5.digest(email?.toByteArray()).toTypedArray() return byteArrayToString(md5HashBytes) } catch (e: Exception) { return null } }   Convert the byte array to String, which is the requires MD5 hash of the email string. fun byteArrayToString(array: Array<Byte>): String { val result = StringBuilder(array.size * 2) for (byte: Byte in array) { val toAppend: String = String.format("%x", byte).replace(" ", "0") result.append(toAppend) } return result.toString() }   Now, a URL is generated using the hash. This is the URL that will be used to fetch the avatar. The URL format is https://www.gravatar.com/avatar/HASH, where HASH is the hash of the email of the user. In case, the hash is invalid, Gravatar returns a placeholder avatar. Also, append ‘.jpg’ to the URL to maintain image format consistency in the app. Finally, load this URL using Picasso and set it in the appropriate view holder. fun setAvatar(context: Context, email: String?, imageView: ImageView) { val imageUrl: String = GRAVATAR_URL + toMd5Hash(email) + ".jpg" Picasso.with(context) .load(imageUrl) .fit().centerCrop() .error(R.drawable.ic_susi) .transform(CircleTransform()) .into(imageView) }   You can now see the avatar image of the users in the feedback section. 😀 Resources MessageDigest | Android Developers https://developer.android.com/reference/java/security/MessageDigest  

Continue ReadingDisplaying Avatar of Users using Gravatar on SUSI.AI Android App

Use objects to pass multiple query parameters when making a request using Retrofit

There are multiple instances where there is a need to make an API call to the SUSI.AI server to send or fetch data. The Android client uses Retrofit to make this process easier and more convenient. While making a GET or POST request, there are often multiple query parameters that need to sent along with the base url. Here is an example how this is done: @GET("/cms/getSkillFeedback.json") Call<GetSkillFeedbackResponse> fetchFeedback( @Query("model") String model, @Query("group") String group, @Query("language") String language, @Query("skill") String skill);   It can be seen that the list of params can be very long indeed. A long list of params would lead to more risks of incorrect key value pairs and typos. This blog would talk about replacing such multiple params with objects. The entire process would be explained with the help of an example of the API call being made to the getSkillFeedback.json API. Step - 1 : Replace multiple params with a query map. @GET("/cms/getSkillFeedback.json") Call<GetSkillFeedbackResponse> fetchFeedback(@QueryMap Map<String, String> query);   Step - 2 : Make a data class to hold query param values. data class FetchFeedbackQuery( val model: String, val group: String, val language: String, val skill: String )   Step - 3 : Instead of passing all different strings for different query params, pass an object of the data class. Hence, add the following code to the ISkillDetailsModel.kt interface. ... fun fetchFeedback(query: FetchFeedbackQuery, listener: OnFetchFeedbackFinishedListener) ...   Step - 4 : Add a function in the singleton file (ClientBuilder.java) to get SUSI client. This method should return a call. ... public static Call<GetSkillFeedbackResponse> fetchFeedbackCall(FetchFeedbackQuery queryObject){ Map<String, String> queryMap = new HashMap<String, String>(); queryMap.put("model", queryObject.getModel()); queryMap.put("group", queryObject.getGroup()); queryMap.put("language", queryObject.getLanguage()); queryMap.put("skill", queryObject.getSkill()); //Similarly add other params that might be needed return susiService.fetchFeedback(queryMap); } ...   Step - 5 : Send a request to the getSkillFeedback.json API by passing an object of FetchFeedbackQuery data class to the fetchFeedbackCall method of the ClientBuilder.java file which in turn would return a call to the aforementioned API. ... override fun fetchFeedback(query: FetchFeedbackQuery, listener: ISkillDetailsModel.OnFetchFeedbackFinishedListener) { fetchFeedbackResponseCall = ClientBuilder.fetchFeedbackCall(query) ... }   No other major changes are needed except that instead of passing individual strings for each query param as params to different methods and creating maps at different places like in a view, create an object of FetchFeedbackQuery class and use it to pass data throughout the project. This ensures type safety. Also, data classes reduce the code length significantly and hence are more convenient to use in practice. Resources Kotlin data classes https://kotlinlang.org/docs/reference/data-classes.html A blog on ‘Retrofiting on Android with Kotlin’ https://segunfamisa.com/posts/using-retrofit-on-android-with-kotlin Link to the SUSI.AI Android repository https://github.com/fossasia/susi_android

Continue ReadingUse objects to pass multiple query parameters when making a request using Retrofit

Make an API to check if an email address has been registered for SUSI.AI

This blog post talks about the implementation of the checkRegistration.json API on the SUSI.AI server, which is a part of the AAA system. The API endpoint to check if an email address has been registered for SUSI is https://api.susi.ai/aaa/checkRegistration.json It accepts one compulsory url parameter - check_email - It is the parameter that contains the string type email address which the user enters in the email address field of the login screen. The minimalUserRole is set to ANONYMOUS for this API, as initially the registration status of the email address is unknown. API Development The parameter is first extracted via the post object that is passed to the serviceImpl function. The  parameter is then stored in a variable. If the parameter is absent, then it is set to the default value null. There is a check if the email is null. If null, an exception is thrown. This code snippet discusses the above two points - @Override public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization auth, final JsonObjectWithDefault permissions) throws APIException { String checkEmail = post.get("check_email", null); JSONObject result = new JSONObject(); if (checkEmail == null) { throw new APIException(422, "Email not provided."); } . . .   Set the credential variable of type ClientCredential by passing the parameters passwd_login and checkEmail to the ClientCredential constructor. Finally pass this credential variable to the getAuthentication method defined in the DAO to return the authentication object. The authentication object then invokes the authentication.getIdentity() method. If the result is null, it means the email address has not been registered yet and vice-versa. Internally, the entire checking procedure is done from the authentication.json file that is stored in data/settings/ directory of the server. The response object is then sent with three key values mainly, apart from the session object. They are - accepted -  true - It tells that the API call has been successful. exists - It tells that the email address has already been registered. check_email -  It is the same email address that was sent as a query parameter. Here are the important code snippets - Continuation of the first code snippet - . . . // check if id exists already ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, checkEmail); Authentication authentication =DAO.getAuthentication(credential); if (authentication.getIdentity() != null) { result.put("exists", true); } else { result.put("exists", false); } result.put("accepted", true); result.put("check_email", checkEmail); return new ServiceResponse(result); }   Sample response of checkRegistration.json API endpoint - { "check_email": "abc@email.com", "session": {"identity": { "type": "host", "name": "127.0.0.1_356778ca", "anonymous": true }}, "exists": true, "accepted": true }   The API development was done in the above explained way. This API will be used in improving the authentication flow in the Android client, where, if an email address has already been registered, then the user would be taken to the ‘Enter Password Screen’ otherwise he/she would be directed to the Signup screen. Resources Learn about Application Programming Interface (API) https://en.wikipedia.org/wiki/Application_programming_interface A blog on API development - An Introductory Guide https://dzone.com/articles/api-development-an-introductory-guide Check out some good login/signup UI flows https://medium.muz.li/login-sign-up-inspiration-for-mobile-apps-aeff34090bbd

Continue ReadingMake an API to check if an email address has been registered for SUSI.AI

Fetch Five Star Skill Rating from getSkillList API in SUSI.AI Android

SUSI.AI had a thumbs up/down rating system till now, which has now been replaced by a five star skill rating system. Now, the user is allowed to rate the skill based on a five star rating system. The UI components include a rating bar and below the rating bar is a section that displays the skill rating statistics - total number of ratings, average rating and a graph showing the percentage of users who rated the skill with five stars, four stars and so on. SUSI.AI Skills are rules that are defined in SUSI Skill Data repo which are basically the processed responses that SUSI returns to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which in turn fetches data from SUSI Skill Data and returns a JSON response to the app. Similarly, to get skill ratings, a call to the ‘/cms/getSkillList.json’ API is made. In this API, the server checks the SUSI Skill Data repo for the skills and returns a JSON response consisting of all the required information like skill name, author name, description, ratings, etc. to the app. Then, this JSON response is parsed to extract individual fields to display the appropriate information in the skill details screen of the app. API Information The endpoint to fetch skills is ‘/cms/getSkillList.json’ The endpoints takes three parameters as input - model - It tells the model to which the skill belongs. The default value is set to general. group - It tells the group(category) to which the skill belongs. The default value is set to All. language - It tells the language to which the skill belongs. The default value is set to en. Since all skills have to be fetched, this API is called for every group individually. For instance, call “https://api.susi.ai/cms/getSkillList.json?group=Knowledge” to get all skills in group “Knowledge”. Similarly, call for other groups. Here is a sample response of a skill named ‘Capital’ from the group Knowledge : "capital": { "model": "general", "group": "Knowledge", "language": "en", "developer_privacy_policy": null, "descriptions": "A skill to tell user about capital of any country.", "image": "images/capital.png", "author": "chashmeet singh", "author_url": "https://github.com/chashmeetsingh", "skill_name": "Capital", "terms_of_use": null, "dynamic_content": true, "examples": ["What is the capital of India?"], "skill_rating": { "negative": "0", "positive": "4", "feedback_count" : 0, "stars": { "one_star": 0, "four_star": 1, "five_star": 0, "total_star": 1, "three_star": 0, "avg_star": 4, "two_star": 0 } }, "creationTime": "2018-03-17T17:11:59Z", "lastAccessTime": "2018-06-06T00:46:22Z", "lastModifiedTime": "2018-03-17T17:11:59Z" }, It consists of all details about the skill called ‘Capital’: Model (model) Group (group) Language (language) Developer Privacy Policy (developer_privacy_policy) Description (descriptions) Image (image) Author (author) Author URL (author_url) Skill name (skill_name) Terms of Use (terms_of_use) Content Type (dynamic_content) Examples (examples) Skill Rating (skill_rating) Creation Time (creationTime) Last Access Time (lastAccessTime) Last Modified Time (lastModifiedTime) From among all this information, the information of interest for this blog is Skill Rating. This blog mainly deals with showing how to parse the JSON response to get the skill rating star values, so as to…

Continue ReadingFetch Five Star Skill Rating from getSkillList API in SUSI.AI Android

Handling Android Runtime permissions in UI Tests in SUSI.AI Android

With the introduction of Marshmallow (API Level 23), in SUSI.AI it was needed to ensure that: It was verified if we had the permission that was needed, when required The user was requested to grant permission, when it deemed appropriate The request (empty states or data feedback) was correctly handled within the UI to represent the outcome of being granted or denied the required permission You might have written UI tests. What about instances where the app needs the user’s permissions, like allow the app to access contacts on the device, for the tests to run? Would the tests pass when the test is run on Android 6.0+ devices? And, can Espresso be used to achieve this? Unfortunately, Espresso doesn’t have the ability to access components from outside of the application package. So, how to handle this? There are two approaches to handle this : 1) Using the UI Automator 2) Using the GrantPermissionRule Let us have a look at both of these approaches in detail : Using UI Automator to Handle Runtime Permissions on Android for UI Tests : UI Automator is a UI testing framework suitable for cross-app functional UI testing across system and installed apps. This framework requires Android 4.3 (API level 18) or higher. The UI Automator testing framework provides a set of APIs to build UI tests that perform interactions on user apps and system apps. The UI Automator APIs allows you to perform operations such as opening the Settings menu or the app launcher in a test device. This testing framework is well-suited for writing black box-style automated tests, where the test code does not rely on internal implementation details of the target app. The key features of this testing framework include the following : A viewer to inspect layout hierarchy. For more information, see UI Automator Viewer. An API to retrieve state information and perform operations on the target device. For more information, see Accessing device state. APIs that support cross-app UI testing. For more information, see UI Automator APIs. Unlike Espresso, UIAutomator can interact with system applications which means that you’ll be able to interact with the permissions dialog, if needed. So, how to do this? Well, if you want to grant a permission in a UI test then you need to find the corresponding UiObject that you wish to click on. In our case, the permissions dialog box is the UiObject. This object is a representation of a view - it is not bound to the view but contains information to locate the matching view at runtime, based on the properties of the UiSelector instance within it’s constructor. A UiSelector instance is an object that declares elements to be targeted by the UI test within the layout. You can set various properties such as a text value, class name or content-description, for this UiSelector instance. So, once you have your UiObject (the permissions dialog), you can determine which option you want to select and then use click( ) method to grant/deny permission access.…

Continue ReadingHandling Android Runtime permissions in UI Tests in SUSI.AI Android