Deploying loklak Server on Kubernetes with External Elasticsearch

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.

kubernetes.io

Kubernetes is an awesome cloud platform, which ensures that cloud applications run reliably. It runs automated tests, flawless updates, smart roll out and rollbacks, simple scaling and a lot more.

So as a part of GSoC, I worked on taking the loklak server to Kubernetes on Google Cloud Platform. In this blog post, I will be discussing the approach followed to deploy development branch of loklak on Kubernetes.

New Docker Image

Since Kubernetes deployments work on Docker images, we needed one for the loklak project. The existing image would not be up to the mark for Kubernetes as it contained the declaration of volumes and exposing of ports. So I wrote a new Docker image which could be used in Kubernetes.

The image would simply clone loklak server, build the project and trigger the server as CMD

FROM alpine:latest

ENV LANG=en_US.UTF-8
ENV JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8

WORKDIR /loklak_server

RUN apk update && apk add openjdk8 git bash && \
    git clone https://github.com/loklak/loklak_server.git /loklak_server && \
    git checkout development && \
    ./gradlew build -x test -x checkstyleTest -x checkstyleMain -x jacocoTestReport && \
    # Some Configurations and Cleanups

CMD ["bin/start.sh", "-Idn"]

[SOURCE]

This image wouldn’t have any volumes or exposed ports and we are now free to configure them in the configuration files (discussed in a later section).

Building and Pushing Docker Image using Travis

To automatically build and push on a commit to the master branch, Travis build is used. In the after_success section, a call to push Docker image is made.

Travis environment variables hold the username and password for Docker hub and are used for logging in –

docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD

[SOURCE]

We needed checks there to ensure that we are on the right branch for the push and we are not handling a pull request –

# Build and push Kubernetes Docker image
KUBERNETES_BRANCH=loklak/loklak_server:latest-kubernetes-$TRAVIS_BRANCH
KUBERNETES_COMMIT=loklak/loklak_server:kubernetes-$TRAVIS_COMMIT
  
if [ "$TRAVIS_BRANCH" == "development" ]; then
    docker build -t loklak_server_kubernetes kubernetes/images/development
    docker tag loklak_server_kubernetes $KUBERNETES_BRANCH
    docker push $KUBERNETES_BRANCH
    docker tag $KUBERNETES_BRANCH $KUBERNETES_COMMIT
    docker push $KUBERNETES_COMMIT
elif [ "$TRAVIS_BRANCH" == "master" ]; then
    # Build and push master
else
    echo "Skipping Kubernetes image push for branch $TRAVIS_BRANCH"
fi

[SOURCE]

Kubernetes Configurations for loklak

Kubernetes cluster can completely be configured using configurations written in YAML format. The deployment of loklak uses the previously built image. Initially, the image tagged as latest-kubernetes-development is used –

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: server
  namespace: web
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: server
    spec:
      containers:
      - name: server
        image: loklak/loklak_server:latest-kubernetes-development
        ...

[SOURCE]

Readiness and Liveness Probes

Probes act as the top level tester for the health of a deployment in Kubernetes. The probes are performed periodically to ensure that things are working fine and appropriate steps are taken if they fail.

When a new image is updated, the older pod still runs and servers the requests. It is replaced by the new ones only when the probes are successful, otherwise, the update is rolled back.

In loklak, the /api/status.json endpoint gives information about status of deployment and hence is a good target for probes –

livenessProbe:
  httpGet:
    path: /api/status.json
    port: 80
  initialDelaySeconds: 30
  timeoutSeconds: 3
readinessProbe:
  httpGet:
    path: /api/status.json
    port: 80
  initialDelaySeconds: 30
  timeoutSeconds: 3

[SOURCE]

These probes are performed periodically and the server is restarted if they fail (non-success HTTP status code or takes more than 3 seconds).

Ports and Volumes

In the configurations, port 80 is exposed as this is where Jetty serves inside loklak –

ports:
- containerPort: 80
  protocol: TCP

[SOURCE]

If we notice, this is the port that we used for running the probes. Since the development branch deployment holds no dumps, we didn’t need to specify any explicit volumes for persistence.

Load Balancer Service

While creating the configurations, a new public IP is assigned to the deployment using Google Cloud Platform’s load balancer. It starts listening on port 80 –

ports:
- containerPort: 80
  protocol: TCP

[SOURCE]

Since this service creates a new public IP, it is recommended not to replace/recreate this services as this would result in the creation of new public IP. Other components can be updated individually.

Kubernetes Configurations for Elasticsearch

To maintain a persistent index, this deployment would require an external Elasticsearch cluster. loklak is able to connect itself to external Elasticsearch cluster by changing a few configurations.

Docker Image and Environment Variables

