Websearch and Link Preview support in SUSI iOS

The SUSI.AI server responds to API calls with answers to the queries made. These answers might contain an action, for example a web search, where the client needs to make a web search request to fetch different web pages based on the query. Thus, we need to add a link preview in the iOS Client for each such page extracting and displaying the title, description and a main image of the webpage.

At first we make the API call adding the query to the query parameter and get the result from it.

API Call:

http://api.susi.ai/susi/chat.json?timezoneOffset=-330&q=amazon

And get the following result:

{

"query": "amazon",

"count": 1,

"client_id": "aG9zdF8xMDguMTYyLjI0Ni43OQ==",

"query_date": "2017-06-02T14:34:15.675Z",

"answers": [

{

"data": [{

"0": "amazon",

"1": "amazon",

"timezoneOffset": "-330"

}],

"metadata": {

"count": 1

},

"actions": [{

"type": "answer",

"expression": "I don't know how to answer this. Here is a web search result:"

},

{

"type": "websearch",

"query": "amazon"

}]

}],

"answer_date": "2017-06-02T14:34:15.773Z",

"answer_time": 98,

"language": "en",

"session": {

"identity": {

"type": "host",

"name": "108.162.246.79",

"anonymous": true

}

}

}

After parsing this response, we first recognise the type of action that needs to be performed, here we get `websearch` which means we need to make a web search for the query. Here, we use `DuckDuckGo’s` API to get the result.

API Call to DuckDuckGo:

http://api.duckduckgo.com/?q=amazon&format=json

I am adding just the first object of the required data since the API response is too long.

Path: $.RelatedTopics[0]

{

 "Result": "<a href=\"https://duckduckgo.com/Amazon.com\">Amazon.com</a>Amazon.com, also called Amazon, is an American electronic commerce and cloud computing company...",

 "Icon": {

"URL": "https://duckduckgo.com/i/d404ba24.png",

"Height": "",

"Width": ""

},

 "FirstURL": "https://duckduckgo.com/Amazon.com",

 "Text": "Amazon.com Amazon.com, also called Amazon, is an American electronic commerce and cloud computing company..."

}

For the link preview, we need an image logo, URL and a description for the same so, here we will use the `Icon.URL` and `Text` key. We have our own class to parse this data into an object.

class WebsearchResult: NSObject {
   
   var image: String = "no-image"
   var info: String = "No data found"
   var url: String = "https://duckduckgo.com/"
   var query: String = ""
   
   init(dictionary: [String:AnyObject]) {
       
       if let relatedTopics = dictionary[Client.WebsearchKeys.RelatedTopics] as? [[String : AnyObject]] {
           
           if let icon = relatedTopics[0][Client.WebsearchKeys.Icon] as? [String : String] {
               if let image = icon[Client.WebsearchKeys.Icon] {
                   self.image = image
               }
           }
           
           if let url = relatedTopics[0][Client.WebsearchKeys.FirstURL] as? String {
               self.url = url
           }
           
           if let info = relatedTopics[0][Client.WebsearchKeys.Text] as? String {
               self.info = info
           }
           
           if let query = dictionary[Client.WebsearchKeys.Heading] as? String {
               let string = query.lowercased().replacingOccurrences(of: " ", with: "+")
               self.query = string
           }   
       }   
   }   
}

We now have the data and only thing left is to display it in the UI.

Within the Chat Bubble, we need to add a container view which will contain the image, and the text description.

 let websearchContentView = UIView()
    
    let searchImageView: UIImageView = {
        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
        imageView.contentMode = .scaleAspectFit

       // Placeholder image assigned
        imageView.image = UIImage(named: "no-image")
        return imageView
    }()
    
    let websiteText: UILabel = {
        let label = UILabel()
        label.textColor = .white
        return label
    }()

   func addLinkPreview(_ frame: CGRect) {
        textBubbleView.addSubview(websearchContentView)
        websearchContentView.backgroundColor = .lightGray
        websearchContentView.frame = frame
        
        websearchContentView.addSubview(searchImageView)
        websearchContentView.addSubview(websiteText)




       // Add constraints in UI
        websearchContentView.addConstraintsWithFormat(format: "H:|-4-[v0(44)]-4-[v1]-4-|", views: searchImageView, websiteText)
        websearchContentView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: searchImageView)
        websearchContentView.addConstraintsWithFormat(format: "V:|-4-[v0(44)]-4-|", views: websiteText)
    }

