Enhancing Github profile scraper service in loklak

The Github profile scraper is one of the several scrapers present in the loklak project, some of the other scrapers being Quora profile scraper, WordPress profile scraper, Instagram profile scraper, etc.Github profile scraper scrapes the profile of a given github user and provides the data in json format. The scraped data contains user_id, followers, users following the given user, starred repositories of an user, basic information like user’s name, bio and much more. It uses the popular Java Jsoup library for scraping.

The entire source code for the scraper can be found here.

Changes made

The scraper previously provided very limited information like, name of the user, description, starred repository url, followers url, following url, user_id, etc. One of the major problem was the api accepted only the profile name as a parameter and it returned the entire data, that is, the scraper scraped all the data even if the user did not ask for it. Moreover the service provided certain urls as data for example starred repository url, followers url, following url instead of providing the actual data present at those urls.

The scraper contained only one big function where all the scraping was performed. The code was not modular.

The scraper has been enhanced in the ways mentioned below.

  • The entire code has been refactored.Code scraping user specific data has been separated from code scraping organization specific data. Also separate method has been created for accessing the Github API.
  • Apart from profile parameter, the api now accepts another parameter called terms. It is a list of fields the user wants information on. In this way the scraper scrapes only that much data which is required by the user. This allows better response time and prevents unnecessary scraping.
  • The scraper now provides more information like user gists, subscriptions, events, received_events and repositories.

Code refactoring

The code has been refactored to smaller methods. ‘getDataFromApi’ method has been designed to access Github API. All the other methods which want to make request to api.github.com/ now calls ‘getDatFromApi’ method with required parameter. The method is shown below.

private static JSONArray getDataFromApi(String url) {
       URI uri = null;
       try {
           uri = new URI(url);
       } catch (URISyntaxException e1) {
           e1.printStackTrace();
       }

       JSONTokener tokener = null;
       try {
           tokener = new JSONTokener(uri.toURL().openStream());
       } catch (Exception e1) {
           e1.printStackTrace();
       }
       JSONArray arr = new JSONArray(tokener);
       return arr;
   }

 

For example if we want to make a request to the endpoint https://api.github.com/users/djmgit/followers then we can use the above method and we will get a JSONArray in return.

All the code which scrapes user related data has been moved to ‘scrapeGithubUser’ method. This method scrapes basic user information like full name of the user, bio, user name, atom feed link, and location.

String fullName = html.getElementsByAttributeValueContaining("class", "vcard-fullname").text();
githubProfile.put("full_name", fullName);
 
String userName = html.getElementsByAttributeValueContaining("class", "vcard-username").text();
githubProfile.put("user_name", userName);
 
String bio = html.getElementsByAttributeValueContaining("class", "user-profile-bio").text();
githubProfile.put("bio", bio);
 
String atomFeedLink = html.getElementsByAttributeValueContaining("type", "application/atom+xml").attr("href");
githubProfile.put("atom_feed_link", "https://github.com" + atomFeedLink);
 
String worksFor = html.getElementsByAttributeValueContaining("itemprop", "worksFor").text();
githubProfile.put("works_for", worksFor);
 
String homeLocation = html.getElementsByAttributeValueContaining("itemprop", "homeLocation").attr("title");
githubProfile.put("home_location", homeLocation);

 

Next it returns other informations like starred repositories information, followers information, following information and information about the organizations the user belongs to but before fetching such data it checks whether such data is really needed. In this way unnecessary api calls are prevented.