The image used for Elasticsearch is taken from pires/docker-elasticsearch-kubernetes. It allows easy configuration of properties from environment variables in configurations. Here is a list of configurable variables, but we needed just a few of them to do our task –

image: quay.io/pires/docker-elasticsearch-kubernetes:2.0.0
env:
- name: KUBERNETES_CA_CERTIFICATE_FILE
  value: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- name: NAMESPACE
  valueFrom:
    fieldRef:
      fieldPath: metadata.namespace
- name: "CLUSTER_NAME"
  value: "loklakcluster"
- name: "DISCOVERY_SERVICE"
  value: "elasticsearch"
- name: NODE_MASTER
  value: "true"
- name: NODE_DATA
  value: "true"
- name: HTTP_ENABLE
  value: "true"

[SOURCE]

Persistent Index using Persistent Cloud Disk

To make the index last even after the deployment is stopped, we needed a stable place where we could store all that data. Here, Google Compute Engine’s standard persistent disk was used. The disk can be created using GCP web portal or the gcloud CLI.

Before attaching the disk, we need to declare a volume where we could mount it –

volumeMounts:
- mountPath: /data
  name: storage

[SOURCE]

Now that we have a volume, we can simply mount the persistent disk on it –

volumes:
- name: storage
  gcePersistentDisk:
    pdName: data-index-disk
    fsType: ext4

[SOURCE]

Now, whenever we deploy these configurations, we can reuse the previous index.

Exposing Kubernetes to Cluster

The HTTP and transport clients are enabled on port 9200 and 9300 respectively. They can be exposed to the rest of the cluster using the following service –

apiVersion: v1
kind: Service
...
Spec:
  ...
  ports:
  - name: http
    port: 9200
    protocol: TCP
  - name: transport
    port: 9300
    protocol: TCP

[SOURCE]

Once deployed, other deployments can access the cluster API from ports 9200 and 9300.

Connecting loklak to Kubernetes

To connect loklak to external Elasticsearch cluster, TransportClient Java API is used. In order to enable these settings, we simply need to make some changes in configurations.

Since we enable the service named “elasticsearch” in namespace “elasticsearch”, we can access the cluster at address elasticsearch.elasticsearch:9200 (web) and elasticsearch.elasticsearch:9300 (transport).

To confine these changes only to Kubernetes deployment, we can use sed command while building the image (in Dockerfile) –

sed -i.bak 's/^\(elasticsearch_transport.enabled\).*/\1=true/' conf/config.properties && \
sed -i.bak 's/^\(elasticsearch_transport.addresses\).*/\1=elasticsearch.elasticsearch:9300/' conf/config.properties && \

[SOURCE]

Now when we create the deployments in Kubernetes cluster, loklak auto connects to the external elasticsearch index and creates indices if needed.

Verifying persistence of the Elasticsearch Index

In order to see that the data persists, we can completely delete the deployment or even the cluster if we want. Later, when we recreate the deployment, we can see all the messages already present in the index.

I  [2017-07-29 09:42:51,804][INFO ][node                     ] [Hellion] initializing ...
 
I  [2017-07-29 09:42:52,024][INFO ][plugins                  ] [Hellion] loaded [cloud-kubernetes], sites []
 
I  [2017-07-29 09:42:52,055][INFO ][env                      ] [Hellion] using [1] data paths, mounts [[/data (/dev/sdb)]], net usable_space [84.9gb], net total_space [97.9gb], spins? [possibly], types [ext4]
 
I  [2017-07-29 09:42:53,543][INFO ][node                     ] [Hellion] initialized
 
I  [2017-07-29 09:42:53,543][INFO ][node                     ] [Hellion] starting ...
 
I  [2017-07-29 09:42:53,620][INFO ][transport                ] [Hellion] publish_address {10.8.1.13:9300}, bound_addresses {10.8.1.13:9300}
 
I  [2017-07-29 09:42:53,633][INFO ][discovery                ] [Hellion] loklakcluster/cJtXERHETKutq7nujluJvA
 
I  [2017-07-29 09:42:57,866][INFO ][cluster.service          ] [Hellion] new_master {Hellion}{cJtXERHETKutq7nujluJvA}{10.8.1.13}{10.8.1.13:9300}{master=true}, reason: zen-disco-join(elected_as_master, [0] joins received)
 
I  [2017-07-29 09:42:57,955][INFO ][http                     ] [Hellion] publish_address {10.8.1.13:9200}, bound_addresses {10.8.1.13:9200}
 
I  [2017-07-29 09:42:57,955][INFO ][node                     ] [Hellion] started
 
I  [2017-07-29 09:42:58,082][INFO ][gateway                  ] [Hellion] recovered [8] indices into cluster_state

In the last line from the logs, we can see that indices already present on the disk were recovered. Now if we head to the public IP assigned to the cluster, we can see that the message count is restored.