Next, in the Collection View, while checking other action types, we add checking for `websearch` and then call the API there followed by adding frame size and calling the `addLinkPreview` function.

else if message.responseType == Message.ResponseTypes.websearch {
  let params = [
    Client.WebsearchKeys.Query: message.query!,
    Client.WebsearchKeys.Format: "json"
  ]

  Client.sharedInstance.websearch(params, { (results, success, error) in                      
    if success {
      cell.message?.websearchData = results
      message.websearchData = results
      self.collectionView?.reloadData()
      self.scrollToLast()
    } else {
      print(error)
    }
  })
                    
  cell.messageTextView.frame = CGRect(x: 16, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 30)
  

 cell.textBubbleView.frame = CGRect(x: 4, y: -4, width: estimatedFrame.width + 16 + 8 + 16, height: estimatedFrame.height + 20 + 6 + 64)
                    
  let frame = CGRect(x: 16, y: estimatedFrame.height + 20, width: estimatedFrame.width + 16 - 4, height: 60 - 8)
  

 cell.addLinkPreview(frame)

}

And set the collection View cell’s size.

else if message.responseType == Message.ResponseTypes.websearch {
  return CGSize(width: view.frame.width, height: estimatedFrame.height + 20 + 64)
}

And we are done 🙂

Here is the final version how this would look like on the device:

 

Continue ReadingWebsearch and Link Preview support in SUSI iOS

Connecting Social Apps with Open Event Orga API Server

Orga API Server serves the organizer with many features but there was need of one feature which will allow us to provide Organizer an option to connect with their social media audience directly from API server. This will also allow the Orga Server users to share their experience for different events on their social media platforms.

For this feature, some of the social media platforms added are:

  • FaceBook
  • Twitter
  • Instagram
  • Google+

    Before connecting with these social media platforms, we have to implement and test The Auth Tool – OAuth 2.0

Without going on introductory introduction to OAuth 2.0, Let’s focus on its implementation in Orga API Server

OAuth Roles

OAuth defines four roles:

  • Resource Owner
  • Client
  • Resource Server
  • Authorization Server

Let’s look at the responsibility of these roles when you connect your social apps with API server

> Resource Owner: User/Organizer ( You )

You as User or Organizer connection your accounts with API server are the resource owners who authorize API server to access your account. During authorization, you provide us access to read your account details like Name, Email, Profile photo, etc.

> Resource / Authorization Server: Social Apps

The resource server here is your social platforms/apps where you have your account registered. These Apps provide us limited access to fetch details of your account once you authorize our application to do so. They make sure the token we provide match with the authorization provided before through your account.

> Client: Orga API Server

Orga API Server acts as the client to access your account details. Before it may do so, it must be authorized by the user, and the authorization must be validated by the API.

The process to add

A simple work plan to follow:

  1. Understanding how OAuth is implemented.
  2. Test OAuth implementation on all 4 social medias.
  3. After Necessary correction. Make sure we have all views(routes) to connect these 4 social medias.
  4. Implementing the same feature on the template file.
  5. Make sure these connect buttons are shown only when Admin has registered its client credentials in Settings.
  6. Creating a view to unlink your social media account.

Understanding how OAuth is implemented.

Current Implementation of Oauth is very simple and interesting on API server. We have Oauth helper classes which provide all necessary endpoints and different methods to get the job done.

Test OAuth implementation on all 4 social medias.

Now we can work on testing on the callbacks of all 4 social apps. We have callback defined in views/util_routes.py For this, I picked up the auth OAuth URLs and called them directly on my browsers and testing their callback. Now on callback, those methods required some change to save user data on database thus connecting their accounts with API server. This lead to changes in update_user_details and on callback methods.

