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

Adding support for rich text in Eventyay Organizer App

The Open Event Organizer App provides the users with its one of the core features of the ability to create or update an event. To add this feature, we will use HTML and android’s WebView in order to aid us integrate support for rich text in the app. The first step is adding the Wasabeef RichText library to your project. Open your build.gradle file and add the support library to the dependency section. Adding the dependency in build.gradle(app-level) in the project: dependencies {    //Other dependencies    //Rich text editor    implementation "jp.wasabeef:richeditor-android:1.2.2" } What we need is an activity accessible throughout the project for any element that needs to input rich text, which means that this activity goes in the utils package. Let’s start with building this activity: We need to first set a hint text or a placeholder text for the editor in case there’s no saved text for the concerned field. public class RichEditorActivity extends AppCompatActivity {    …    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        …        binding.editor.setPlaceholder(getString(R.string.enter_text));        Intent intent = getIntent();        if (intent != null) {          description = intent.getStringExtra(TAG_RICH_TEXT);          if (!TextUtils.isEmpty(description) && !description.equals(getString(R.string.describe_event))) {            binding.editor.setHtml(description);          }        } } Now let's see how we add the formatted text to our WebView. Currently we are supporting the options: Undo Redo Bold Italic StrikeThrough Bulleted list Numbered list as follows: binding.actionUndo.setOnClickListener(v -> binding.editor.undo()); binding.actionRedo.setOnClickListener(v -> binding.editor.redo()); binding.actionBold.setOnClickListener(v -> binding.editor.setBold()); binding.actionItalic.setOnClickListener(v -> binding.editor.setItalic()); binding.actionStrikethrough.setOnClickListener(v -> binding.editor.setStrikeThrough()); binding.actionInsertBullets.setOnClickListener(v -> binding.editor.setBullets()); binding.actionInsertNumbers.setOnClickListener(v -> binding.editor.setNumbers()); To add support for adding links, we need to first setup a click listener and show a dialog on click: binding.actionInsertLink.setOnClickListener(v -> {    if (linkDialog == null) {        createLinkDialog();    }    linkDialog.show(); }); The above listener is using a linkDialog, which is initialized in the method below, as follows: We are first dynamically creating a LinearLayout, and then 2 EditTextViews, and then adding those views to the LinearLayout. Finally we build the AlertDialog as usual and set the Dialog’s view to the LinearLayout we created. private void createLinkDialog() {    LinearLayout layout = new LinearLayout(this);    layout.setOrientation(LinearLayout.VERTICAL);    final EditText text = new EditText(this);    text.setHint(getString(R.string.text));    layout.addView(text);    final EditText link = new EditText(this);    link.setHint(getString(R.string.insert_url));    layout.addView(link);    linkDialog = new AlertDialog.Builder(this)       .setPositiveButton(getString(R.string.create), (dialog, which) -> {            binding.editor.insertLink(link.getText().toString(), text.getText().toString());       })       .setNegativeButton(getString(R.string.cancel), (dialog, which) -> {           dialog.dismiss();       })       .setView(layout)       .create(); } Here’s how the result looks like: Resources Wasabeef’s Rich Editor README https://github.com/wasabeef/richeditor-android

Continue ReadingAdding support for rich text in Eventyay Organizer App

Adding Events’ Payment Preferences to Eventyay Organizer Android App

The Open Event Organizer Android App allows creating events from the app itself. But organizers had to enter the payment information every time. To solve this problem, the PR#1058 was opened which saves the Organizers' payment preferences in Event Settings. The Open Event project offers 5 types of payment options: Online: 1. Paypal 2. Stripe Offline: 3. Cash payment 4. Bank Transfer 5. Cheques Each of the above need the payment specific details to be saved. And stuffing all of them into a single Event Settings screen isn’t a good option. Therefore the following navigation was desired: Event Settings -> Payment Preferences -> All options with their preferences Android Developer guide references a simple method to achieve the above, which is by using nested preference screens. But unfortunately, there’s a bug in the support library and it cannot be implemented with  PreferenceFragmentCompat So we had to apply a hack to the UI. We set an OnPreferenceClickListener as follows: public class EventSettingsFragment extends PreferenceFragmentCompat {    …    @Override    public void onCreatePreferencesFix(@Nullable Bundle bundle, String rootKey) {        …        findPreference("payment_preferences").setOnPreferenceClickListener(preference -> {            FragmentTransaction transaction = getFragmentManager().beginTransaction();            transaction                .replace(R.id.fragment_container, PaymentPrefsFragment.newInstance())                .addToBackStack(null)                .commit();            return true;        });    }    … } Once the preference item “Payment Preferences” is clicked, we initiate a fragment transaction opening the Payment Preferences screen, and add it to the fragment back stack. For each payment option, we have two things to consider: Is that payment option supported by the organizer? If yes, we need to store the necessary details in order to direct the payment to the organizer. We are also keeping track of whether the organizer wants to keep using the same payment preferences for future events as well. This way we save the organizer’s effort of entering payment details for each new event. <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">    <CheckBoxPreference        android:key="use_payment_prefs"        android:title="@string/use_payment_prefs"        android:summaryOn="@string/using_payment_preferences"        android:summaryOff="@string/not_using_payment_preferences"        android:defaultValue="false" />    <PreferenceCategory        android:title="Bank Transfer">        <CheckBoxPreference            android:key="accept_bank_transfers"            android:title="@string/accept_payment_by_bank_transfer"            android:defaultValue="false"/>        <EditTextPreference            android:key="bank_details"            android:title="@string/bank_details" />    </PreferenceCategory>    … </PreferenceScreen> Now the only thing remaining is to set payment preferences once the Event Creation form is opened. Hence the following method is called in  CreateEventPresenter  sets the payment details for each option that the organizer has already saved the information for. It does this by using constants named like PREF… all declared in the  Constants.java  file. using a custom Preference class which abstracts away some boilerplate code for us.    public void setPaymentPreferences(Preferences preferences) {        if (preferences.getBoolean(PREF_USE_PAYMENT_PREFS, false)) {            …            event.setCanPayByBank(                preferences.getBoolean(PREF_ACCEPT_BANK_TRANSFER, false)            );            event.setBankDetails(                preferences.getString(PREF_BANK_DETAILS, null)            );            …            getView().setPaymentBinding(event);        }    } This is how the result looks like: Resources Save key-value data | Android Developers https://developer.android.com/training/data-storage/shared-preferences Android developer fundamentals course practicals https://google-developer-training.gitbooks.io/android-developer-fundamentals-course-practicals/content/en/Unit%204/91_p_shared_preferences.html Working with Android Shared Preferences https://android.i-visionblog.com/working-with-android-shared-preferences-4668efa298a8

Continue ReadingAdding Events’ Payment Preferences to Eventyay Organizer Android App

Using RealmRecyclerView Adapter to show list of recorded sensor data from Realm Database

In previous blog Storing Recorded Sensor Data in Realm Database we have stored the data fetched from sensors into the Realm Database by defining model classes. In this blog, we will use the data stored in the Realm to display a list of recorded experiments in the form of well defining card view items so that it is easier for the user to understand. For showing the list we will make use of RecyclerView  widget provided by Android which is a more advanced version of the List view and is used to display large data sets in a vertical list, horizontal list, grid, staggered grid etc. RecyclerView  works in accordance with RecyclerView Adapter which is core engine that is responsible of inflating the layout of list items, populating the items with data, recycling of list item views when they go out of viewing screen and much more. For this blog, we are going to use a special RecyclerView Adapter provided by Realm itself because it integrates properly with the Realm Database and handles modifying, addition, deletion or updating of Realm data automatically and efficiently.    Step 1 Adding the dependencies As always first we need to add the following code in our build.gradle file to add the dependency of Realm database and RealmRecyclerViewAdapter. dependencies { implementation"com.android.support:recyclerview-v7:27.1.1 " implementation 'io.realm:android-adapters:2.1.1' } Step 2 Adding RecyclerView widget in our Activity layout file First, we need to create an activity and name it as “DataLoggerActivity”, inside the layout of the Activity add the <RecyclerView> widget. This RecyclerView will act as a container of our list item. <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".activity.DataLoggerActivity"> <android.support.v7.widget.RecyclerView android:layout_below="@id/top_app_bar_layout" android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout> Step 3 Creating the layout and View holder for the list item We have to create the layout of the list item which will be inflated by the Adapter. So for this create an XML file in res folder and name it “data_list_item.xml”. For the list of the experiments, we want to show Name of the experiment, recording time, recording date for every list item. For this we will make use of <CardView> and <TextView>. This gist shows the code of xml file. The layout of the list item created is shown in Figure 2 Now we need to create a view holder for this layout which we need to pass to the Adapter, the following code shows the implementation of View Holder for above list item layout. public class ViewHolder extends RecyclerView.ViewHolder { private TextView sensor, dateTime; ImageView deleteIcon; private CardView cardView; public ViewHolder(View itemView) { super(itemView); dateTime = itemView.findViewById(R.id.date_time); sensor = itemView.findViewById(R.id.sensor_name); deleteIcon = itemView.findViewById(R.id.delete_item); cardView = itemView.findViewById(R.id.data_item_card); } } Step 4 Creating the adapter for RecyclerView   In this step, we will start by creating a class called “SensorLoggedListAdpater” and for using use the RecyclerView adapter provided by Realm we need to make this class extend the RealmRecyclerViewAdpater class. But for that we need to pass two generic parameter: Model Class : This is class which define a…

Continue ReadingUsing RealmRecyclerView Adapter to show list of recorded sensor data from Realm Database

Storing Recorded Sensor Data in Realm Database

PSLab android app provides various new features like accessing data from the sensors that are either inbuilt into the Android phone or common I2C sensors which are connected to the PSLab device through PIC microcontroller. But the problem is that if the user records the data one time he/she may not be able to view that data in the future as there was no way to save that data somewhere. Saved data can be used for school experiments, preparing reports, research purposes etc. So, now we have integrated Realm database with the Sensor Data Logger module which is a mobile database that can be used to store real-time data in fast and flawless manner. It is a object oriented database so it stores data in the form of objects which makes it usage with object oriented programming language like Java much easier. In this blog we will demonstrate the process of storing data from one instrument  i.e., Lux Meter which records illuminance with respect to time to understand the process. First, we have defined a model class “SensorLogged” which contains information pertaining to all one experiment performed by the user. It will have fields like time of start of recording, the time of the end of the recording, date of recording, sensor name etc. Whenever a user performs an experiment we will store a object of the SensorLogged model class in realm database containing info for that experiment. public class SensorLogged extends RealmObject { private String sensor; private long dateTimeStart; @PrimaryKey private long uniqueRef; private long dateTimeEnd; public SensorLogged(String sensor) { this.sensor = sensor; } public void setSensor(String sensor) { this.sensor = sensor; } public void setDateTimeStart(long dateTimeStart) { this.dateTimeStart = dateTimeStart; } public void setUniqueRef(long uniqueRef) { this.uniqueRef = uniqueRef; } public void setDateTimeEnd(long dateTimeEnd) { this.dateTimeEnd = dateTimeEnd; } }   For storing Lux data we have to define a model class “LuxData” which defines all the fields in one reading of experiment. public class LuxData extends RealmObject { private long foreignKey; private float lux; private long timeElapsed; public LuxData() { } public LuxData(float lux, long timeElapsed) { this.lux = lux; this.timeElapsed = timeElapsed; } public long getForeignKey() { return foreignKey; } public void setForeignKey(long foreignKey) { this.foreignKey = foreignKey; } } We will use the object of this class for every reading of one measurement and provide them with the same Foreign Key which will be Primary key uniqueRef of “SensorLogged” model class. In this way, we can query all the reading belonging to one measurement from the database containing all the LuxData entries. For storing the data in Realm database we will follow these steps: Begin the Realm transaction. realm.beginTransaction(); Create a object of “SensorLogged” model class for every measurement with the unique Ref as the primary key and store the information like time of start, date of start, sensor name etc. copy it to the Realm Database. SensorLogged sensorLogged = realm.createObject(SensorLogged.class, uniqueRef); sensorLogged.setSensor("Lux Meter"); sensorLogged.setDateTimeStart(startTime); realm.copyToRealm(sensorLogged); For every sensor, reading create a object of LuxData…

Continue ReadingStoring Recorded Sensor Data in Realm Database

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