Conclusion

In this blog post, I discussed how we utilised the Kubernetes setup to shift loklak to Google Cloud Platform. The deployment is active and can be accessed from the link provided under wiki section of loklak/loklak_server repo.

I introduced these changes in pull request loklak/loklak_server#1349 with the help of @niranjan94, @uday96 and @chiragw15.

Resources

Continue ReadingDeploying loklak Server on Kubernetes with External Elasticsearch

Scraping Concurrently with Loklak Server

At Present, SearchScraper in Loklak Server uses numerous threads to scrape Twitter website. The data fetched is cleaned and more data is extracted from it. But just scraping Twitter is under-performance.

Concurrent scraping of other websites like Quora, Youtube, Github, etc can be added to diversify the application. In this way, single endpoint search.json can serve multiple services.

As this Feature is under-refinement, We will discuss only the basic structure of the system with new changes. I tried to implement more abstract way of Scraping by:-

1) Fetching the input data in SearchServlet

Instead of selecting the input get-parameters and referencing them to be used, Now complete Map object is referenced, helping to be able to add more functionality based on input get-parameters. The dataArray object (as JSONArray) is fetched from DAO.scrapeLoklak method and is embedded in output with key results

    // start a scraper
    inputMap.put("query", query);
    DAO.log(request.getServletPath() + " scraping with query: "
           + query + " scraper: " + scraper);
    dataArray = DAO.scrapeLoklak(inputMap, true, true);

 

2) Scraping the selected Scrapers concurrently

In DAO.java, the useful get parameters of inputMap are fetched and cleaned. They are used to choose the scrapers that shall be scraped, using getScraperObjects() method.

Timeline2.Order order= getOrder(inputMap.get("order"));
Timeline2 dataSet = new Timeline2(order);
List<String> scraperList = Arrays.asList(inputMap.get("scraper").trim().split("\\s*,\\s*"));

 

Threads are created to fetch data from different scrapers according to size of list of scraper objects fetched. input map is passed as argument to the scrapers for further get parameters related to them and output data according to them.

List<BaseScraper> scraperObjList = getScraperObjects(scraperList, inputMap);
ExecutorService scraperRunner = Executors.newFixedThreadPool(scraperObjList.size());

try{
    for (BaseScraper scraper : scraperObjList)
    {
        scraperRunner.execute(() -> {
            dataSet.mergePost(scraper.getData());
        });

    }

} finally {
    scraperRunner.shutdown();

    try {
        scraperRunner.awaitTermination(24L, TimeUnit.HOURS);
    } catch (InterruptedException e) { }
}

 

3) Fetching the selected Scraper Objects in DAO.java

Here the variable of abstract class BaseScraper (SuperClass of all search scrapers) is used to create List of scrapers to be scraped. All the scrapers’ constructors are fed with input map to be scraped accordingly.

List<BaseScraper> scraperObjList = new ArrayList<BaseScraper>();
BaseScraper scraperObj = null;

if (scraperList.contains("github") || scraperList.contains("all")) {
    scraperObj = new GithubProfileScraper(inputMap);
    scraperObjList.add(scraperObj);
}
.
.
.

 

References:

Continue ReadingScraping Concurrently with Loklak Server

Email and Password Validation in Open Event Android

The Open Event API Server exposes a well documented JSONAPI compliant REST API that can be used in The Open Even Android and Frontend. The Open Event API Server enables the Android and web clients to add the user authentication (sign up/login) in the project. In the process of signing up or logging in user it is needed to validate email and password entered by the user and show the error to give better user experience. In this post I explain how to validate email and password entered by the user using TextInputLayout.

1. Add TextInputLayout

TextInputLayout wraps an EditText (or descendant) to show a floating label when the hint is hidden due to the user inputting text. Add TextInputLayout for email field in the layout as given below.

<android.support.design.widget.TextInputLayout
            android:id="@+id/text_input_layout_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.AppCompatEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/email"
                android:inputType="textEmailAddress" />
</android.support.design.widget.TextInputLayout>

Here the hint attribute is used to display hint in the floating label. Specify the input type so the system displays the appropriate soft input method (such as an on-screen keyboard) for the field. For email EditText we are using textEmailAddress input type. Similarly add TextInputLayout for the password field. The input type for the password is textPassword.

2.  Create and initialize object

Now in the activity create and initialize TextInputLayout and EditText objects for email and password.

@BindView(R.id.text_input_layout_email)
TextInputLayout mTextInputLayoutEmail;
@BindView(R.id.text_input_layout_password)
TextInputLayout mTextInputLayoutPassword;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ButterKnife.bind(this);

    private AppCompatEditText mEditTextEmail = (AppCompatEditText) mTextInputLayoutEmail.getEditText();
    private AppCompatEditText mEditTextPassword = (AppCompatEditText) mTextInputLayoutPassword.getEditText();
}