def update_user_details(first_name=None,
                        last_name=None,
                        facebook_link=None,
                        twitter_link=None,
                        file_url=None,
                        instagram=None,
                        google=None):

Make sure we have all views(routes) to connect these 4 social medias

This has to be done on views/users/profile.py Addition of one method

@profile.route('/google_connect/', methods=('GET', 'POST'))
def google_connect():
        ....
        ....
    return redirect(gp_auth_url)

and testing, correction on other 3 methods

Implementing the same feature on template file.

Updating gentelella/users/settings/pages/applications.html to add changes required to add this feature. This included ability to show URLs of connected accounts and functioning connect and disconnect button

Make sure these connect buttons are shown only when Admin has registered its client credentials in Settings.

    fb = get_settings()['fb_client_id'] != None and get_settings()['fb_client_secret'] != None
        ....
        ....
        ...

The addition of such snippet provides data to the template to decide whether to show those fields or not. It will not make any sense if there is no application created to connect those accounts by Admin.

Creating a view to unlink your social media account.

utils_routes.route('/unlink-social/<social>')
def unlink_social(social):
    if login.current_user is not None and login.current_user.is_authenticated:
        ...
        ...

A method is created to unlink the connected accounts so that users can anytime disconnect their accounts from API server.

Where to connect?

Settings > Applications

 

How it Works (GIF below )

Continue ReadingConnecting Social Apps with Open Event Orga API Server

Setup Lint Testing in SUSI Android

As developers tend to make mistakes while writing code, small mistakes or issues can cause a negative impact on the overall functionality and speed of the app. Therefore it is necessary to understand the importance of Lint testing in Android.

Android Lint is a tool present in Android studio which is effective in scanning and reporting different types of bugs present in the code, it also find typos and security issues in the app. The issue is reported along with severity level thus allowing the developer to fix it based on the priority and level of damage they can cause. It is easy to use and can significantly improve the quality of your code.

Effect of Lint testing on Speed of the Android App

Lint testing can significantly improve the speed of the app in the following ways

  1. Android Link test helps in removing the declaration redundancy in the code, thus the Gradle need not to bind the same object again and again helping to improve the speed.
  2. Lint test helps to find bugs related to Class Structure in different Activities of the Application which is necessary to avoid the case of memory leaks.
  3. Lint testing also tells the developer above the size of resources use for example the drawable resources which sometimes take up a large piece of memory in the application. Cleaning these resources or replacing them with lightweight drawables helps in increasing the speed of the app.
  4. Overall Lint Testing helps in removing Typos, unused import statement, redundant strings, hence refactoring the whole code and increasing stability and speed.

Setup

We can use Gradle to invoke the list test by the following commands in the root directory of the folder.

To set up we can add this code to our build.gradle file

 lintOptions {
        lintConfig file("lint.xml")
    }

The lint.xml generated will look something like this

<?xml version="1.0" encoding="UTF-8"?>
<lint>
   <!-- Changes the severity of these to "error" for getting to a warning-free build -->
   <issue id="UnusedResources" severity="error"/>
</lint>

To explicitly run the test on Android we can use the following commands.

On Windows

gradlew lint

On Mac

./gradlew lint

We can also use the lint testing on various variants of the app, using commands such as

gradle lintDebug 

  or 

gradle lintRelease.

The xml file generated contains the error along with their severity level .