if (terms.contains("starred") || terms.contains("all")) {
            String starredUrl = GITHUB_API_BASE + profile + STARRED_ENDPOINT;
            JSONArray starredData = getDataFromApi(starredUrl);
            githubProfile.put("starred_data", starredData);
 
            int starred = Integer.parseInt(html.getElementsByAttributeValue("class", "Counter").get(1).text());
            githubProfile.put("starred", starred);
        }
 
        if (terms.contains("follows") || terms.contains("all")) {
            String followersUrl = GITHUB_API_BASE + profile + FOLLOWERS_ENDPOINT;
            JSONArray followersData = getDataFromApi(followersUrl);
            githubProfile.put("followers_data", followersData);
 
            int followers = Integer.parseInt(html.getElementsByAttributeValue("class", "Counter").get(2).text());
            githubProfile.put("followers", followers);
        }
 
        if (terms.contains("following") || terms.contains("all")) {
            String followingUrl = GITHUB_API_BASE + profile + FOLLOWING_ENDPOINT;
            JSONArray followingData = getDataFromApi(followingUrl);
            githubProfile.put("following_data", followingData);
 
            int following = Integer.parseInt(html.getElementsByAttributeValue("class", "Counter").get(3).text());
            githubProfile.put("following", following);
        }
 
        if (terms.contains("organizations") || terms.contains("all")) {
            JSONArray organizations = new JSONArray();
            Elements orgs = html.getElementsByAttributeValue("itemprop", "follows");
            for (Element e : orgs) {
                JSONObject obj = new JSONObject();
 
                String label = e.attr("aria-label");
                obj.put("label", label);
 
                String link = e.attr("href");
                obj.put("link", "https://github.com" + link);
 
                String imgLink = e.children().attr("src");
                obj.put("img_link", imgLink);
 
                String imgAlt = e.children().attr("alt");
                obj.put("img_Alt", imgAlt);
 
                organizations.put(obj);
            }
            githubProfile.put("organizations", organizations);
        }

 

Similarly ‘scrapeGithubOrg’ is used to scrape information related to Github organization.

API usage

The API can be used in the ways shown below.

  • api/githubprofilescraper.json?profile=<profile_name> : This is equivalent to api/githubprofilescraper.json?profile=<profile_name>&terms=all :This will return all the data the scraper is currently capable of collecting.
  • api/githubprofilescraper.json?profile=<profile_name>&terms=<list_containing_required_fields> : This will return the basic information about a profile and data on selective fields as mentioned by the user.

For example – api/githubprofilescraper.json?profile=djmgit&terms=followers,following,organizations

The above request will return data about followers, following, and organizations apart from basic profile information. Thus the scraper will scrape only those data that is required by the user.

Future scope

‘scrapeGithubUser’ is still lengthy and it can be further broken down into smaller methods. The part of the method which scrapes basic user information like name, bio, etc can be moved to a separate method. This will further increase code readability and maintainability.

Now we have a much more functional and enhanced Github profile scraper which provides more data than the previous one.

Continue ReadingEnhancing Github profile scraper service in loklak

Meilix System Lock

Meilix-Systemlock has to two main shell files: lock.sh and unlock.sh. The purpose of the script is that if the lock.sh is called, the content inside the home directory will be reset after rebooting of the system. And it will be in that state of getting back reset to home directory until unlock.sh is being called.

An Example to illustrate

Suppose in a computer lab, the students are given computers and they make changes which don’t seem convenient to the maintainer of the lab. The maintainer freezes the system to have a clean state, and the students use the computer and make it “dirty”, and the reboot restores to the clean state when maintainer running freeze. As soon as the lock.sh is being called system will copy the files in the home directory to another place, then reboot. Reboot and copying don’t occur parallelly.

The system remains in the frozen state and every time when its get rebooted, it gets to the same state of home directory when it is being frozen. This can be stopped by calling unlock.sh. This can be helpful for the maintainer for creating a new freeze point.
I get the idea through chat with Yeo Wei and used the logo from here.

Diagram for clear representation

How does it solve someone’s problem in daily life?

As I explained in the example, it can easily solve the problem of a computer lab assistant at a school, college, public cafe, etc. I request this idea should reach to them, and they can run the lock.sh to freeze the system so as to avoid the dirtiness made by a third person. Maintainer generally left out this dirt for next user who is going to work on the same PC. But this technique will make the PC new and new user doesn’t have to clean the dirt made by the previous user and they can setup their environment freshly.

Understanding the Important Code Mechanism:

In the lock.sh script

Line: 44

echo “sudo rsync -a –delete /etc/.ofris/$ofris_user/ /home/$ofris_user/” >> ofris_tmp

– this line restore the files stored in /etc/.ofris/user to /home/user” into /etc/rc.local (which runs every time computer boots).

Line: 38, 39

if [ $ofris_rst = 1 ]; then 
echo "Error: The system has been locked, please select the fourth choice to unfreeze the system..."

– this line stops the execution of lock.sh if it hasn’t been unlocked yet.

In the unlock.sh script

Line: 1

grep -v "sudo rsync -a --delete /etc/" /etc/rc.local > ofris_tmp_b

– this check if the rc.local is being modified or not.
And rest command removes the created folder and restore the home folder to its original state.