Here we are using ButterKnife for binding views with fields. The getEditText() method returns the EditText view used for text input inside the TextInputLayout.

3.  Create validate() method

Create validate() method which takes two arguments. The first is email and the second password. It will return true if the email and password are valid else false.

private boolean validate(String email, String password) {

        // Reset errors.
        mTextInputLayoutEmail.setError(null);
        mTextInputLayoutPassword.setError(null);

        if (Utils.isEmpty(email)) {
            mTextInputLayoutEmail.setError("Email is required");
            return false;
        } else if (!Utils.isEmailValid(email)) {
            mTextInputLayoutEmail.setError("Enter a valid email");
            return false;
        }

        if (Utils.isEmpty(password)) {
            mTextInputLayoutPassword.setError("Password is required");
            return false;
        } else if (!Utils.isPasswordValid(password)) {
            mTextInputLayoutPassword.setError("Password must contain at least 6 characters");
            return false;
        }

        return true;
}

Here it first resets the error for the TextInputLayout by setting it to null. Then it checks email string if it is empty then it will show “Email is required” error using setError() method.

4.  Create isEmailValid() and isPasswordValid() method

Now create isEmailValid() and isPasswordvalid() method which is used by validate() method. The isEmailValid() method should take email string as an argument and return boolean indicating whether the email is valid or not. The isEmailValid() method uses Pattern and Matcher class to determine if the pattern of input is email or not. The isPasswordValid() method should take password string as an argument and return true if the password is satisfying minimum condition. Here in our case length of the password should be minimum 6.

public static boolean isEmailValid(String email){
        Pattern pattern = Patterns.EMAIL_ADDRESS;
        Matcher matcher = pattern.matcher(email);
        return matcher.matches();
}

//Check password with minimum requirement here(it should be minimum 6 characters)
public static boolean isPasswordValid(String password){
        return password.length() >= 6;
}

5.  Use validate() method

Now we are ready to use validate() method when signing up or logging in the user. The getText() method of EditText will return text input.

String email = mEditTextEmail.getText().toString();
String password = mEditTextPassword.getText().toString();

if (validate(email, password)) {
    //Sign up or login User
}

Conclusion

Using TextInputLayout with floating hint label and error handling gives awesome UI and UX.

Continue ReadingEmail and Password Validation in Open Event Android

Working with Activity API in Open Event API Server


Recently, I added the Activities API with documentation and dredd tests for the same in
Open Event API Server. The Activity Model in the Open Event Server is basically a log of all the things that happen while the server is running, like – event updates, speaker additions, invoice generations and other similar things. This blogpost explains how to implement Activity API in the Open Event API Server’s nextgen branch. In the Open Event Server, we first add the endpoints, then document these so that the consumers ( Open Event Orga App, Open Event Frontend) find it easy to work with all the endpoints.

We also test the documentation against backend implementation to ensure that a end-developer who is working with the APIs is not misled to believe what each endpoint actually does in the server.

We also test the documentation against backend implementation to ensure that a end-developer who is working with the APIs is not misled to believe what each endpoint actually does in the server.
The Activities API endpoints are based on the Activity database model. The Activity table has four columns – 
id, actor, time, action, the names are self-explanatory. Now for the API schema, we need to make fields corresponding these columns.
Since id is auto generated, we do not need to add it as a field for API. Also the activity model’s __init__ method stamps time with the current system time. So this field is also not required in the API fields. We are left with two fields- actor and action.

Defining API Schema

Next, we define the API Schema class for Activities model. This will involve a Meta class and fields for the class.The Meta class contains the metadata of the class. This includes details about type_,

self_view, self_view_kwargs and an inflect parameter to dasherize the input fields from request body.

We define the four fields – id, actor, time and action according to marshmallow fields based on the data type and parameters from the activities model. Since id, actor and action are string columns and time is a DateTime column, the fields are written as following:

The id field is marked as dump only because it is a read-only type field. The other fields are marked with allow_none as they are all non-required field.

ActivityList Class:
The activity list class will provide us with the endpoint: “/v1/activities”

This endpoint will list all the activities. Since we wanted only GET requests to be working for this, so defined method = [‘GET’, ] for ResourceList. The activities are to be internally created based on different actions like creating an event, updating an event, adding speakers to sessions and likewise. Since the activities are to be shown only to the server admin, 
is_admin permission is used from the permission manager.

ActivityDetail Class:

The activity detail gives methods to work with an activity based on the activity id.
The endpoint provided is :  ‘/v1/activity/<int:activity_id>’

Since this is also an admin-only accessible GET only endpoint the following was written:

Writing Documentation:

The documentation is written using API Blueprint. Since we have two endpoints to document : /v1/activities and /v1/activities/<int:activity_id> both GET only.

So we begin by defining the ‘Group Activities’ , under which we first list  ‘Activity Collection’ which essentially is the Activity List class.

For this class, we have the endpoint:  /v1/activities. This is added for GET request. The parameters – actor, time and action are described along with description, type and whether they are required or not.

The request headers are written as part of the docs followed by the expected response.

Next we write the ‘Activity Details’ which represents the ActivityDetail class. Here the endpoint /v1/activities/<int:activity_id> is documented for GET. The parameter here is activity_id, which is the id of the activity to get the details of.

Writing DREDD Test for Documentation

To imitate the request responses, we need a faker script which creates an object of the the class we are testing the docs for then makes the request. For this we use FactoryBoy and dredd hooks to insert data into the database.

Defining Factory Model for Activity db model

The above is the factory model for activity model. It is derived from

factory.alchemy.SQLAlchemyModelFactory. The meta class defines the db model and sqlalchemy session to be used. The actor and action have dummy strings as part of the request body.

Writing Hooks
Now to test these endpoints we need to add objects to the database so that the GET requests have an object to fetch. This is done by dredd hooks. Before each request, an object of the corresponding factory class is initialised and committed into the database. Thus a dummy object is available for dredd to test on. The request is made and the real output is compared with the expected output written in the API Blueprint documentation.

This is what the hooks look like for  this endpoint: GET /activities

Now if the expected responses and actual responses match, the dredd test successfully passes. This dredd test in run on each build of the project on Travis to ensure that documented code does exactly what is says!

This concludes the process to write an API right from Schema to Resources and Documentation and Dredd tests.

Additional Resources:

Continue ReadingWorking with Activity API in Open Event API Server

Using Lombok to Reduce Boilerplate Code in Open Event Android App

The Open Even App Android project contains data/model classes like Event, Track, Session, Speaker etc which are used to fetch data from the server and store data in the database. Each model contains private fields, getters, setters and toString() method which are used to change data of the object, access data of the object, logging and debugging. Adding all these methods manually makes the code boilerplate.

In this blog post I explain how to use Lombok to reduce boilerplate code in the model class.

Add dependency

To set up Lombok for your application you have to add the dependency in your app module’s build.gradle file.

dependencies {
	provided   "org.projectlombok:lombok:1.16.18"
}

Install Lombok plugin

In addition to setting up your gradle project correctly, you need to add the Lombok IntelliJ plugin to add Lombok support to Android Studio

  1. Go to File > Settings > Plugins
  2. Click on Browse repositories
  3. Search for Lombok Plugin
  4. Click on Install plugin
  5. Restart Android Studio

Write model class

Lombok has annotations to generate Getters, Setters, Constructors, toString(), Equal() and hashCode() methods.

@Getter,  @Setter, @ToString, @EqualsAndHashCode

@Data is a shortcut annotation that bundles the features of @Getter, @Setter, @ToString and @EqualsAndHashCode

Here I am only defining track model because of its simplicity and less complexity.

@Data
public class Track {

    private int id;
    private String name;
    private String description;
    private String color;
    private String fontColor;
    private RealmList<Session> sessions;
}

Create and use object

After defining models you can create an instance of the object and you will notice that you can access all the getters and setters.

Track track = new Track();
track.setName("Android");

String name = track.getName(); // here value of name will be "Android" 

You can also specify which fields to include and exclude in the toString(), equals() and hashCode() methods using @ToString, @EqualsAndHashCode annotation.

@ToString(of={"id", "name"})

@ToString(exclude="color")

@EqualsAndHashCode(of={"id", "name"})

@EqualsAndHashCode(exclude={"color", "fontColor"})

Constructors

Lombok has three methods to generator constructors

  • @NoArgsConstructor: It generates constructor with no parameters
  • @RequiredArgsConstructor: It generates a constructor with 1 parameter for each field that requires special handling.
  • @AllArgsConstructor: It generates a constructor with 1 parameter for each field in your class.

Conclusion

As you can see, Lombok uses succinct annotations to generate methods such as getters, setters, and constructors. It can easily help you get rid of hundreds of lines of boilerplate code. Lombok also allows you to make your code more expressive, concise and can help you avoid some bugs. To learn more about Lombok project follow the links given below.

Continue ReadingUsing Lombok to Reduce Boilerplate Code in Open Event Android App

Zooming Feature in the Phimpme Android’s Camera

The Phimpme Android application comes with a complete package of camera, Edit images, sharing and gallery functionalities. It has a well featured and fully functional camera with all the capabilities that a user expects from a camera application. One such feature in the Phimpme Android application is the zooming functionality. It provides the user the option to zoom in using the pinch gesture of the fingers or the user can select the settings to zoom in from the volume buttons. In this tutorial, I will be explaining how I achieved the zooming functionality in the Phimpme Android app.