<?xml version="1.0" encoding="UTF-8"?>
<lint>
   <issue id="Invalid Package" severity="ignore" />
   <!-- All below are issues that have been brought to informational (so they are visible, but don't break the build) -->
   <issue id="GradleDependency" severity="informational" />    <issue id="Old TargetApi" severity="informational" />
</lint>

Testing on Susi Android:-

After testing the result on Susi Android we find the following errors.

As we can see that there are two errors and a lot of warnings. Though warning are not that severe but we can definitely improve on this. Thus making a habit of testing your code with lint test will improve the performance of your app and will make it more faster.

The test provides a complete and detailed list of issues present in the project.

We can find the exact location as well as the cause of the error by going deeper into the directory like this.

We can see there is a error in build.gradle file which is due to different versions of libraries present in the gradle files as of com.android.support libraries must be of same version.

In this way we can test out code and find errors in it.

Continue ReadingSetup Lint Testing in SUSI Android

Automatic deployment of Github repositories to a web server using SFTP

Git and Github are amazing tools for managing projects and are being used widely to manage various web applications among other projects.  Even though it is very efficient to manage codebase of the project, there is one concern. Since generally the project is developed at a different location and then deployed to the production server, it becomes burdensome to keep both the instances in sync.

One of the most basic methods to achieve this is to push the changes at the developing instance to Github and then pull them to the instance on the server. A slightly better and less stringent option would be to use tools like git-ftp.

From git-ftp’s documentation

If you use Git and you need to upload your files to an FTP server, Git-ftp can save you some time and bandwidth by uploading only those files that changed since the last upload.

Despite the fact that it decreases the strenuous work of accessing the server and pulling the changes, but there is a caveat. In the world of Version Control, a lot of projects are developed in collaboration with various contributors and it wouldn’t be wise to give everybody access to the server.

Among the different ways to facilitate the task of keeping the two instances in sync, there is one way using which we can automate the deployment process of a Github repository to a web server and keep it in sync. We can achieve this by using a script that executes git-ftp commands in every CI build.

The script does include some dependencies but at the crux of it are the following commands.

git config git-ftp.url $FTP_URL
git config git-ftp.user $FTP_USER
git config git-ftp.password $FTP_PASSWORD

if ! git ftp push ; then
  git ftp init
fi

Travis configuration

sudo: required

before_script:
- sudo apt-get install build-essential debhelper libssh2-1-dev
- sudo apt-get source libcurl13
- sudo apt-get build-dep libcurl13
- sudo add-apt-repository -y ppa:git-ftp/ppa
- sudo apt-get update
- sudo apt-get install git-ftp

script:
- source <(curl -s https://raw.githubusercontent.com/imujjwal96/sftp-audodeploy/master/init.sh)
Continue ReadingAutomatic deployment of Github repositories to a web server using SFTP

Using FastAdapter in Open Event Organizer Android Project

RecyclerView is an important graphical UI component in any android application. Android provides RecyclerView.Adapter class which manages all the functionality of RecyclerView. I don’t know why but android people have kept this class in a very abstract form with only basic functionalities implemented by default. On the plus side it opens many doors for custom adapters with new functionalities for example, sticky headers, scroll indicator, drag and drop actions on items, multiview types items etc. A developer should be able to make an adapter of his need by extending RecyclerView.Adapter. There are many custom adapters developers have shared which comes with built in functionalities. FastAdapter is one of them which comes with all the good functionalities built in and also it is very easy to use. I just got to use this in the Open Event Organizer Android App of which the core feature is Attendees Check In. We have used FastAdapter library to show attendees list which needs many features which are absent in plane RecyclerView.Adapter. FastAdapter is built in such way that there are many different ways of using it on developer’s need. I have found a simplest way which I will be sharing here. The first part is extending the item model to inherit AbstractItem.

public class Attendee extends AbstractItem<Attendee, AttendeeViewHolder> {
  @PrimaryKey
  private long id;
  ...
  ...

  @Override
  public long getIdentifier() {
      return id;
  }

  @Override
  public int getType() {
      return 0;
  }

  @Override
  public int getLayoutRes() {
      return R.layout.attendee_layout;
  }

  @Override
  public AttendeeViewHolder getViewHolder(View view) {
      return new AttendeeViewHolder(DataBindingUtil.bind(view));
  }

  @Override
  public void bindView(AttendeeViewHolder holder, List<Object> list) {
      super.bindView(holder, list);
      holder.bindAttendee(this);
  }

  @Override
  public void unbindView(AttendeeViewHolder holder) {
      super.unbindView(holder);
      holder.unbindAttendee();
  }
}

The methods are pretty obvious by name. Implement these methods accordingly. You may notice that we have used Databinding here to bind data to views but it is not necessary. Also you will have to create your ViewHolder for adapter. You can either use RecyclerView.ViewHolder or you can just create a custom one by inheriting it as per your need. Once this part is over you are half done as most of the things are been taken care in model itself. Now we will be writing code for adapter and setting it to your RecyclerView.

FastItemAdapter<Attendee> fastItemAdapter = new FastItemAdapter<>();
fastItemAdapter.setHasStableIds(true);
...
// functionalities related code
...
recyclerView.setAdapter(fastItemAdapter);

Initialize FastItemAdapter which will be our main adapter handling all the direct functions related to the RecyclerView. Set up some boolean constants according to the project need. In our project we have Attendee model which has id as a primary field. FastItemAdapter can take advantage of distinct field of the model called as identifier . Hence it is set true as Attendee model has id field. But you should be careful about setting it to True as then you must have implemented getIdentifier in the model to return correct field which will be used as an identifier by our adapter. And the adapter is good to set to the RecyclerView.

Now we got to decide which functionalities we will be implementing to our RecyclerView. In our case we needed: 1. Search filter for attendees, 2. Sticky header for attendees groups arranged alphabetically and 3. On click listener for attendee item.

FastItemAdapter has ItemFilter adapter wrapped inside which manages all the filtering stuff. Filtering logic can be set using it.

fastItemAdapter.getItemFilter().withFilterPredicate(this::shallFilter);

Where shallFilter is method which takes attendee object and returns boolean whether to filter the item or not. And after this you can use FastItemAdapter’s filter method to filter the items. For sticky headers you need to implement StickyRecyclerHeadersAdapter extending AbstractAdapter. In this class you will have to implement your filter logic in getHeaderId method. This must return an unique id for items of the same group.

@Override
public long getHeaderId(int position) {
   IItem item = getItem(position);
   if(item instanceof Attendee && ((Attendee)item).getFirstName() != null)
       return ((Attendee) item).getFirstName().toUpperCase().charAt(0);
   return -1;
}

Like in this case we have grouped attendees alphabetically hence just returning initial character’s ASCII value will do good. You can modify this method according to your need. For other unimplemented methods just keep their default return values. With this you will also have to implement onCreateHeaderViewHolder and onBindHeaderViewHolder methods to bind view and data to the header layout. Once this is done you are ready to set sticky headers to your RecyclerView with following code:

stickyHeaderAdapter = new StickyHeaderAdapter();
final HeaderAdapter headerAdapter = new HeaderAdapter();
recyclerView.setAdapter(stickyHeaderAdapter.wrap((headerAdapter.wrap(fastItemAdapter))));

final StickyRecyclerHeadersDecoration decoration = new StickyRecyclerHeadersDecoration(stickyHeaderAdapter);
recyclerView.addItemDecoration(decoration);
adapterDataObserver = new RecyclerView.AdapterDataObserver() {
   @Override
   public void onChanged() {
       decoration.invalidateHeaders();
   }
};
stickyHeaderAdapter.registerAdapterDataObserver(adapterDataObserver);

For click listener, the code is similar to the RecyclerView.Adapter’s one.

fastItemAdapter.withOnClickListener(new FastAdapter.OnClickListener<Item>() {
  @Override
  public boolean onClick(View v, IAdapter<Item> adapter, Item item, int position) {
     // your on click logic
   return true;
  }
});

With this now you have successfully implemented FastItemAdapter to your RecyclerView. Although there are some important points to be taken care of. If you are using filter in your application then you will have to modify your updateItem logic. As when filter is applied to the adapter its items list is filtered. And if you are updating the item using its position from original list it then it will result in exception or updating the wrong item. So you will have to change the position to the one in filtered list. For example the updateAttendee method from Organizer App code looks like this:

public void updateAttendee(int position, Attendee attendee) {
   position = fastItemAdapter.getAdapterPosition(attendee);
   fastItemAdapter.getItemFilter().set(position, attendee);
}
Continue ReadingUsing FastAdapter in Open Event Organizer Android Project

Combining multiple images into one image in the Phimpme Android Application

The Phimpme app comes with a lot of image editing functionalities. One such fascinating feature Phimpme provides is to combine two or more multiple images into one with just a button click. To do this in the Phimpme Application, long click two or more medias and click the affix button to combine them into one from the menu options. This is how two images when affixed looks like:

 

In this post, I will talk about how to achieve this functionality in any Android application with some code snippets. Here is the step by step tutorial on how to achieve this:

Step 1:

The first step is to reduce the bitmap of the selected images to a smaller size to avoid the OutOFMemory Exception while affixing images. As by affixing the images we will be making the images smaller, we don’t need the high quality bitmaps for it. To reduce the bitmaps use the function provided below:

private Bitmap getBitmap(String path) {

       Uri uri = Uri.fromFile(new File(path));
       InputStream in = null;
       try {
       final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
       in = getContentResolver().openInputStream(uri);

       // Decode image size
       BitmapFactory.Options o = new BitmapFactory.Options();
       o.inJustDecodeBounds = true;
       BitmapFactory.decodeStream(in, null, o);
       in.close();

       int scale = 1;
           while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) >
               IMAGE_MAX_SIZE) {
                   scale++;
       }

       Bitmap bitmap = null;
       in = getContentResolver().openInputStream(uri);
       if (scale > 1) {
           scale--;
           // scale to max possible inSampleSize that still yields an image
           // larger than target
           o = new BitmapFactory.Options();
           o.inSampleSize = scale;
           bitmap = BitmapFactory.decodeStream(in, null, o);

           // resize to desired dimensions
           int height = bitmap.getHeight();
           int width = bitmap.getWidth();

           double y = Math.sqrt(IMAGE_MAX_SIZE
                   / (((double) width) / height));
           double x = (y / height) * width;

           Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x,
                   (int) y, true);
           bitmap.recycle();
           bitmap = scaledBitmap;

           System.gc();
       } else {
           bitmap = BitmapFactory.decodeStream(in);
       }
       in.close();

       Log.d(TAG, "bitmap size - width: " +bitmap.getWidth() + ", height: " +
               bitmap.getHeight());
       return bitmap;
   } catch (IOException e) {
       Log.e(TAG, e.getMessage(),e);
       return null;
   }
}