The important repository containing:
https://github.com/fossasia/meilix-systemlock

Continue ReadingMeilix System Lock

Implementing Voice Search In Susper (in Chrome only)

Last week @mariobehling opened up an issue to implement voice search in Susper. Google Chrome provides an API to integrate Speech recognition feature with any website. More about API can be read here: https://shapeshed.com/html5-speech-recognition-api/

The explanation might be in Javascript but it has been written following syntax of Angular 4 and Typescript. So, I created a speech-service including files:

  • speech-service.ts
  • speech-service.spec.ts

Code for speech-service.ts: This is the code which will control the working of voice search.

import { Injectable, NgZone } from ‘@angular/core’;
import { Observable } from ‘rxjs/Rx’;
interface
IWindow extends Window {
  webkitSpeechRecognition: any;
}
@Injectable()
export
class SpeechService {

constructor(private zone: NgZone) { }

record(lang: string): Observable<string> {
  return Observable.create(observe => {
    const { webkitSpeechRecognition }: IWindow = <IWindow>window;

    const recognition = new webkitSpeechRecognition();

    recognition.continuous = true;
    recognition.interimResults = true;
    recognition.onresult = take => this.zone.run(() => observe.next(take.results.item(take.results.length 1).item(0).transcript)
);

    recognition.onerror = err =>observe.error(err);
    recognition.onend = () => observe.complete();
    recognition.lang = lang;
    recognition.start();
});
}
}

You can find more details about API following the link which I have provided above in starting. Here recognition.onend() => observe.complete() works as an important role here. Many developers forget to use it when working on voice search feature. It works like: whenever a user stops speaking, it will automatically understand that voice action has now been completed and the search can be attempted. And for this:

speechRecognition() {
  this.speech.record(‘en_US’).subscribe(voice => this.onquery(voice));
}

We have used speechRecognition() function. onquery() function is called when a query is entered in a search bar.

Default language has been set up as ‘en_US’ i.e English. We also created an interface to link it with the API which Google Chrome provides for adding voice search feature on any website.

I have also used a separate module by name NgZone. Now, what is NgZone? It is used as an injectable service for executing working inside or outside of the Angular zone. I won’t go into detail about this module much here. More about it can be found on angular-docs website.

We have also, implemented a microphone icon on search bar similar to Google. This is how Susper’s homepage looks like now:

This feature only works in Google Chrome browser and for Firefox it doesn’t. So, for Firefox browser there was no need to show ‘microphone’ icon since voice search does not work Firefox. What we did simply use CSS code like this:

@mozdocument urlprefix() {
  .microphone {
    display: none;
  }
}

@-moz-document url-prefix() is used to target elements for Firefox browser only. Hence using, this feature we made it possible to hide microphone icon from Firefox and make it appear in Chrome.

For first time users: To use voice search feature click on the microphone feature which will trigger speechRecognition() function and will ask you permission to allow your laptop/desktop microphone to detect your voice. Once allowing it, we’re done! Now the user can easily, use voice search feature on Susper to search for a random thing.

Continue ReadingImplementing Voice Search In Susper (in Chrome only)

Creating a drop up in Susper

We are accustomed to creating drop-downs in our navigation bars, but sometimes we are faced with the need of creating drop ups.

In Susper, we had to create a drop up for settings for the footer.

This is how it looks:

Let us see the step by step procedure to create the drop up:

  1. Write the required Html content (in Susper it is in the footer-navbar.component.html)
<span class="dropup">

 <a class="dropdown-toggle" data-toggle="dropdown">Settings</a>

 <ul class="dropdown-menu" id="fsett">

   <li><a routerLink="/preferences">Search settings</a></li>

   <li><a routerLink="/advancedsearch">Advanced Search</a></li>

   <li><a routerLink="/crawlstartexpert" routerLinkActive="active">Crawl Job</a></li>

 </ul>

</span>

 

Make sure to link all the listed items in the drop down correctly. Notice that all of this has been put in the parent tag span, with class drop up

 

  1. We need to write the CSS part now:
.dropup{

cursor: pointer;

}

#fsett {

background: #fff;

border: 1px solid #999;

bottom: 30px;

padding: 10px 0;

position: absolute;

box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);

text-align: left;

z-index: 104;

margin-left: -85%;

margin-bottom: -9%;

}