Step 1

The first thing we need to do is to check whether the device will support the zoom in functionality or not to avoid random crashes while runtime of the application and while performing the zoom action in case the camera of the device doesn’t support this feature. This can be done by the following lines of code:

Camera.Parameters params = mCamera.getParameters();
Boolean supports = params.isZoomSupported();

Step 2

Now after getting the camera parameters and checking whether the camera supports the zoom in functionality, we need to add the touch listener to the surface view of the camera so that we can get the touch locations and the finger spacing of the user to get the pinch to zoom in functionality. This can be done using the following line of code.

surfaceView.setOnTouchListener(this);

Whenever the user touches the screen this touch listener gives a callback to the overridden onTouchEvent method and passes the MotionEvent to the function. The motion event object in Android handles the movement reports. Now in the onTouchEvent method, we calculate the finger spacing between the two fingers and calculate the approximate amount by which the user wants to zoom in. The finger spacing can be calculated using the following lines of code.

float x = event.getX(0) - event.getX(1);
   float y = event.getY(0) - event.getY(1);
   return FloatMath.sqrt(x * x + y * y);

After getting the finger spacing we need to cancel the auto focus of the camera before performing the zoom action so that the application does not crash. This can be achieved by a single line of code below.

mCamera.cancelAutoFocus();

Step 3

The final step is to set the zoom level in the camera application by calculating the zoom level by using the finger spacing. For this, first we need to get the max zoom level supported by the device so that we do not apply the zoom level that is not supported by the device. The calculation of max zoom level and setting of the desired zoom level by the user can be performed by using the following lines of code.

int maxZoom = params.getMaxZoom();
   int zoom = params.getZoom();
   float newDist = getFingerSpacing(event);
   if (newDist > mDist) {
       //zoom in
       if (zoom < maxZoom)
           zoom++;
   } else if (newDist < mDist) {
       //zoom out
       if (zoom > 0)
           zoom--;
   }
   mDist = newDist;
   params.setZoom(zoom);

This is how we have achieved the functionality of zooming in and clicking pictures in the Phimpme Android application. To get the full source code and to know how to use the volume control buttons to zoom in/out, please refer to the Phimpme Android repository.

Resources

  1. GitHub – Open camera source code : https://github.com/almalence/OpenCamera
  2. Android developer’s guide – MotionEvents in Android : https://developer.android.com/reference/android/view/MotionEvent.html
  3. StackOverflow – Pinch to zoom functionality : https://stackoverflow.com/questions/8120753/android-camera-preview-zoom-using-double-finger-touch
  4. GitHub – Phimpme Android repository : https://github.com/fossasia/phimpme-android
Continue ReadingZooming Feature in the Phimpme Android’s Camera

Creating SharedPreferences Util in Open Event Android

In the Open Event Android we have the fragment for schedule, speakers which has the option to sort the list. Schedule Fragment have the option to sort by Title, Tracks and  Start Time. Speakers Fragment has the option to sort by Name, Organization and Country. If the user preferred to sort by name then it should always sort the list by name whenever the user uses the app. For this we need to store user preference for sorting list. Another part of the app like Live feed, About fragment also needs to store event id, facebook page id/name etc.

In Android there is a SharedPreferences class to store key value pair in the App specific storage. To store data in SharedPreferences we need to create SharedPreferences Object in different activities and fragment. In this post I explain how to create SharedPreferences Util which can be used to store key value pairs from all over the App.

1. Create SharedPreferencesUtil Class

The first step is to create SharedPreferncesUtil.java file which will contain static SharedPreferences object.

public class SharedPreferencesUtil {
    ...
}

2. Create static objects

Create static SharedPreferences and SharedPreferences.Editor object in the SharedPreferncesUtil.java file.

private static SharedPreferences sharedPreferences;
private static SharedPreferences.Editor editor;

3. Initialize objects

Now after creating objects initialize them in the static block. The code inside static block is executed only once: The first time you make an object of that class or the first time you access a static member of that class.