Step 2:

Create a bitmap array of the selected images using the following snippet:

for (int i = 0; i < selectedMedias.size(); i++) {
    bitmapArray.add(getBitmap(selectedMedias.get(i).getPath()));
}

Here the selectedMedias is the ArrayList<Media> files which is used to create a bitmap array of reduced pixels using the function getBitmap we created in the first step.

Step 3:

Create a union bitmap of the two images of particular width and height and then draw your bitmaps into a canvas using the Android Canvas class :

unionBitmap = Bitmap.createBitmap(getBitmapsWidth(bitmapArray), getMaxBitmapHeight(bitmapArray), Bitmap.Config.ARGB_8888);
Canvas comboImage = new Canvas(unionBitmap);

Step 4:

The last step is to combine the two images and get the affixed Canvas as the result. This can be done using the function given below which takes the Canvas and ArrayList of bitmaps as a parameter and returns a Canvas of images.

private static Canvas combineBitmap(Canvas cs, ArrayList bpmList){
       int width = bpmList.get(0).getWidth();
       cs.drawBitmap(bpmList.get(0), 0f, 0f, null);

       for (int i = 1; i < bpmList.size(); i++) {
           cs.drawBitmap(bpmList.get(i), width, 0f, null);
           width += bpmList.get(i).getWidth();
       }
       return cs;
   }