#fsett a {

display: block;

line-height: 44px;

padding: 0 20px;

text-decoration: none;

white-space: nowrap;

}

#fsett a:hover {

text-decoration: underline;

background-color: white;

}

Here are some useful attributes, that can style your drop up:

  • Cursor: It sets your cursor to whatever you like when you move over the drop up. The pointer changes it to the hand symbol, default changes it to the standard arrow and so on…
  • Z-index: can be used to set the height of your elements, it is equal to its parent element by default, setting the z-index of something to more than that will make it have a higher stack order so it will be in the front.
  • Text-decoration: This attribute is used to add/remove decoration like underline for links.
  • Margin and position: Use margin-left and margin-right to set the position of the drop-up, combine it with position: absolute, to give absolute dimensions.
  • Box-shadow: This gives the drop up a shadow effect, which looks really nice. The first 3 parameters are for dimensions (X-offset, Y-offset, Blur). The rgba specifies colour, with parameters as (red-component, green-component, blue-component, opacity).
Continue ReadingCreating a drop up in Susper

Hotword Detection with Pocketsphinx for SUSI.AI

Susi has many apps across all the major platforms. Latest addition to them is the Susi Hardware which allows you to setup Susi on a Hardware Device like Raspberry Pi.

Susi Hardware was able to interact with a push of a button, but it is always cool and convenient to call your assistant anytime rather than using a button.

Hotword Detection helps achieve that. Hotword Detection involves running a process in the background that continuously listens for voice. On noticing an utterance, we need to check whether it contains desired word. Hotword Detection and its integration with Susi AI can be explained using the diagram below:

 

What is PocketSphinx?

PocketSphinx is a lightweight speech recognition engine, specifically tuned for handheld and mobile devices, though it works equally well on the desktop.

PocketSphinx is free and open source software. PocketSphinx has various applications but we utilize its power to detect a keyword (say Hotword) in a verbally spoken phrase.

Official Github Repository: https://github.com/cmusphinx/pocketsphinx

Installing PocketSphinx

We shall be using PocketSphinx with Python. Latest version on it can be installed by

pip install pocketsphinx

If you are using a Raspberry Pi or ARM based other board, Python 3.6 , it will install from sources by the above step since author doesn’t provide a Python Wheel. For that, you may need to install swig additionally.

sudo apt install swig

How to detect Hotword with PocketSphinx?

PocketSphinx can be used in various languages. For Susi Hardware, I am using
Python 3.

Steps:

      • Import PyAudio and PocketSphinx
        from pocketsphinx import *
        import pyaudio
        

         

      • Create a decoder with certain model, we are using en-us model and english us default dictionary. Specify a keyphrase for your application, for Susi AI , we are using “Susi” as Hotword
        pocketsphinx_dir = os.path.dirname(pocketsphinx.__file__)
        model_dir = os.path.join(pocketsphinx_dir, 'model')
        
        config = pocketsphinx.Decoder.default_config()
        config.set_string('-hmm', os.path.join(model_dir, 'en-us'))
        config.set_string('-keyphrase', 'susi')
        config.set_string('-dict', os.path.join(model_dir, dict_name))
        config.set_float('-kws_threshold', self.threshold)

         

      • Start a PyAudio Stream from Microphone Input
        p = pyaudio.PyAudio()
        
        stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=20480)
        stream.start_stream()

         

      • In a forever running loop, read from stream and process buffer in chunks of 1024 frames if it is not empty.
        buf = stream.read(1024)
        if buf:
            decoder.process_raw(buff)
        else:
            break;
        

         

      • Check for hotword and start speech recognition if hotword detected. After returning from the method, start detection again.

        if decoder.hyp() is not None:
            print("Hotword Detected")
            decoder.end_utt()
            start_speech_recognition()
            decoder.start_utt()
        

         

      • Run the app, if detection doesn’t seem to work well, adjust kws_threshold in step 2 to give optimal results.

In this way, Hotword Detection can be added to your Python Project. You may also develop some cool hacks with our AI powered Assistant Susi by Voice Control.
Check repository for more info: https://github.com/fossasia/susi_hardware

Continue ReadingHotword Detection with Pocketsphinx for SUSI.AI

Adding a Notification page using Ember.JS to Open Event Front-end

