Search Functionalities in SUSI Android App Using Android SearchView Widget

Searching is a common feature that is required in most applications. But the problem in implementing searching functionality is that there is no common way to do that. People fight over whose way is best to implement search functionality. In this blog post we’ll be looking at how search functionality works in SUSI Android App and how is it implemented. We have used Android’s SearchView widget to do that. There are many other ways to do so but this one is best suited for our requirements. Let’s see how it works.

UI Components used for Searching

1. Search icon (magnifying glass icon)

In the action bar, you can see a small icon. Clicking on the icon initiates search.

2. Edit text

An Obvious requirement is an edit test to enter search query.

3. Up and Down arrow keys

Required to search through the whole app. Simply use the up and down arrow keys to navigate through the app and find out each occurrence of the word you want to search.

 

 

 

 

 

 

 

4. Cross Button

Last but not the least, a close or cross button to close the search action.

Implementation

We have used Android’s inbuilt Widget SearchView. According to official android documentation

A widget that provides a user interface for the user to enter a search query and submit a request to a search provider. Shows a list of query suggestions or results, if available, and allows the user to pick a suggestion or result to launch into.

This widget makes searching a lot easier. It provides all methods and listeners which are actually required for searching. Let’s cover them one by one.

  1. Starting the search: searchView.setOnSearchClickListener Listener simply activates when a user clicks on search icon in the toolbar. Do all your work which needs to be done at the starting of the search like, hiding some other UI elements of doing an animation inside the listener
searchView.setOnSearchClickListener({
   chatPresenter.startSearch()
})
  1. Stop the Search: searchView.setOnCloseListener Listener gets activated when a user clicks on the cross icon to close the search. Add all the code snippet you want which is needed to be executed when the search is closed inside this like maybe notify the adapter about data set changes or closing the database etc.
searchView.setOnCloseListener({
   chatPresenter.stopSearch()
   false
})
  1.  Searching a query:  searchView.setOnQueryTextListener Listener overrides 2 methods:

3.1 onQueryTextSubmit: As the name suggests, this method is called when the query to be searched is submitted.

3.2 onQueryTextChange: This method is called when query you are writing changes.

We, basically wanted same thing to happen if user has submitted the query or if he is still typing and that is to take the query at that particular moment, find it in database and highlight it. So, chatPresenter.onSearchQuerySearched(query) this method is called in both onQueryTextSubmit and onQueryTextSubmit  to do that.

 searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
 
      override fun onQueryTextSubmit(query: String): Boolean {
           //Handle Search Query
           chatPresenter.onSearchQuerySearched(query)
           recyclerAdapter.query = query
           return false
       }

       override fun onQueryTextChange(newText: String): Boolean {
           if (TextUtils.isEmpty(newText)) {
               modifyMenu(false)
               recyclerAdapter.highlightMessagePosition = -1
               recyclerAdapter.notifyDataSetChanged()
               if (!editText.isFocused) {
                   editText.requestFocus()
               }
           } else {
               chatPresenter.onSearchQuerySearched(newText)
               recyclerAdapter.query = newText
           }
           return false
       }
   })
   return true
}
  1. Finding query in database: Now we have a query to be searched, we can just use a database operation to do that. The below code snippet finds all the messages which has the query present in it and work on it. If the query is not found, it simply displays a toast saying “Not found”
override fun onSearchQuerySearched(query: String) {
   chatView?.displaySearchElements(true)
   results = databaseRepository.getSearchResults(query)
   offset = 1
   if (results.size > 0) {
       chatView?.modifyMenu(true)
       chatView?.searchMovement(results[results.size - offset].id.toInt())
   } else {
       chatView?.showToast(utilModel.getString(R.string.not_found))
   }
}

This is the database operation.

override fun getSearchResults(query: String): RealmResults<ChatMessage> {
   return realm.where(ChatMessage::class.java).contains(Constant.CONTENT,
           query, Case.INSENSITIVE).findAll()
}

  1. Highlighting the part of message: Now, we know which message has the query, we just want to highlight it with a bright color to display the result. For that, we used SpannableString to highlight a part of complete string.