This is how we achieved the affixing images feature in our phimpme Android application. For the complete working source code. Refer to the Affix.java class in Phimpme Android project source code.

Resources:

  1. https://developer.android.com/guide/topics/graphics/2d-graphics.html
  2. https://github.com/fossasia/phimpme-android/
  3. https://github.com/HoraApps/LeafPic
  4. https://stackoverflow.com/questions/11740362/merge-two-bitmaps-in-android
Continue ReadingCombining multiple images into one image in the Phimpme Android Application

How to add a new Servlet/API to SUSI Server

You have got a new feature added to enhance SUSI-AI (in web/android/iOS application) but do not find an API which could assist you in your work to make calls to the server {since the principle of all Susi-AI clients is to contact with SUSI-server for any feature}. Making servlets for  Susi is quite different from a normal JAVA servlet. Though the real working logic remains the same but we have got classes which allow you to directly focus on one thing and that is to maintain your flow for the feature. To find already implemented servlets, first clone the susi_server repository  from here.

git clone https://github.com/fossasia/susi_server.git

Cd to susi_server directory or open your terminal in susi_server directory. (This blog focuses on servlet development for Susi only and hence it is assumed that you have any version of JAVA8 installed properly). If you have not gone through how to run a susi_server manually, then follow  below steps to start the server:

./gradlew build	   //some set of files and dependencies will be downloaded
bin/start.sh		   //command to start the server

This will start your Susi server and it will listen at port 4000.

The first step is to analyze that to which class of API is your  servlet  going to be added. Let us take a small example and see how to proceed step by step. Let us look at development of ListSettingsService servlet. (to find the code of this servlet, browse to the following location: susi_server->src->ai->susi->server->api->aaa). Once you have decided the classification of your srvlet, create a .java file in it (Like we created ListSettingsService.java file in aaa folder). Extend AbstractAPIHandler class to your class and implement APIHandler to your class. If you are using any IDE like Intelij IDEA or eclipse then they will give you an error message and when you click on it, it  will ask you to Override some methods. Select the option and if you are using a simple text editor, then override the following methods in the given way:

@Override
    public String getAPIPath() {
        return null;
    }
@Override
    public BaseUserRole getMinimalBaseUserRole() {
        return null;
    }
@Override
    public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
        return null;
    }
@Override
    public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization rights, JsonObjectWithDefault permissions) throws APIException {
        Return null;
    }

What all these methods are for and why do we need them?

These are those 4 methods that make our work way easy. With the code compilation, first getAPIPath() is called to evaluate the end point.  Whenever this end point is called properly, it responds with whatever is defined in serviceImpl(). In our case we have given the endpoint

"/aaa/listSettings.json".

Ensure that you do not have 2  servlets with same end point.

Next in the line is getMinimalBaseUserRole() method. While developing certain features, a need of special privilege {like admin login} might be required. If you are implementing a feature for Admins only (like  we are doing in this servlet), return BaseUserRole.ADMIN. If you want to give access to anyone (registered or not) then return BaseUserRole.Anonymous. These might be login, signup or maybe a search point. By default all these methods are returning null. Once you are decided what to return, encode it in serviceImpl() method.

Look at the below implementation of the servlet :

@Override
    public String getAPIPath() {
        return "/aaa/listSettings.json";
}

@Override
    public BaseUserRole getMinimalBaseUserRole() {
        return BaseUserRole.ADMIN;
}

@Override
    public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
        return null;
}

@Override
    public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization rights, JsonObjectWithDefault permissions) throws APIException {

        String path = DAO.data_dir.getPath()+"/settings/";
        File settings = new File(path);
        String[] files = settings.list();
        JSONArray fileArray = new JSONArray(files);
        return new ServiceResponse(fileArray);
    }
}

As discussed earlier, the task of this servlet is to list all the files in data/settings folder. But this list is only available to users with admin login.

DAO.data_dir.getPath() returns a String identifier which is the path to data directory present in susi_server folder. We append “/settings/” to access settings folder inside it. Next we list all the files present in settings folder, encode them as a JSONArray object and reeturn the JSONArray object.

Think you can enhance Susi-server now? Get started right away!!

Continue ReadingHow to add a new Servlet/API to SUSI Server

Implementing Travis for Testing Phimpme Android

What is Travis CI? It’s importance in Android Testing.

Travis CI is continuous integration service which is hosted online and makes it easy for developers to automate any kinds of test on their Github projects. In an open source project, there are many developers from around the world working together and the repository experiences many new commits in a single day. Their might be a chance that while making the changes, they have broken something in the application. In the first blog, I discussed how to add Espresso UI tests. After this the question arises is how to use these tests to check the stability at each push or a pull request.

How I ran Espresso Tests in Phimpme using Travis?

After adding the Espresso Tests in the application, we  need to configure .travis.yml file to run the tests on each push in the repository. This can be achieved by following the steps below:

Step 1:

In the .travis.yml file of the repository, check whether the existing configuration exists to check for gradle build of the application. Create one in case it is not their and add the configurations to do a build check. You can take help from the official site of Travis or from the .travis.yml file in the Phimpme repository.

Step 2:

Now in the before script part of the file add the following codes which will start the emulator in the Travis to run the Espresso Tests.

    Before_script:

    – echo no | android create avd force n test t android19 – –abi armeabiv7a

    emulator avd test noaudio nowindow &

     sleep 300

     adb shell input keyevent 82 &

The sleep 300 is used to wait for a specified time so that the emulator starts perfectly before running the test.

Step 3:

In the script part of the file, run the Espresso test written in the application by adding the following line of codes.

Script:

./gradlew connectedAndroidTest

After adding these, make a test commit on your repository. Login to your Travis account to check the status of the test.

For the complete code of how to implement Travis test, refer to this Pull request in the Phimpme Android repository. In this Pull request, I did not make use of the sleep 300, due to which sometimes the test was failing without any broken UI. Hence, make sure to add this line of script for better results.

Resources:

https://docs.travis-ci.com/user/languages/android/

https://github.com/fossasia/phimpme-android

Continue ReadingImplementing Travis for Testing Phimpme Android

Making SUSI’s login experience easy


Every app should provide a smooth and user-friendly login experience, as it is the first point of contact between app and user. To provide easy login in SUSI, auto-suggestion email address is used. With this feature, the user is able to select his email from autocomplete dropdown if he has successfully logged in earlier in the app just by typing first few letters of his email. Thus one need not write the whole email address each and every time for login.
Let’s see how to implement it.
AutoCompleteTextView is the subclass of EditText class, which displays a list of suggestions in a drop down menu from which user can select only one suggestion or value. To use AutoCompleteTextView the dependency of the latest version of design library to the Gradle build file should be added.

dependencies {
compile "com.android.support:design:$support_lib_version"
}

Next, in the susi_android/app/src/main/res/layout/activity_login.xml. The AutoCompleteTextView is wrapped inside TextInputLayout to provide an input field for email to the user.

<android.support.design.widget.TextInputLayout
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email"
android:id="@+id/email_input"
android:textColor="@color/edit_text_login_screen"
android:inputType="textEmailAddress"
android:textColorHint="@color/edit_text_login_screen" />
<android.support.design.widget.TextInputLayout/>

href=”https://github.com/fossasia/susi_android/blob/development/app/src/main/java/org/fossasia/susi/ai/activities/LoginActivity.java”>susi_android/app/src/main/java/org/fossasia/susi/ai/activities/LoginActivity.java following import statements is added to import the collections.

import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

The following code binds the AutoCompleteTextView using ButterKnife.

@BindView(R.id.email_input)
AutoCompleteTextView autoCompleteEmail;

To store every successfully logged in email id, use Preference Manager in login response.

...
if (response.isSuccessful() &amp;&amp; response.body() != null) {
Toast.makeText(LoginActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
// Save email for autocompletion
savedEmails.add(email.getEditText().getText().toString());
PrefManager.putStringSet(Constant.SAVED_EMAIL,savedEmails);
}
...

Here Constant.SAVED_EMAIL is a string defined in Constants.java as

public static final String SAVED_EMAIL="saved_email";

Next to specify the list of suggestions emails to be displayed the array adapter class is used. The setAdapter method is used to set the adapter of the autoCompleteTextView.

private Set savedEmails = new HashSet&lt;&gt;();
if (PrefManager.getStringSet(Constant.SAVED_EMAIL) != null) {
savedEmails.addAll(PrefManager.getStringSet(Constant.SAVED_EMAIL));
autoCompleteEmail.setAdapter(new ArrayAdapter&lt;&gt;(this, android.R.layout.simple_list_item_1, new ArrayList&lt;&gt;(savedEmails)));
}

Then just test it with first logging in and after that every time you log in, just type first few letters and see the email suggestions. So, next time when you make an app with login interface do include AutoCompleteview for hassle-free login.

Continue ReadingMaking SUSI’s login experience easy