Play Youtube Videos in SUSI.AI Android app

SUSI.AI Android app has many response functionalities ranging from giving simple ANSWER type responses to complex TABLE and MAP type responses. Although, even after all these useful response types there were some missing action types all related to media. SUSI.AI app was not capable of playing any kind of music or video.So, to do this  the largest source of videos in the world was thought and the functionality to play youtube videos in the app was added. Since, the app now has two build flavors corresponding to the FDroid version and PlayStore version respectively it had to be considered that while adding the feature to play youtube videos any proprietary software was not included with the FDroid version. Google provides a youtube API that can be used to play videos inside the app only instead of passing an intent and playing the videos in the youtube app. Steps to integrate youtube api in SUSI.AI The first step was to create an environment variable that stores the API key, so that each developer that wants to test this functionality can just create an API key from the google API console and put it in the build.gradle file in the line below def YOUTUBE_API_KEY = System.getenv('YOUTUBE_API_KEY') ?: '"YOUR_API_KEY"'     In the build.gradle file the buildConfigfield parameter names API_KEY was created so that it can used whenever we need the API_KEY in the code. The buildConfigfield was declared for both the release and debug build types as : release {   minifyEnabled false  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  buildConfigField "String", 'YOUTUBE_API_KEY', YOUTUBE_API_KEY } debug {   buildConfigField "String", 'YOUTUBE_API_KEY', YOUTUBE_API_KEY } The second step is to catch the audio and video action type in the ParseSusiResponseHelper.kt file which was done by adding two constants “video_play” and “audio_play” in the Constant class. These actions were easily caught in the app as : Constant.VIDEOPLAY -> try {   identifier = susiResponse.answers[0].actions[i].identifier } catch (e: Exception) {   Timber.e(e) } Constant.AUDIOPLAY -> try {   identifier = susiResponse.answers[0].actions[i].identifier } catch (e: Exception) {   Timber.e(e) } The third step involves making a ViewHolder class for the audio and video play actions. A simple layout was made for this viewholder which can display the thumbnail of the video and has a play button on the thumbnail, on clicking which plays the youtube video. Also, in the ChatFeedRecyclerAdapter we need to specify the action as Audio play and video play and then load the specific viewholder for the youtube videos whenever the response from the server for this particular action is fetched. The YoutubeViewHolder.java file describes the class that displays the thumbnail of the youtube video whenever a response arrives. public void setPlayerView(ChatMessage model) {   this.model = model;   if (model != null) {       try {           videoId = model.getIdentifier();           String img_url = "http://img.youtube.com/vi/" + videoId + "/0.jpg";           Picasso.with(itemView.getContext()).load(img_url).                   placeholder(R.drawable.ic_play_circle_filled_white_24dp)                   .into(playerView);       } catch (Exception e) {           Timber.e(e);       }   } The above method shows how the thumbnail is set for a particular youtube video. The fourth step is to pass the response from the server in the ChatPresenter to…

Continue ReadingPlay Youtube Videos in SUSI.AI Android app

Added “table” type action support in SUSI android app

SUSI.AI has many actions supported by it, for eg: answer, anchor, map, piechart, websearch and rss.These actions are a few of those that can be supported in the SUSI.AI android app, but there are many actions implemented on the server side and the web client even has the implementation of how to handle the “table” type response. The table response is generally a JSON array response with different json objects, where each json object have similar keys, and the actions key in the JSON response has the columns of the table response which are nothing but the keys in the data object of the response. To implement the table type response in the susi android app a separate file needed to made to parse the table type response, since the keys and values both are required to the display the response. The file ParseTableSusiResponseHelper.kt was made which parsed the JSON object using the Gson converter factory to get the key value of the actions : "actions": [        {          "columns": {            "ingredients": "Ingredients",            "href": "Instructions Link",            "title": "Recipe"          },          "count": -1,          "type": "table"        }      ]   The inside the columns the keys and the values, both were extracted, values were to displayed in the title of the column and keys used were to extract the values from the “data” object of the response. The files TableColumn.java, TableData.java are POJO classes that were used for storing the table columns and the data respectively. The TableDatas.java class was used to store the column list and the data list for the table response. To fetch the table type response from the server a TableSusiResponse.kt file was added that contained serializable entities which were used to map the response values fetched from the server. A variable that contained the data stored in the “answers” key of the response was made of type of an ArrayList of TableAnswers. @SerializedName("answers") @Expose val answers: List<TableAnswer> = ArrayList() The TableAnswer.kt is another file added that contains serializable variables to store values inside the keys of the “answers” object. The actions object shown above is inside the answers object and it was stored in the form of an ArrayList of TableAction. @SerializedName("actions") @Expose val actions: List<TableAction> = ArrayList() Similar to TableAnswer.kt file TableAction.kt file also contains serializable variables that map the values stored in the “actions” object. In the retrofit service interface SusiService.java a new call was added to fetch the data from the server as follows : @GET("/susi/chat.json") Call<TableSusiResponse> getTableSusiResponse(@Query("timezoneOffset") int timezoneOffset,                                            @Query("longitude") double longitude,                                            @Query("latitude") double latitude,                                            @Query("geosource") String geosource,                                            @Query("language") String language,                                            @Query("q") String query); Now, after the data was fetched, the table response can be parsed using the Gson converter factory in the ParseTableSusiResponseHelper.kt file. Below is the implementation : fun parseSusiResponse(response: Response<TableSusiResponse>) {   try {       var response1 = Gson().toJson(response)       var tableresponse = Gson().fromJson(response1, TableBody::class.java)       for (tableanswer in tableresponse.body.answers) {           for (answer in tableanswer.actions) {               var map = answer.columns               val set = map?.entries               val iterator = set?.iterator()               while (iterator?.hasNext().toString().toBoolean()) {                   val entry = iterator?.next()                   listColumn.add(entry?.key.toString())                   listColVal.add(entry?.value.toString())…

Continue ReadingAdded “table” type action support in SUSI android app

Link Preview Holder on SUSI.AI Android Chat

SUSI Android contains several view holders which binds a view based on its type, and one of them is LinkPreviewHolder. As the name suggests it is used for previewing links in the chat window. As soon as it receives an input as of link it inflates a link preview layout. The problem which exists was that whenever a user inputs a link as an input to app, it crashed. It crashed because it tries to inflate component that doesn’t exists in the view that is given to ViewHolder. So it gave a Null pointer Exception, due to which the app crashed. The work around for fixing this bug was that based on the type of user it will inflate the layout and its components. Let’s see how all functionalities were implemented in the LinkPreviewHolder class. Components of LinkPreviewHolder @BindView(R.id.text) public TextView text; @BindView(R.id.background_layout) public LinearLayout backgroundLayout; @BindView(R.id.link_preview_image) public ImageView previewImageView; @BindView(R.id.link_preview_title) public TextView titleTextView; @BindView(R.id.link_preview_description) public TextView descriptionTextView; @BindView(R.id.timestamp) public TextView timestampTextView; @BindView(R.id.preview_layout) public LinearLayout previewLayout; @Nullable @BindView(R.id.received_tick) public ImageView receivedTick; @Nullable @BindView(R.id.thumbs_up) protected ImageView thumbsUp; @Nullable @BindView(R.id.thumbs_down) protected ImageView thumbsDown; Currently in this it binds the view components with the associated id using declarator @BindView(id) Instantiates the class with a constructor public LinkPreviewViewHolder(View itemView , ClickListener listener) { super(itemView, listener); realm = Realm.getDefaultInstance(); ButterKnife.bind(this,itemView); } Here it binds the current class with the view passed in the constructor using ButterKnife and initiates the ClickListener. Now it is to set the components described above in the setView function: Spanned answerText; text.setLinksClickable(true); text.setMovementMethod(LinkMovementMethod.getInstance()); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { answerText = Html.fromHtml(model.getContent(), Html.FROM_HTML_MODE_COMPACT); } else { answerText = Html.fromHtml(model.getContent()); } Sets the textView inside the view with a clickable link. Version checking also has been put for checking the version of Android (Above Nougat or not) and implement the function accordingly. This ViewHolder will inflate different components based on the thing that who has requested the output. If the query wants to inflate the LinkPreviewHolder them some extra set of components will get inflated which need not be inflated for the response apart from the basic layout. if (viewType == USER_WITHLINK) { if (model.getIsDelivered()) receivedTick.setImageResource(R.drawable.ic_check); else receivedTick.setImageResource(R.drawable.ic_clock); } In the above code  received tick image resource is set according to the attribute of message is delivered or not for the Query sent by the user. These components will only get initialised when the user has sent some links. Now comes the configuration for the result obtained from the query.  Every skill has some rating associated to it. To mark the ratings there needs to be a counter set for rating the skills, positive or negative. This code should only execute for the response and not for the query part. This is the reason for crashing of the app because the logic tries to inflate the contents of the part of response but the view that is passed belongs to query. So it gives NullPointerException there, so there is a need to separate the logic of Response from the Query. if (viewType !=…

Continue ReadingLink Preview Holder on SUSI.AI Android Chat

Datewise splitting of the Bookmarks in the Homescreen of Open Event Android

In the Open Event Android app we had already incorporated bookmarks in the homescreen along with an improved UI. Now there was scope for further improvement in terms of user experience. The bookmarks were already sorted date wise but we needed to place them under separate date headers. In this blog I will be talking about how this was done in the app.                 Initially the user had no way of knowing which session belonged to which day. This could be fixed with a simple addition of a header indicating the day each bookmark belonged to. One way to do this was to add a day header and then get the bookmarks for each day and so on. But this proved to be difficult owing to the fact the number of days could be dynamic owing to the fact that this is a generic app. Another issue was that adding change listeners for the realm results to the bookmarks list for each day produced view duplication and other unexpected results whenever the bookmark list changed. So another approach was chosen that was to get all the bookmarks first and then add the date header and traverse through the bookmarks and only add sessions which belong to the date for which the date header was added earlier. Bookmark Item Support in GlobalSearchAdapter The main reason why we are reusing the GlobalSearchAdapter is that we have already defined a DIVIDER type in this adapter which can be reused as the date header. We needed to initialize a constant for the Bookmark type. private final int BOOKMARK = 5; //Can be any number Then we add the Bookmark type in the getItemViewType() function which would return a constant that we defined earlier to indicate that in the filteredResultList we have an object of type Bookmark. @Override public int getItemViewType(int position) {    if (filteredResultList.get(position) instanceof Track) {        return TRACK;    }     //Other Cases here    } else if(filteredResultList.get(position) instanceof Session){        return BOOKMARK;    } else {        return 1;    } } Now we create the viewholder if the list item is of the type Session which in this case will be a bookmark. @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {    RecyclerView.ViewHolder resultHolder = null;    LayoutInflater inflater = LayoutInflater.from(parent.getContext());    //Other cases for Track,Location etc case BOOKMARK:    View bookmark = inflater.inflate(R.layout.item_schedule, parent, false);    resultHolder = new DayScheduleViewHolder(bookmark,context);    break;   //Some code } Now we do the same in onBindViewHolder(). We bind the contents of the object to the ViewHolder here by calling the bindSession() function. We also pass in an argument which is our database repository i.e realmRepo here. @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {     switch (holder.getItemViewType()) {     //Other cases handled here case BOOKMARK:    DayScheduleViewHolder bookmarkTypeViewHolder = (DayScheduleViewHolder) holder;    Session bookmarkItem = (Session) getItem(position);    bookmarkTypeViewHolder.setSession(bookmarkItem);    bookmarkTypeViewHolder.bindSession(realmRepo);    break; } Updating the AboutFragment private GlobalSearchAdapter bookMarksListAdapter; private List<Object> mSessions =…

Continue ReadingDatewise splitting of the Bookmarks in the Homescreen of Open Event Android