We have added a notification page for users, where they can have a look at their notifications and manage them separately. Here, we have two buttons for either viewing only the unread or all the notifications. The ‘mark all read’ button, as the name suggests will mark all the notifications as  read and is only present in `/notifications/`.

To create the notification page we have three steps

  • Create parent route for notification
  • Create sub routes for notification/all, notification/index
  • Design the notification page

We’ll be generating parents and sub routes by following three commands

ember generate route notifications
ember generate route notifications/index
ember generate route notifications/all
import Ember from ’ember’;

const { Route } = Ember;

export default Route.extend({
titleToken() {
return this.l10n.t(‘Unread’);
},
templateName: ‘notifications/all’,
model() {
return [{
title       : ‘New Session Proposal for event1 by  user1’,
description : ‘Title of proposal’,
createdAt   : new Date(),
isRead      : false
},
{
title       : ‘New Session Proposal for event3 by  user3’,
description : ‘Title of proposal’,
createdAt   : new Date(),
isRead      : false
},
{
title       : ‘New Session Proposal for event5 by  user5’,
description : ‘Title of proposal’,
createdAt   : new Date(),
isRead      : false
}];
}
});

In the js file we have defined a model, and this model is returned whenever the user navigates to this route ie /notifications.

We have used template name attribute to explicitly define template for this route. As /notifications/ and /notifications/all both have almost the same layout with different data, we have used the same template `notifications/all.hbs` for both of them.