String text = chatTextView.getText().toString();
SpannableString modify = new SpannableString(text);
Pattern pattern = Pattern.compile(query, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(modify);
while (matcher.find()) {
   int startIndex = matcher.start();
   int endIndex = matcher.end();
   modify.setSpan(new BackgroundColorSpan(Color.parseColor("#ffff00")), startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
chatTextView.setText(modify);

Summary

The whole point of this blog post was to educate about SearchView widget of android and how it makes it easy to search queries. All the methods you need are already implemented. You just need to call them and add database operation.

Resources

  1. The link to official android documentation explaining about different methods in SearchView Class https://developer.android.com/reference/android/widget/SearchView.html
  2. Another tutorial about SearchView http://www.journaldev.com/12478/android-searchview-example-tutorial
Continue ReadingSearch Functionalities in SUSI Android App Using Android SearchView Widget

Integrating Stock Sensors with PSLab Android App

A sensor is a digital device (almost all the time an integrated circuit) which can receive data from outer environment and produce an electric signal proportional to that. This signal will be then processed by a microcontroller or a processor to provide useful functionalities. A mobile device running Android operating system usually has a few sensors built into it. The main purpose of these sensors is to provide user with better experience such as rotating the screen as he moves the device or turn off the screen when he is making a call to prevent unwanted screen touch events. PSLab Android application is capable of processing inputs received by different sensors plugged into it using the PSLab device and produce useful results. Developers are currently planning on integrating the stock sensors with the PSLab device so that the application can be used without the PSLab device.

This blog is about how to initiate a stock sensor available in the Android device and get readings from it. Sensor API provided by Google developers is really helpful in achieving this task. The process is consist of several steps. It is also important to note the fact that there are devices that support only a few sensors while some devices will support a lot of sensors. There are few basic sensors that are available in every device such as

  • “Accelerometer” – Measures acceleration along X, Y and Z axis
  • “Gyroscope” – Measures device rotation along X, Y and Z axis
  • “Light Sensor” – Measures illumination in Lux
  • “Proximity Sensor” – Measures distance to an obstacle from sensor

The implementing steps are as follows;

  1. Check availability of sensors

First step is to invoke the SensorManager from Android system services. This class has a method to list all the available sensors in the device.

SensorManager sensorManager;
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

Once the list is populated, we can iterate through this to find out if the required sensors are available and obstruct displaying activities related to sensors that are not supported by the device.

for (Sensor sensor : sensors) {
   switch (sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
           break;
       case Sensor.TYPE_GYROSCOPE:
           break;
       ...
   }
}

  1. Read data from sensors

To read data sent from the sensor, one should implement the SensorEventListener interface. Under this interface, there are two method needs to be overridden.

public class StockSensors extends AppCompatActivity implements SensorEventListener {

    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {

    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

    }
}

Out of these two methods, onSensorChanged() method should be addressed. This method provides a parameter SensorEvent which supports a method call getType() which returns an integer value representing the type of sensor produced the event.

@Override
public void onSensorChanged(SensorEvent sensorEvent) {
   switch (sensorEvent.sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
           break;
       case Sensor.TYPE_GYROSCOPE:
           break;
       ...
   }
}

Each available sensor should be registered under the SensorEventListener to make them available in onSensorChanged() method. The following code block illustrates how to modify the previous code to register each sensor easily with the listener.

for (Sensor sensor : sensors) {
   switch (sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
           sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
           break;
       case Sensor.TYPE_GYROSCOPE:
           sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_UI);
           break;
   }
}

Depending on the readings we can provide user with numerical data or graphical data using graphs plotted using MPAndroidChart in PSLab Android application.

The following images illustrate how a similar implementation is available in Science Journal application developed by Google.

Resources

Continue ReadingIntegrating Stock Sensors with PSLab Android App

Controlling Camera Actions Using Voice Interaction in Phimpme Android

In this blog, I will explain how I implemented Google voice actions to control camera features on the Phimpme Android project. I will cover the following features I have implemented on the Phimpme project:

  • Opening the application using Google Voice command.
  • Switching between the cameras.
  • Clicking a Picture and saving it through voice command.

Opening application when the user gives a command to Google Now.                       When the user gives command “Take a selfie” or “Click a picture” to Google Now it directly opens Phimpme camera activity.

 First                                                                                                                                        We need to add an intent filter to the manifest file so that Google Now can  detect Phimpme camera activity

<activity
   android:name=".opencamera.Camera.CameraActivity"
   android:screenOrientation="portrait"
   android:theme="@style/Theme.AppCompat.NoActionBar">
   <intent-filter>
       <action android:name="android.media.action.IMAGE_CAPTURE"/>

       <category android:name="android.intent.category.DEFAULT"/>
       <category android:name="android.intent.category.VOICE"/>
   </intent-filter>
</activity>

category android:name=”android.intent.category.VOICE” is added to the IMAGE_CAPTURE intent filter for the Google Now to detect the camera activity. For the Google Now assistance to accept the command in the camera activity we need to add the following in the STILL_IMAGE_CAMERA intent filter in the camera activity.

<intent-filter>
   <action android:name="android.media.action.STILL_IMAGE_CAMERA"/>

   <category android:name="android.intent.category.DEFAULT"/>
   <category android:name="android.intent.category.VOICE"/>
</intent-filter>

So, now when the user says “OK Google” and “Take a Picture” the camera activity on Phimpme opens.

Integrating Google Voice assistance in Camera Activity

Second,                                                                                                                               After opening the camera application the Google Assistance should ask a question.

The cameraActivity in Phimpme can be opened in two ways, such as:

  • When opened from a different application.
  • When given the command to Goole Now assistance.

We need to check whether the camera activity is prompted from Google assistance or not to activate voice command. We will check it in onResume function.

@Override
public void onResume() {
if (CameraActivity.this.isVoiceInteraction()) {
     startVoiceTrigger();
  }
} 

If is.VoiceInteraction gives “true” then voice Assistance prompts.             Assistance to ask which camera to use

Third,                                                                                                                                 After the camera activity opens the Google assistance should ask which camera to use either front or back.

To take any voice input from the user, we to store the expected commands in VoiceInteractor.PickoptionRequest. This function listens to the command by the user. We need to add synonyms for the same command.

To choose the rear camera

VoiceInteractor.PickOptionRequest.Option rear = new VoiceInteractor.PickOptionRequest.Option(getResources().getString(R.string.camera_rear), 0);
rear.addSynonym(getResources().getString(R.string.rear));
rear.addSynonym(getResources().getString(R.string.back));
rear.addSynonym(getResources().getString(R.string.normal)); 

I added synonyms like the rear, normal and back.

To choose front camera

VoiceInteractor.PickOptionRequest.Option front = new VoiceInteractor.PickOptionRequest.Option(getResources().getString(R.string.camera_front), 1);
front.addSynonym(getResources().getString(R.string.front));
front.addSynonym(getResources().getString(R.string.selfie_camera));
front.addSynonym(getResources().getString(R.string.forward));

I added synonyms like the front, selfie camera and forward. 

For assistance to ask any question such as “Which camera to use we” I have used getVoiceinteractor and inflating VoiceInteractor.PickOptionRequest.option[] array with options front and rear.

CameraActivity.this.getVoiceInteractor()
     .submitRequest(new VoiceInteractor.PickOptionRequest(
           new VoiceInteractor.Prompt(getResources().getString(“Which camera would you like to use?”),
           new VoiceInteractor.PickOptionRequest.Option[]{front, rear},
           null) {

The google assistance waits for a response from the user for only a few seconds and it goes inactive. If the user gives any unexpected command the assistance will ask the question one more time.

Check if the user gives an expected command or not.

We will override OnOptionResult(boolean finished, Options[] selection, Bundle result) function.if  (finished && selections.length == 1) if the speech length matches with any of the options provided it checks which option was used.

Check the command given by the user to switch between the cameras.

Two array objects are passed 0 and 1.  If the command given was “rear” then selection[0].getindex() = 0 and camera activity switches to the rear camera and if the the command given by the user is rear then selection[0].getIndex = 1 and camera activity switches to front camera.

@Override
public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
  if (finished && selections.length == 1) {
     Message message = Message.obtain();
     message.obj = result;
     if (selections[0].getIndex() == 0)
     {  rearCamera();
        asktakePicture();
     }
     if (selections[0].getIndex() == 1)
     {
        asktakePicture();
     }
  }else{

       getActivity().finish();
  }

Click Picture when the user says “Cheese

After switching the camera the assistant prompts the message”Say cheese”. We need to add voiceInteractor.prompt(“Say cheese”).

We need to store the synonyms in VoiceInteractor.PickOption.Options options. I have added synonyms like ready, go, take it, OK, and Cheese to click a picture. If the user gives an unexpected output the assistance checks selection.length ==1 or not and prompts the message “Say cheese” again.

private void asktakePicture() {
  VoiceInteractor.PickOptionRequest.Option option = new VoiceInteractor.PickOptionRequest.Option(getResources().getString(R.string.cheese), 2);
  option.addSynonym(getResources().getString(R.string.ready));
  option.addSynonym(getResources().getString(R.string.go));
  option.addSynonym(getResources().getString(R.string.take));
  option.addSynonym(getResources().getString(R.string.ok));
getVoiceInteractor()
        .submitRequest(new VoiceInteractor.PickOptionRequest(
              new VoiceInteractor.Prompt(getResources().getString(R.string.say_cheese)),
              new VoiceInteractor.PickOptionRequest.Option[]{option},
              null) {
           @Override
           public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
              if (finished && selections.length == 1) {
                 Message message = Message.obtain();
                 message.obj = result;
                 takePicture();
              } else {
                 getActivity().finish();
              }
           }
           @Override
           public void onCancel() {
              getActivity().finish();
           }
        });                                                                                                                                     

Conclusion

Now, Users can start camera activity on Phimpme through voice command “Take a Selfie”. They can switch between the cameras through voice command “Selfie camera” or “back camera”, “back” or “front” and finally click a picture by giving voice command “Cheese”, “Click it” and related synonyms.

Github

Resources

Continue ReadingControlling Camera Actions Using Voice Interaction in Phimpme Android

Generating the Mozilla All Hands Open Event Android App

The main aim of FOSSASIA Open Event Android App is to give an event organiser the ability to generate the app through a single click by providing the necessary json and binary files. The app was tested on the Mozilla All Hands 2017 event which is an annual conference held for Mozilla employees to showcase their experience and also interact with each other.  The sample files for the event can be found here. (https://github.com/fossasia/open-event/tree/master/sample/MozillaAllHands17). The data for the event was taken from here. (https://sanfranciscoallhandsjune2017.sched.com/).

What was required for building the sample for Mozilla All Hands 2017 event?

  • images folder containing the necessary images of speaker having support for local images too, the logo of the event, the background image of the event.
  • event json file which has all the event specific information like the name of the event, the schedule of the event, the description of the event etc.
  • forms json file having session and speaker form data.
  • meta json file having the root url of the event.
  • microlocations json file having all the locations where the sessions are going to happen.
  • session_types json file consisting data of all the type of session which will occur in the event (the length of session, the name of type and the id).
  • sessions json file consisting session specific data like the title of the session, start time and end time of session, which track that session belongs to etc.
  • speakers json file consisting of speaker specific data like the name of the speaker, image of the speaker, social links of the speaker etc.
  • sponsors json file consisting list of all sponsors of the event.
  • tracks json file consisting of tracks specific data.
  • config.json file which consists of the api url, app name.

After generating the required zip containing the above json files, the zip is uploaded to this site (http://droidgen.eventyay.com/) and we get an apk after filling the required information. This means all the event organiser is able to generate the sample app by providing the necessary information about the event.

The sample can be found here:

Folder Link: https://github.com/fossasia/open-event/tree/master/sample/MozillaAllHands17

Zip Link: https://github.com/fossasia/open-event/tree/master/sample/MozillaAllHands17.zip

How did the Mozilla All Hands 2017 sample app look like?

The screenshots of the sample apk can be seen below:

 

 

What were the issues found in the sample app?

As compared to the previous sample apk like Google IO Open Event Android App some issues like support for local speaker images, background issue of the logo were solved. However there were certain issues which we observed on testing the app with the Mozilla All Hands 2017 event:

  1. The theme of the app remains the same no matter which event it is. It is important to give the event organiser the ability to customise the theme of the app.
  2. Certain information in the app like the event information is hard-coded and needs to be taken from the assets folder instead of strings.xml.

Related Links

Continue ReadingGenerating the Mozilla All Hands Open Event Android App

Using Jackson Library in Open Event Android App for JSON Parsing

Jackson library is a popular library to map Java objects to JSON and vice-versa and has a better parsing speed as compared to other popular parsing libraries like GSON, JSONP etc. This blog post gives a basic explanation on how we utilised the Jackson library in the Open Event Android App.

To use the Jackson library we need to add the dependency for it in the app/build.gradle file.

compile 'com.squareup.retrofit2:converter-jackson:2.2.0'

After updating the app/build.gradle file with the code above we were able to use Jackson library in our project.

Example of Jackson Library JSON Parsing

To explain how the mapping is done let us take an example. The file given below is the sponsors.json file from the android app.

[
    “description”: “”,
    “id”: 1,
    “level”: 3,
    “name”:  KI Group,
    “type”: Gold,
    “url: “”,
    “logo-url”: “” 
]

The sponsors.json consists of mainly 7 attributes namely description, id, level, name, type, url and logo url for describing one sponsor of the event. The Sponsors.java file for converting the json response to Java POJO objects was done as follows utilizing the Jackson library:

public class Sponsors extends RealmObject {
    @JsonProperty(“id”)
    private int id;

    @JsonProperty(“type”)
    private String type;

    @JsonProperty(“description”)
    private String description;

    @JsonProperty(“level”)
    private String level;

    @JsonProperty(“name”)
    private String name;

    @JsonProperty(“url”)
    private String url;

    @JsonProperty(“logo-url”)
    private String logoUrl;
}

As we can see from the above code snippet, the JSON response is converted to Java POJO objects simply by using the annotation “@JsonProperty(“”)” which does the work for us.

Another example which makes this library amazing are the setter and getter annotations which help us use a single variable for two different json attributes if need be. We faced this situation when we were moving from the old api to the new json api. In that case we wanted support for both, old and new json attributes. In that case we simply used the following code snippet which made our transition to new api easier.

public class Sponsors extends RealmObject {
    private int id;
    private String type;
    private String description;
    private String level;
    private String name;
    private String url;
    private String logoUrl;

    @JsonSetter(“logo”)
    public void setLogo(String logoUrl) {
        this.logoUrl = logoUrl;
    }

    @JsonSetter(“logo-url”)
    public void setLogo(String logoUrl) {
        this.logoUrl = logoUrl;
    }
}

As we can see the setter annotations allow easy naming of variable to multiple attributes if need be thus making the code easily adaptable with less overload.

Related Links:

Continue ReadingUsing Jackson Library in Open Event Android App for JSON Parsing

Using Flask SocketIO Library in the Apk Generator of the Open Event Android App

Recently Flask SocketIO library was used in the apk generator of the Open Event Android App as it gave access to the low latency bi-directional communications between the client and the server side. The client side of the apk generator was written in Javascript which helped us to use a SocketIO official client library to establish a permanent connection to the server.

The main purpose of using the library was to display logs to the user when the app generation process goes on. This gives the user an additional help to check what is the mistake in the file uploaded by them in case an error occurs. Here the library established a connection between the server and the client so that during the app generation process the server would send real time logs to the client so that they can be viewed by the user displayed in the frontend.

To use this library we first need to download it using pip command:

pip install flask-socketio

This library depends on the asynchronous services which can be selected amongst the following listed below:

  1. Eventlet
  2. Gevent
  3. Flask development server based on Werkzeug

Amongst the three listed, eventlet is the best performant option as it has support for long polling and WebSocket transports.

The next thing was importing this library in the flask application i.e the apk generator of the app as follows:

from flask_socketio import SocketIO

current_app = create_app()
socketio = SocketIO(current_app)

if __name__ == '__main__':
    socketio.run(current_app)

The main use of the above statement (socket.run(current_app)) is that with this the startup of the web server is encapsulated. When the application is run in debug mode it is preferred to use the Werkzeug development server. However in production mode the use of eventlet web server or gevent web server is recommended.

We wanted to show the status messages currently shown to the user in the form of logs. For this firstly the generator.py file was looked upon which had the status messages and these were sent to the client side of the generator by establishing a connection between them using this library. The code written on the server side to send messages to the client side was as follows:

def handle_message(message):
    if message not None:
        namespace = “/” + self.identifier;
        send(message, namespace = namespace)

As we can see from the above code the message had to be sent to that particular namespace. The main idea of using namespace was that if there were more than one users using the generator at the same time, it would mean the send() method would send the messages to all the clients connected which would lead to the mixing up of status messages and hence it was important to use namespace so that a message was sent to that particular client.

Once the server sent the messages to the client we needed to add functionality to the our client side to receive the messages from them and also update the logs area with suitable content received. For that first the socket connection was established once the generate button was clicked which generated the identifier for us which had to be used as the namespace for that process.

socket = io.connect(location.protocol + "//" + location.host + "/" + identifier, {reconnection: false});

This piece of code established the connection of the socket and helps the client side to receive and send messages from and to the server end.

Next thing was receiving messages from the server. For this the following code snippet was added:

socket.on('message', function(message) {
    $buildLog.append(message);
});

This piece of code receives the messages from the server for unnamed events. Once the connection is established and the messages are received, the logs are updated with each message being appended so as to show the user the real time information about their build status of their app.

This was how the idea of logs being shown in the apk generator was incorporated by making the required changes in the server and client side code using the Flask SocketIO library.

Related Links:

Continue ReadingUsing Flask SocketIO Library in the Apk Generator of the Open Event Android App

Writing Tests for ISO8601Date.java class of the Open Event Android App

ISO8601Date.java class of Open Event Android App was a util class written to perform the date manipulation functions and ensure the code base got more simpler and deterministic. However it was equally important to test the result from this util class so as to ensure the result returned by it was what we wanted. A test class named “DateTest.java” was written to ensure all the edge cases of conversion of the dates string from one timezone to another timezone were handled properly.

For writing unit tests, first we needed to add these libraries as dependencies in the app’s top level build.gradle file as shown below:

dependencies {
  testCompile 'junit:junit:4.12'
}

Then a JUnit 4 Test class, which was a Java class containing the required test methods was created. The structure of the class looked like this:

public class DateTest {
   @Test
   public void methodName() {
   }
}

Next step was including all the required methods which ensured the util class returned the correct results according to our needs. Various edge cases were taken into account by including functions like converting of date string from local time zone to specified timezone, from international timezone to local timezone, from local timezone to international timezone and many more. Some of the methods which were added in the class are shown below:

  • Test of conversion from local timezone to specified timezone:

This function aimed at ensuring the util class worked well with date conversion from local time zone to a specified timezone. An example as shown below was taken where the conversion of the date string was tested from UTC timezone to Singapore timezone.

@Test
public void shouldConvertLocalTimeZoneDateStringToSpecifiedTimeZoneDateString() {
    ISO8601Date.setTimeZone("UTC");
    ISO8601Date.setEventTimeZone("Asia/Singapore");

    String dateString1 = "2017-06-02T07:59:10Z";
    String actualString = ISO8601Date.getTimeZoneDateStringFromString(dateString1);
    String expectedString = "Thu, 01 Jun 2017, 23:59, UTC";
    Assert.assertEquals(expectedString, actualString);
}
  • Test of conversion from local timezone to international timezone:

This function aimed at ensuring the util class worked well with the date conversion from local timezone to international timezone. An example as shown below was taken where the conversion of the date string was tested from Amsterdam timezone to Singapore timezone.

@Test
public void shouldConvertLocalTimeZoneDateStringToInternationalTimeZoneDateString() {
    ISO8601Date.setTimeZone("Europe/Amsterdam");
    ISO8601Date.setEventTimeZone("Asia/Singapore");

    String dateString = "2017-06-02T02:29:10Z";
    String actualString = ISO8601Date.getTimeZoneDateStringFromString(dateString);
    String expectedString = "Thu, 01 Jun 2017, 20:29, GMT+02:00";
    Assert.assertEquals(expectedString, actualString);
}


Above were some functions added to ensure the conversion of a date string from one timezone to another was correct and thus ensured the util class was working properly and returned the results as required.

The last thing left was running the test to check the results the util class returned. For this we had to do two things:

  1. Sync the project with Gradle.
  2. Run the test by right clicking on the class and selecting “Run” option.

Through this we were able to run the test and check the output of the util class on different cases through the results which could be seen on the Android Monitor in the Android Studio.

Related Links:

  1. This link is about building effective unit tests in android. (https://developer.android.com/training/testing/unit-testing/index.html)
  2. This link is about the unit testing on date processing. (https://stackoverflow.com/questions/565289/unit-testing-code-that-does-date-processing-based-on-todays-date)
Continue ReadingWriting Tests for ISO8601Date.java class of the Open Event Android App

Expandable ListView In PSLab Android App

In the PSLab Android App, we show a list of experiments for the user to perform or refer to while performing an experiment, using PSLab hardware device. A long list of experiments need to be subdivided into topics like Electronics, Electrical, School Level, Physics, etc. In turn, each category like Electronics, Electrical, etc can have a sub-list of experiments like:

  • Electronics
    • Diode I-V characteristics
    • Zener I-V characteristics
    • Transistor related experiments
  • Electrical
    • Transients RLC
    • Bode Plots
    • Ohm’s Law

This list can continue in similar fashion for other categories as well. We had to  display  this experiment list to the users with a good UX, and ExpandableListView seemed the most appropriate option.

ExpandableListView is a two-level listView. In the Group view an individual item can be expanded to show it’s children. The Items associated with ExpandableListView come from ExpandableListAdapter.

 

 

 

 

 

 

 

 

Implementation of Experiments List Using ExpandableListView

First, the ExpandableListView was declared in the xml layout file inside some container like LinearLayout/RelativeLayout.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">
   <ExpandableListView
       android:id="@+id/saved_experiments_elv"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:divider="@color/colorPrimaryDark"
       android:dividerHeight="2dp" />
</LinearLayout>

Then we populated the data onto the ExpandableListView, by making an adapter for ExpandableListView by extending BaseExpandableListAdapter and implementing its methods. We then passed a Context, List<String> and Map<String,List<String>> to the Adapter constructor.

Context: for inflating the layout

List<String>: contains titles of unexpanded list

Map<String,List<String>>: contains sub-list mapped with title string

public SavedExperimentAdapter(Context context,
                                 List<String> experimentGroupHeader,
                                 HashMap<String, List<String>> experimentList) {
       this.context = context;
       this.experimentHeader = experimentGroupHeader;
       this.experimentList = experimentList;
   }

In getGroupView() method, we inflate, set title and return group view i.e the main list that we see on clicking and the  sub-list is expanded. You can define your own layout in xml and inflate it. For PSLab Android, we used the default one provided by Android

 android.R.layout.simple_expandable_list_item_2
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
   String headerTitle = (String) getGroup(groupPosition);
   if (convertView == null) {
       LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       convertView = inflater.inflate(android.R.layout.simple_expandable_list_item_2, null);
   }
   TextView tvExperimentListHeader = (TextView) convertView.findViewById(android.R.id.text1);
   tvExperimentListHeader.setTypeface(null, Typeface.BOLD);
   tvExperimentListHeader.setText(headerTitle);
   TextView tvTemp = (TextView) convertView.findViewById(android.R.id.text2);
   tvTemp.setText(experimentDescription.get(groupPosition));
   return convertView;
}

Similarly, in getChildView() method, we inflate, set data and return child view. We wanted simple TextView as sub-list item thus inflated the layout containing only TextView and setText by taking reference of textView from the inflated view.

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
   String experimentName = (String) getChild(groupPosition, childPosition);
   if (convertView == null) {
       LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       convertView = inflater.inflate(R.layout.experiment_list_item, null);
   }
   TextView tvExperimentTitle = (TextView) convertView.findViewById(R.id.exp_list_item);
   tvExperimentTitle.setText(experimentName);
   return convertView;
}

The complete code for the Adapter can be seen here.

After creating the adapter we proceeded similarly to the normal ListView. Take the reference for ExpandableListView by findViewById() or BindView if you are using ButterKnife and set the adapter as an instance of adapter created above.

@BindView(R.id.saved_experiments_elv)
ExpandableListView experimentExpandableList;
experimentAdapter = new SavedExperimentAdapter(context, headerList, map);
experimentExpandableList.setAdapter(experimentAdapter);
Source: PSLab Android

Roadmap

We are planning to divide the experiment sub-list into categories like

  • Electronics
    • Diode
      • Diode I-V
      • Zener I-V
      • Diode Clamping
      • Diode Clipping
    • BJT and FET
      • Transistor CB (Common Base)
      • Transistor CE (Common Emitter)
      • Transistor Amplifier
      • N-FET output characteristic
    • Op-Amps
  • Electrical

This is a bit more complex than it looks, I tried using an ExpandableListView as a child for a group item but ran into some errors. I will write a post as soon as this view hierarchy has been achieved.

Resources

Continue ReadingExpandable ListView In PSLab Android App

Share Images on Pinterest from Phimpme Android Application

After successfully establishing Pinterest authentication in Phimpme our next goal was to share the image on the Pinterest website directly from Phimpme, without using any native Android application.

Adding Pinterest Sharing option in Sharing Activity in Phimpme

To add various sharing options in Sharing Activity in the Phimpme project, I have applied a ScrollView for the list of the different sharing options which include: Facebook, Twitter, Pinterest, Imgur, Flickr and Instagram. All the App icons with the name are arranged in a TableLayout in the activity_share.xml file. Table rows consist of two columns. In this way, it is easier to add more app icons for future development.

<ScrollView
   android:layout_width="wrap_content"
   android:layout_height="@dimen/scroll_view_height"
   android:layout_above="@+id/share_done"
   android:id="@+id/bottom_view">
   <LinearLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <TableLayout

Adding Pinterest app icon on the icons_drawable array. This array is then used to inflate the icon on the list view.

private int[] icons_drawables = {R.drawable.ic_facebook_black, R.drawable.ic_twitter_black,R.drawable.ic_instagram_black, R.drawable.ic_wordpress_black, R.drawable.ic_pinterest_black);

Adding Pinterest text on the titles_text array. This array is then used to inflate the names of the various sharing activity.

private int[] titles_text = {R.string.facebook, R.string.twitter, R.string.instagram,
       R.string.wordpress, R.string.pinterest);

Prerequisites to share Image on Pinterest

To share an Image on Pinterest a user has to add a caption and Board ID. Our first milestone was to get the input of the Board ID  by the user. I have achieved this by taking the input in a Dialog Box. When the user clicks on the Pinterest option, a dialog box pops and then the user can add their Board ID.

private void openPinterestDialogBox() {
   AlertDialog.Builder captionDialogBuilder = new AlertDialog.Builder(SharingActivity.this, getDialogStyle());
   final EditText captionEditText = getCaptionDialog(this, captionDialogBuilder);

   captionEditText.setHint(R.string.hint_boardID);

   captionDialogBuilder.setNegativeButton(getString(R.string.cancel).toUpperCase(), null);
   captionDialogBuilder.setPositiveButton(getString(R.string.post_action).toUpperCase(), new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           //This should be empty it will be overwrite later
           //to avoid dismiss of the dialog on the wrong password
       }
   });

   final AlertDialog passwordDialog = captionDialogBuilder.create();
   passwordDialog.show();

   passwordDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
           String captionText = captionEditText.getText().toString();
           boardID =captionText;
           shareToPinterest(boardID);
           passwordDialog.dismiss();
       }
   });
}

A user can fetch the Board ID by following the steps:

Board ID is necessary because it specifies where the image needs to be posted.

Creating custom post function for Phimpme

The image is posted using a function in PDKClient class. PDKClient is found in the PDK module which we get after importing Pinterest SDK. Every image is posted on Pinterest is called a Pin. So we will call createPin function. I have made my custom createPin function so that it also accepts Bitmaps as a parameter. In the Pinterest SDK it only accepts image URL to share, The image should already be on the internet to be shared. For this reason, we to add a custom create Pin function to accept Bitmaps as an option.

public void createPin(String note, String boardId, Bitmap image, String link, PDKCallback callback) {
   if (Utils.isEmpty(note) || Utils.isEmpty(boardId) || image == null) {
       if (callback != null) callback.onFailure(new PDKException("Board Id, note, Image cannot be empty"));
       return;
   }

   HashMap<String, String> params = new HashMap<String, String>();
   params.put("board", boardId);
   params.put("note", note);
   params.put("image_base64", Utils.base64String(image));
   if (!Utils.isEmpty(link)) params.put("link", link);
   postPath(PINS, params, callback);
}

Compressing Bitmaps

Since Pinterest SDK cannot accept Bitmap I have added a function to compress the Bitmap and encode it to strings.

public static String base64String(Bitmap bitmap) {
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
   String b64Str = Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
   return b64Str;
}

Calling createPin function from the sharingActivity

From the sharingActivity we will call createPin activity. We will pass caption of the image, Board ID, Image bitmap and link(which is optional) as parameters.

PDKClient
       .getInstance().createPin(caption, boardID, image, null, new PDKCallback() {

If the image is posted successfully then onSuccess function is called which pops a snackbar and shows the success message. Otherwise, onFailure function is called which displays failure message on a snackbar.

@Override
public void onSuccess(PDKResponse response) {
   Log.d(getClass().getName(), response.getData().toString());
   Snackbar.make(parent, R.string.pinterest_post, Snackbar.LENGTH_LONG).show();
   //Toast.makeText(SharingActivity.this,message,Toast.LENGTH_SHORT).show();

}

@Override
public void onFailure(PDKException exception) {
   Log.e(getClass().getName(), exception.getDetailMessage());
   Snackbar.make(parent, R.string.Pinterest_fail, Snackbar.LENGTH_LONG).show();
   //Toast.makeText(SharingActivity.this, boardID + caption, Toast.LENGTH_SHORT).show();

}

Conclusion

In Phimpme user can now send Image on Pinterest directly from the application. This is done without the use of the native Pinterest application.

Github

Resources

Continue ReadingShare Images on Pinterest from Phimpme Android Application