static {
        sharedPreferences = OpenEventApp.getAppContext().getSharedPreferences(ConstantStrings.FOSS_PREFS, Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();
}

 

Here make sure to use the Application context to avoid a memory leak. The getSharedPreferences() method takes two arguments name of the shared preference and mode. Here we are using Context.MODE_PRIVATE File creation mode where the created file can only be accessed by the calling application.

4. Add methods

Now create static methods to store data so that we can use these methods directly from the other activities or classes. Here I am only adding methods for integer you can add more methods for String, long, boolean etc.

public static void putInt(String key, int value) {
        editor.putInt(key, value).apply();
}

public static int getInt(String key, int defaultValue) {
        return sharedPreferences.getInt(key, defaultValue);
}

5. Use SharedPreferencesUtil class

Now we are ready to use this Util class to store key value pair in SharedPreferences.

SharedPreferencesUtil.putInt(ConstantStrings.PREF_SORT, sortType);

Here the putInt() methods take two arguments one the key and second the value. To get the stored value use getInt() method.

SharedPreferencesUtil.getInt(ConstantStrings.PREF_SORT, 0);

To know more how I solved this issue in Open Event Project visit this link.

Continue ReadingCreating SharedPreferences Util in Open Event Android

Implementing an Interface for Reading Configuration from a YAML File for Yaydoc

Yaydoc reads configuration specified in a YAML file to set various options during the build process. This allows users to customize various properties of the build process. The current implementation for this was very basic. Basically it uses a pyYAML, a yaml parser for python to read the file and convert it to a python dictionary. From the dictionary we extracted values for various properties and converting them to strings using various heuristics such as converting True to ”true”, False to ”false”, a list to comma separated string and None to an empty string. Finally, we exported variables with those values.

Recently the entire code for this was rewritten using object-oriented paradigm. The motivation for this came from the fact that the implementation lacked certain features and also required some refactoring for long term readability. In the following paragraph, I have discussed the new implementation.

Firstly a Configuration class was created which basically wraps around a dictionary and provide certain utility methods. The primary difference is that the Configuration class allows dotted key access. This means that you can use the following syntax to access nested keys.

theme = conf[‘build.theme.name’]

The class provides another method connect which is used to connect environment variables with configuration values. This method also takes a dotted key but provides an extension on top of that to handle the case when a certain option can take multiple values. For example,

option: my_option

Or,

option:
  - my_option1
  - my_option2

To indicate that a certain config is of this type, you can specify a “@” character at the end of the key. Anything after the “@” character is assumed to be an attribute of each element within the list. Let’s see an example of this whole process.

build:
  subproject:
    - url: <url1>
  source: “doc”
    - url: <url2>

Now to extract all urls from the above file, we’d need to do the following

config.connect(‘SUBPROJECT_URLS’, ‘build.subproject@url’)

To extract sources, we’ll also use the default parameter as the source option is optional.

config.connect(‘SUBPROJECT_SOURCES’, build.subproject@source’, default=’docs’)

Finally, The Configuration object also provides a getenv method which reads all connection and serializes values to string according to the previously described heuristics. It then returns a dictionary of all environment variables which must be set.

Resources

Continue ReadingImplementing an Interface for Reading Configuration from a YAML File for Yaydoc

Implementing Proper CSS for Static Pages in SUSI.AI Web Chat

Our SUSI.AI Web Chat has many static pages like Overview, Devices, Team and Support. We have separate CSS files for each component. Recently, we faced a problem regarding design pattern where CSS files of one component were affecting another component. This blog is all about solving this issue and we take an example of distortion in our team’s page.

The current folder structure looks like this :

We can see that there are separate CSS files for all components. When the build of our react web app is complete, all the CSS files are loaded at once. So if CSS files contain classes with similar names, then this can disturb the original intended design of a particular component.

Our Team Page after merging of recent pull requests looked like this :

The Card component holding the images had extended vertically. The card component has following code:

<Card className='team-card' key={i}>
  <CardMedia className="container" >
    <img src={serv.avatar} alt={serv.name} 
      className="image" />
      <div className="overlay" >
        <div className="text">
         <FourButtons member={serv} />
        </div>
      </div>
  </CardMedia>
  <CardTitle title={serv.name} subtitle={serv.designation} />
</Card>

The CardMedia component is having className = “container”. This was defined in Team.css file. The CSS for this component is as follows :

.container {
  position: relative;
}
.container:hover .overlay {
  bottom: 0;
  height: 100%;
  opacity:0.7;
}

After inspecting through Chrome’s developer’s tool, it was found that these CSS properties were overwritten by another component having the same className as container. To resolve this issue there are multiple approaches:

  • Find the component with the same className and change the className of that component.
  • Change the className of current component.
  • Change the name of both components to resolve conflicts in future.

All the approaches will do the job for us. Here the easiest task was to change the className of the current component. This will save us time and we would not be adding extra lines of code. This is an efficient solution. So we decided to change the className to “container_div”. Then the CSS files will look like this:

.container_div {
  position: relative;
}
.container_div:hover .overlay {
  bottom: 0;
  height: 100%;
  opacity:0.7;
}

We also have to update the className in our CardMedia to “container_div”. After doing these changes. The cards were back to intended design:

To avoid such conflicts in future, it is recommended to name your CSS classes uniquely and after you’re done with making any component, recheck through developer’s tool that your component’s className does not have any conflicts with other components.

Resources:

CSS best practises: https://code.tutsplus.com/tutorials/30-css-best-practices-for-beginners–net-6741

Code for Team’s Page: https://github.com/fossasia/chat.susi.ai/tree/master/src/components/Team

Team Page: http://chat.susi.ai/team

Continue ReadingImplementing Proper CSS for Static Pages in SUSI.AI Web Chat

Advanced functionality in SUSI FBbot

SUSI AI is integrated to Facebook (blog). During the initial phase, SUSI FBbot had basic UI and functionalities like just “plain text” replies. Facebook provides many more features like replies enclosed in templates (blog link), sharing the replies by SUSI A.I. with friends, get started button or a persistent menu to show quick reply options to the user etc. All these features to enhance the user experience with SUSI AI chatbot.

This blog post walks you through on adding these functionalities to the SUSI FBbot:

Adding Get Started button

A Get Started button is added to the SUSI FBbot to give the user a brief introduction about SUSI AI and what the user can try next.

Clicking on the get started button , will send the message as “Get Started” to the SUSI FBbot:

The reply message, provides the user with options to visit SUSI A.I. repository or to just start chatting.

To have this button in our bot, we use this code snippet:

// Add a get started button to the messenger
request({
    url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
    qs: {access_token:token},
    method: 'POST',
    json: { 
      "get_started":{
        "payload":"GET_STARTED_PAYLOAD"
      }
    }
}, function(error, response, body) {
    // handle errors and response here
})

When a user clicks this button, a postback is sent to the webhook of SUSI FBbot with payload as “GET_STARTED_PAYLOAD”. When we receive such postback, we reply with a message as shown above using generic template.

Adding a persistent menu to the bot

If not at the start, while chatting with SUSI AI for sometime, it is quite possible that the user becomes curious to visit the repository of SUSI A.I. . So we need a quick access to the “Visit repository” button all the time. Persistent menu, helps us with this feature:

This way it is accessible at each point of time. Some other buttons can also be added to the menu like “Latest News” as shown in the image above.

To have a persistent menu for the SUSI FBbot, the following code snippet is used:

request({
        url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
        qs: {access_token:token},
        method: 'POST',
        json: {
                "persistent_menu":[{
                    "locale":"default", 
                        "composer_input_disabled":false,
                        "call_to_actions":[{
                            "type":"web_url",
                            "title":"Visit Repository",
                            "url":"https://github.com/fossasia/susi_server",
                            "webview_height_ratio":"full"
                        }]
                 }]
            }
    }, function(error, response, body) {
        // handle errors and response
    })

We can add more buttons to the menu. JSON object having the required properties of that button can be appended to the key “call_to_actions” to do so.

Adding a messenger code to join SUSI FBbot

To enable Facebook users to chat with SUSI AI by scanning a code through messenger. This feature is added to the bot by making the following POST request:

request({
        url: 'https://graph.facebook.com/v2.6/me/messenger_codes',
        qs: {access_token:token},
        method: 'POST',
        json: {
                type: "standard",
                image_size: 1000
        }
    }, function(error, response, body) {
        // handle errors and response.
});

Adding message sharing feature

To increase the reach of SUSI A.I. to more users on Facebook, message sharing proves to be a big boon. The reply by SUSI A.I. to users can be shared with their friends. Along with the message we can also send a promotional message(related to SUSI A.I.), to the people with which the message was shared.

This sharing can end up having more users trying SUSI A.I., leading to increase the user base of SUSI AI and its popularity.

We can allow sharing of just the message(i.e. the reply) or a promotional message with it. In case of just the reply:

Clicking the share button, will share just the reply with another person. To add capabilities of sharing the reply along with one more message(prompting to try SUSI A.I.), some changes to the code are done:

We need to set the buttons property in generic template like:

buttons : [
            {
                "type":"element_share",
                    "share_contents": { 
                      "attachment": {
                        "type": "template",
                        "payload": {
                          "template_type": "generic",
                          "elements": [
                            {
                              "title": "I had an amazing chat with SUSI.",
                              "buttons": [
                                {
                                  "type": "web_url",
                                  "url": "https://m.me/asksusisu", 
                                  "title": "Chat with SUSI AI"
                                }
                              ]
                            }
                          ]
                        }
                      }
                   }
            } 
       ];

This way when a user shares the message with other, an extra message is sent with the original message, tempting the user to try a chat with SUSI A.I.:

Resources:

  1. By Seth Rosenberg from Facebook developers blogLink Ads to Messenger, Enhanced Mobile Websites, Payments and More.
  2. By Slobodan Stojanović from smashing magazineDevelop a chat bot with node js.
Continue ReadingAdvanced functionality in SUSI FBbot