{{#each model as |notification|}}

class=”ui segment {{unless notification.isRead ‘blue’}}”>
div class=”ui grid”>
div class=”row”>
div class=”eight wide column”>
h4 class=”ui header”>{{notification.title}}h4>
p>{{notification.description}}p>
div>
div class=”eight wide column right aligned”>
i class=”checkmark box icon”>i>
div>
div>
div class=”row”>
div class=”ten wide column”>
button class=”ui blue button”>{{t ‘View Session’}}button>
div>
div class=”six wide column right aligned”>
p>{{moment-from-now notification.createdAt}}p>
div>
div>
div>

{{/each}}

In the template we have checked if the notification is read or not and accordingly a blue segment is put.

<div class=“ui segment {{unless notification.isRead ‘blue’}}”>

Continue ReadingAdding a Notification page using Ember.JS to Open Event Front-end

Cross-Browser Issue: Dealing With an UI Problem Arising Due to Floating HTML Elements in Open Event Webapp

A few days back, I came across a rather interesting but hard to debug bug. The weird part was that it presented itself only on particular browsers like Firefox and didn’t exist on other browsers like Chrome.

In Open Event Webapp, we have collapsible session elements on track, room and schedule pages. The uncollapsed element would show you the major details like the title of the session, its type, name and a small thumbnail picture of the speaker. If the user clicks on it, then the element would collapse and more detail would pop up which would include the detailed description of the session and the speaker(s) presenting it.

Now, the problem which was occurring was that the collapsible sessions on the rooms page were behaving erratically. They were collapsing predictably but left behind a huge white space after they wound up on clicking. I have attached some screenshots to better demonstrate the problem. At first, I was confused. Because the same thing worked perfectly on Chrome.

collapsed.png

Collapsed State

uncollapsed.png

On Clicking Again

The actual solution for this problem turned out to be quite short (a one-liner in fact) but it took me a little while to solve it and actually understand what was going on. The solution:-

.room-filter {
 overflow: hidden;
}

Here, room-filter is the class applied to the session element.

Wait? But how did that solved the problem? Read on!!

As I mentioned in the title, the problem arises due to the floating HTML elements. I am just going to give a quick introduction to the float property. In case you want to read more about it, I have provided links at the end of the article which you can go through for a detailed explanation. Float is a CSS positioning property. It tells whether an element would shift to the right or left of its parent container or another floated element while other HTML elements like images, text and inline elements would wrap around it. One popular analogy which is often given is that of a textbook where the text wraps around the images. We can say that the image is floating to the left (or right) while the text wraps around it.

But how is this problem related to the floating elements still remain unclear? Hold on. This is the structure of the session div on the rooms page:

<div class="room-filter" id="{{session_id}}">

<div class="eventtime col-xs-2 col-sm-2 col-md-2">
<!-- Some Content -->
</div>

<div class="room-container">
<div class="left-border col-xs-10 col-sm-10 col-md-10">
<!-- Some Content -->
</div>
</div>

</div>

 

As we can see from the code, we have a session div with two divs inside it. We have applied bootstrap grid classes to them. These bootstrap layout classes actually make the elements float with a specified width (You can check it on the developer console). So, we have two floating div elements inside the parent div. Remember that although the floating elements remain the part of the document, they are taken outside of the normal flow of the page. Since the parent div here contain nothing but the floating elements, its height collapses to zero.

Because of this, Firefox was not able to correctly expand and collapse the session element because it’s actual height was not being computed and was set to zero. Now, if we apply any of the properties which can make the height of the parent element non-zero, we would be able to solve this problem. Chrome somehow managed to escape this issue and worked predictably.

To make the height non-zero, we apply this property:-

.room-filter {
   overflow: hidden;
}

An intuitive reason to explain why this fixes the problem is that when the element is in its default state (overflow: visible), it does not need to calculate its height to display properly. Once we set overflow: hidden, it needs to see whether the max-height or max-width has been surpassed and hence it needs to calculate its dimensions. Once the height is calculated, it is used in layout and the element no longer collapses.

correct.png

Problem solved.

Another alternative solution to solve the problem can be:

.room-filter {
   display: inline-block;
   width: 100%;
}

In case you want to read more about the float property, you can consult these links:

https://css-tricks.com/all-about-floats/

https://www.smashingmagazine.com/2009/10/the-mystery-of-css-float-property/

Continue ReadingCross-Browser Issue: Dealing With an UI Problem Arising Due to Floating HTML Elements in Open Event Webapp

Creating a basic Gallery in Phimpme Android

In the process of optimizing our Phimpme Android photo application, I had to change the implementation of the gallery in the application, as its loading time and smoothness is not satisfactory. How did I implement it?

In any gallery application, the primary necessity is having a details’ list of all available images on the device. For creating such a list, it is not necessary to iterate in all folders of the device storage for images. It is very time consuming task if we perform every time the app opens. Instead we can use database created by Android system for storing details of all available media on the device which can be accessed by any application. If we query the database with proper arguments, we get a cursor pointing to our target, using which we can form a list for our need.

Querying the mediastore for images can be implemented as shown below

Uri uri;
Cursor cursor;
int column_index;
String path = null,sortOrder;
ArrayList<String> imageList = new ArrayList<>();
uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.MediaColumns.DATA }; 
//DATA is the path to the corresponding image. We only need this for loading //image into a recyclerview

sortOrder = MediaStore.Images.ImageColumns.DATE_ADDED + “ DESC”;
//This sorts all images such that recent ones appear first

cursor = getContentResolver().query(uri, projection, null,null, sortOrder);

try {
       if (null != cursor) {
           column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
           while (cursor.moveToNext()) {
                 path = cursor.getString(column_index_data);
                 imageList.add(path);
            }
            cursor.close(); 
//imageList gets populated with paths to images by here
       }
}catch (Exception e){
       e.printStackTrace();
}

As we now have the list of paths to all images, we can proceed with displaying images using image file path. We can use RecyclerView for displaying grid of images as it is more optimized that default GridView. The whole process of displaying images can be summarized in following simple steps.

  1. Add RecyclerView widget to main layout and create a layout for gallery item.
  2. Create an adapter for populating RecyclerView.
  3. Initialize adapter with imageList and attach it to the recyclerView.

Setting Layout: 

Add the below code to the layout resource for your activity(activity_main.xml).

<android.support.v7.widget.RecyclerView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/gallery_grid”/>

Inorder to use RecyclerView in the project, the following dependency for recyclerView has to be added in the build.gradle file

compile "com.android.support:recyclerview-v7:$supportLibVer"
//change supportLibVer to its corresponding value.

 

Now create a layout for item in the gallery. For this, you can create something like below.

gallery_item.xml

<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent">
     <ImageView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:scaleType="centerCrop"
         android:id="@+id/image"/>
 </RelativeLayout>

Setting the Adapter:

Adapter populates the recyclerView with the list of resources given to it. For displaying images in RecyclerView, if we use normal method for setting image i.e imageview.setImageDrawable(xyz); the recyclerView would load extremely slow everytime we open the app. So, we can use an open-source image caching library Glide, for this purpose. It caches the images on its first load and lazy loads images into imageView giving a smooth experience. It can be used only if the following dependency is added to build.gradle.

compile 'com.github.bumptech.glide:glide:4.0.0-RC0'

Its sample implementation is also shown in the adapter code below.

OnItemClickListener() cannot be used with recyclerView directly, like gridView. So, a method for implementing such kind of listener is also shown in below implementation of adapter.

import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.ArrayList;

public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.mViewHolder> {

static ArrayList<String> galleryImageList;
private Context context;

public GalleryAdapter(ArrayList<String> imageList){
     galleryImageList = imageList;
}

public class mViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener {

     ImageView imageView;
     ItemClickListener itemClickListener;

     public mViewHolder(View view) {
         super(view);
         imageView = (ImageView)view.findViewById(R.id.image);
         view.setOnClickListener(this);
         view.setOnLongClickListener(this);
     }

     public void setClickListener(ItemClickListener itemClickListener) {
         this.itemClickListener = itemClickListener;
     }

     @Override
     public void onClick(View v) {
         itemClickListener.onClick(v, getPosition(), false);
     }

     @Override
     public boolean onLongClick(View v) {
         itemClickListener.onClick(v, getPosition(), true);
         return true;
     }
}

@Override
public GalleryAdapter.mViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     context = parent.getContext();
     View view = LayoutInflater.from(context).inflate(R.layout.gallery_item,parent,false);

//this gallery item is the one that we created above.

     DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

     RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(metrics.widthPixels/3,metrics.widthPixels/3);    //3 columns

     view.setLayoutParams(layoutParams);
     return new mViewHolder(view);
}

@Override
public void onBindViewHolder(GalleryAdapter.mViewHolder holder, final int position) {
     Glide.with(context)
             .load(Uri.fromFile(new File(galleryImageList.get(position)))
             //get path from list and covertin to URI
             .override(100,100) //final image will be this size(100x100)
             .thumbnail(0.1f)   //instead of empty placeholder, a                                              //thumbnail of 0.1th size of original image is used as //placeholder before complete loading of image
             .into(holder.imageView);
     holder.setClickListener(new ItemClickListener() {
         @Override
         public void onClick(View view,int position, boolean isLongClick) {
             if (isLongClick)
                 Toast.makeText(context, "Long Clicked " + position, Toast.LENGTH_SHORT).show();

             else
                 Toast.makeText(context, "Clicked " + position , Toast.LENGTH_SHORT).show();
         }
     });
}

@Override
public int getItemCount() {
     return (null != galleryImageList) ? galleryImageList.size() : 0;
}
}

With this, we reached the final step. 

Attaching the adapter to RecyclerView:

A grid layout manager with a required number of columns is made and attached to recyclerView.

The adapter is also initialized with the created Image List and is set to the recyclerView as shown below.

recyclerView = (RecyclerView)findViewById(R.id.gallery_grid);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this,3);
// 3 columns
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setAdapter(new GalleryAdapter(createAndGetImageList()));

createAndGetImageList() is the first method discussed in post.

Continue ReadingCreating a basic Gallery in Phimpme Android

Sharing the Phimpme Android App using Firebase deep-linking

The share feature in the Android application is really important for the growth of your Android application and to increase the user base. In the Phimpme Android application, we have made use of the Firebase deep link to share the application as it is the most recommended way to do it. In this tutorial, I am discussing two ways to share your Android application with the help of code snippets below. The following are:

Generate the APK and share:

Sharing the APK of the application directly can be achieved very easily but it is the least recommended way to do this. To share the APK of the application via bluetooth or other applications, we can make use of the package manager to generate the apk and pass it to the share intent to get things done smoothly. The following code snippet will be used to generate the apk and to pass it on to the share intent.

PackageManager pm = getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(getPackageName(), 0);
File srcFile = new File(ai.publicSourceDir);
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("application/vnd.android.package-archive");
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(srcFile));
startActivity(Intent.createChooser(share, "APK"));

Sharing the application’s apk directly is not the most recommended way to do it as sometimes the apk generated by the PackageManager might not install properly in another device.

Using the Firebase Deep links:

The best thing about this is that it can behave differently when clicked on different devices. For example, you have Phimpme Android application installed in your mobile phone and someone else might send you the invitation link to download it again. So when you click on the deep link, rather than opening play store or any other storage from where you can download the apk, it will open up the Phimpme Application directly.

Due to these advantages, in the Phimpme Application, we have made use of this service to share the application. In this tutorial, I will discuss on how to achieve this step by step:

Step 1:

Sign in to your google account and go the Firebase console then click on the Add project button and fill in the Project name and your country and press Create Project button to proceed.

Step 2:

Click on Add firebase to your Android application button and fill in the your Android application’s package name and the SHA-1 key. You can generate this key very easily with the help of Android studio.

For this, import and open your Android application in Android studio and then press the Gradle button at the right hand side of your screen. It will open up the screen like that mentioned in the screenshot below:

Go to app>android> and double click on the signingReport and wait for the build to complete. On successful completion of the build, the SHA-1 key will be displayed on the Gradle console below.

Step 3:

Click on the register application button below to proceed. It will provide an option to download googleServices.json file but you can skip that as it is not necessary for sharing the application.

Step 4:

Click on the Dynamic links section and add create a new dynamic link. Fill in the link to locate your application and select the behaviour of how you want the link to behave on different devices. For example, in Android if the application is installed I want to directly open the application so I have made the configuration as seen from the screenshot below:

After doing this, click on the option to Create dynamic link from below and copy the generated link.

Step 5:

After generating the URL, you can simply create a share button in your android application and can share the application with the help of code snippet provided below:

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.install_phimpme) + "\n "+ getString(R.string.invitation_deep_link));
sendIntent.setType("text/plain");
startActivity(sendIntent);

Now go to your String.xml and create a new String resource with name invitation_deep_link and copy the dynamic link you generated using the steps mentioned above.

Resources:

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

https://console.firebase.google.com/

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

Continue ReadingSharing the Phimpme Android App using Firebase deep-linking

Designing Control UI of PSLab Android using Moqups

Mockups are an essential part of app development cycle. With numerous mock-up tools available for android apps (both offline and online), choosing the right mock-up tool becomes quite essential. The developers need a tool that supports the latest features like drag & drop elements, support collaboration upto some extent and allow easy sharing of mockups. So, Moqups was chosen as the mockups tool for the PSLab Android team.

Like other mock-up tools available in the market, using moqups is quite simple and it’s neat & simple user interface makes the job easier. This blog discusses some of the important aspects that need to be taken care of while designing mockups.

A typical online mock-up tool would look like this having a palette to drag & drop UI elements like Buttons, Text boxes, Check boxes etc. Additionally a palette to modify the features of each element ( here on the right ) and other options at the top related to prototyping, previewing etc.

    • The foremost challenge while designing any mock-up is to keep the design neat and simple such that even a layman doesn’t face problems while using it. A simple UI is always appealing and the current trend of UIs is creating flat & crisp UIs.

    • For example, the above mock-up design has numerous advantages for both a user and also as a programmer. There are seek bars as well as text boxes to input the values along with the feature of displaying the value that actually gets implemented and it’s much simpler to use. From the developer’s perspective, presence of seven identical views allows code reuse. A simple layout can be designed for one functionality and since all of them are identical, the layout can be reused in a Recyclerview.
    • The above design is a portion of the Control UI which displays the functionalities for  using PSLab as a function generator and as a voltage/current source.

    • The other section of the UI is of the Read portion. This has the functionalities to measure various parameters like voltage, resistance, capacitance, frequency and counting pulses. Here, drop-down boxes have been provided at places where channel selection is required. Since voltages are most commonly measured values in any experiment, voltages of all the channels have been displayed simultaneously.
    • Attempts should always be made to keep the smaller views as identical as possible since it becomes easier for the developer to implement it and also for the user to understand.

 

The Control UI has an Advanced Section which has features like Waveform Generators allows to generate sine/square waves of a given frequency & phase, Configuring Pulse Width Modulation (PWM)  and selecting the Digital output channel. Since, the use of such features are limited to higher level experiments, they have been separately placed in the Advanced section.

Even here drop-down boxes, text boxes & check boxes have been used to make UI look interactive.

The common dilemma faced while writing the XML file is regarding the view type to be chosen as Android provides a lot of them like LinearLayout, ConstraintLayout, ScrollView, RecyclerView, ListView etc. So, although there are several possible ways of designing a view. Certain things like using ListView or RecyclerView where there is repetition of elements is easier and when the elements are quite distinct from each other, it is better to stick to LinearLayout and ConstraintLayout.

Continue ReadingDesigning Control UI of PSLab Android using Moqups