Option to delete albums from storage in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to delete unwanted albums permanently from the storage. So in this post, I will be discussing how we implemented the functionality to delete albums.

Step 1

First, we need to provide an option in the overflow menu to delete album/albums when the user long-selects album/albums or the user is viewing photos inside a particular album. The option in the overflow menu can be added by integrating following lines of code in the menu_albums.xml file(this file contains the XML code for overflow menu options provided for albums).

 <item
android:id=”@+id/delete_action”
android:title=”@string/delete”
app:showAsAction=”ifRoom” />

Step 2

Now when the user opts to delete some selected albums, we need to retrieve user’s choice of albums to delete and store it in an ArrayList<Album> to keep track of the albums and for further use. We would here also keep track of all the albums displayed currently to the user through another ArrayList<Album> named displayAlbums. Thereafter deleteSelectedAlbums method of class HandlingAlbums will be invoked passing in the activity context as the parameter. Now in this method we’d iterate through the ArrayList<Album>(ArrayList used to store user’s choice of albums to delete) and in turn invoke another method deleteAlbum with each iteration, passing in the album and context as the parameters. The deleteAlbum method would return a boolean value that will indicate whether or not the particular album was successfully deleted. If the boolean value returned is true, indicating that the particular album and its contents have been permanently deleted from the device storage, then we would delete that particular album object from the displayAlbums list and the AlbumsAdapter would be notified to display the remaining set of albums by passing in the modified displayAlbums list. The code snippet used to implement the method deleteSelectedAlbums and deleteAlbum is provided below.

public boolean deleteSelectedAlbums(Context context) {
boolean success = true;

for (Album selectedAlbum : selectedAlbums) {
  int index = dispAlbums.indexOf(selectedAlbum);
  if(deleteAlbum(selectedAlbum, context))
    dispAlbums.remove(index);
  else success = false;
}
return success;
}
public boolean deleteAlbum(Album album, Context context) {
return ContentHelper.deleteFilesInFolder(context, new File(album.getPath()));
}

Step 3

In the previous step, we were invoking a function deleteAlbum which would return a boolean variable denoting whether the album was deleted permanently or not. Therefore in this step, I’d be discussing the working of the deleteAlbum method. So the deleteAlbum method was being invoked passing in the album to delete and the context as the parameters. This method, in turn invokes the deleteFilesInFolder method of the ContentHelper class passing in the context and a File object created from the album path(to be deleted). This method returns a boolean variable indicating the success or failure of the operation and this variable is in turn returned by the deleteAlbum method. The code representing the function invoke has been provided above. The code snippet representing the deleteFilesInFolder method is provided below.

public static boolean deleteFilesInFolder(Context context, @NonNull final File folder) {
 boolean totalSuccess = true;

 String[] children = folder.list();
 if (children != null) {
    for (String child : children) {
       File file = new File(folder, child);
       if (!file.isDirectory()) {
          boolean success = deleteFile(context, file);
          if (!success) {
             Log.w(TAG, “Failed to delete file” + child);
             totalSuccess = false;
          }
       }
    }
 }
 return totalSuccess;
}

Now inside the deleteFilesInFolder method first all the contents of the file are stored in a String[] array child. Thereafter by iterating through the child array, every time a method deleteFile is invoked passing in an item from the child array(an image path) and the context as parameters, which returns a boolean variable indicating if the particular image content is deleted or not.

Now depending on which Android version the device is running, there are two methods used to delete the image file permanently from the storage.

If the device is running on version LOLLIPOP and above, the Android Storage Access Framework has been used to delete the image file, and if the device is running version KITKAT, the MediaStore content provider is used to delete the file from device’s storage. The code snippet implementing the deleteFile method is provided below.

public static boolean deleteFile(Context context, @NonNull final File file) {
 boolean success = file.delete();

 // Try with Storage Access Framework.
 if (!success && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    DocumentFile document = getDocumentFile(context, file, false, false);
    success = document != null && document.delete();
 }

 // Try the Kitkat workaround.
 if (!success && Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
    ContentResolver resolver = context.getContentResolver();

    try {
       Uri uri = null;//MediaStoreUtil.getUriFromFile(file.getAbsolutePath());
       if (uri != null) {
          resolver.delete(uri, null, null);
       }
       success = !file.exists();
    }
    catch (Exception e) {
       Log.e(TAG, “Error when deleting file “ + file.getAbsolutePath(), e);
       return false;
    }
 }

 if(success) scanFile(context, new String[]{ file.getPath() });
 return success;
}

This is how we achieved the functionality to delete album/albums from the storage in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/guide/topics/providers/document-provider#delete

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.MediaStore class – https://developer.android.com/reference/android/provider/MediaStore

4.Deleting files via Android media content provider – https://stackoverflow.com/questions/10925196/deleting-files-via-a-contentresolver-as-opposed-to-deleting-them-via-file-del

Continue Reading

Option to add description to an image in the Phimpme Android app

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to add some description to the image. So in this blog post, I will be discussing how we achieved the functionality to add a description to any image.

Step 1

First, we need to add an option in the overflow menu to add description for the image being viewed. The option to add description to an image has been achieved by adding the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/action_description”
  android:title=“Description”
  app:showAsAction=“never” />

Step 2

Now after the user chooses the option to add description to the image, an alertdialog with an edittext would be displayed to the user to enter the description. The dialog box with edittext has been implemented with the following lines of XML code.

<android.support.v7.widget.CardView
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/description_dialog_card”>
  <ScrollView
      android:layout_width=“match_parent”
      android:layout_height=“match_parent”>
      <LinearLayout
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:orientation=“vertical”>
          <TextView
              android:id=“@+id/description_dialog_title”
              android:layout_width=“match_parent”
              android:textColor=“@color/md_dark_primary_text”
              android:layout_height=“wrap_content”
              android:background=“@color/md_dark_appbar”
              android:padding=“24dp”
              android:text=“@string/type_description”
              android:textSize=“18sp”
              android:textStyle=“bold” />
          <LinearLayout
              android:id=“@+id/rl_description_dialog”
              android:layout_width=“match_parent”
              android:layout_height=“wrap_content”
              android:orientation=“horizontal”
              android:padding=“15dp”>
              <EditText
                  android:id=“@+id/description_edittxt”
                  android:layout_width=“fill_parent”
                  android:layout_height=“wrap_content”
                  android:padding=“15dp”
                  android:hint=“@string/description_hint”
                  android:textColorHint=“@color/grey”
                  android:layout_margin=“20dp”
                  android:gravity=“top|left”
                  android:inputType=“textCapSentences|textMultiLine”
                  android:maxLength=“2000”
                  android:maxLines=“4”
                  android:selectAllOnFocus=“true”/>
              <ImageButton
                  android:layout_width=“@dimen/mic_image”
                  android:layout_height=“@dimen/mic_image”
                  android:layout_alignRight=“@+id/description_edittxt”
                  app2:srcCompat=“@drawable/ic_mic_black”
                  android:layout_gravity=“center”
                  android:background=“@color/transparent”
                  android:paddingEnd=“10dp”
                  android:paddingTop=“12dp”
                  android:id=“@+id/voice_input”/>
          </LinearLayout>
      </LinearLayout>
  </ScrollView>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter description has been provided below.

Step 3

Now after retrieving the description added by the user, the description is saved in the realm database using the realm model object ImageDescModel which will contain the path of the image and the description added as its attributes. The path of the image has been used as the primary key for the description table. The realm model class used for the above-mentioned operation is described below.

public class ImageDescModel extends RealmObject {
  @PrimaryKey
  private String path;
  private String desc;

  public ImageDescModel() {
  }

  public ImageDescModel(String path, String title) {
      this.path = path;
      this.desc = title;
  }

  public String getId() {
      return path;
  }

  public void setId(String path) {
      this.path = path;
  }

  public String getTitle() {
      return desc;
  }

  public void setTitle(String description) {
      this.desc = description;
  }
}

This is how we have implemented the functionality to add description to images in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Realm for Android – https://realm.io/blog/realm-for-android/

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Working with realm tutorial – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/

Continue Reading

Option to rename albums in the Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to rename albums. So in this post, I will be discussing the implementation of the renaming functionality.

Step 1

First we need to add an option in the overflow menu to rename an album while we long-select an album or we are viewing photos inside a particular album. The option in the overflow menu can be added by integrating the following lines of code in the menu_albums.xml file(this file contains the xml code for overflow menu options provided for albums).

<item
android:id=”@+id/renameAlbum”
android:title=”@string/rename”
app:showAsAction=”never” />

Step 2

Now after the user selects the option in the overflow menu to rename the current viewing album, an alertdialog with an edittext would be displayed to the user to enter the new name for the album. The alertdialog containing the edittext has been implemented with the following lines of code.

<android.support.v7.widget.CardView
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/vn.mbm.phimp.me”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
app:cardCornerRadius=”2dp”
android:id=”@+id/dialog_chose_provider_title”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”>
<TextView
android:id=”@+id/rename_title”
android:layout_width=”match_parent”
android:textColor=”@color/md_dark_primary_text”
android:layout_height=”wrap_content”
android:background=”@color/md_dark_appbar”
android:padding=”24dp”
android:text=”@string/rename_photo_action”
android:textSize=”18sp”
android:textStyle=”bold” />
<RelativeLayout
android:id=”@+id/container_edit_text”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:padding=”24dp”/>
</LinearLayout>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter the new name is provided below.

Step 3

Finally, after retrieving the new name for the album entered by the user, the renameAlbum method is invoked by passing in the application context and new name as the parameters. Now inside the renameAlbum method, a new file path is created by the use of the getAlbumPathRenamed method of the StringUtils class and the current path of the album is changed to the new path generated earlier. Similarly path of each media inside the album is changed accordingly by the use of the method getPhotoPathRenamedAlbumChange of the StrinUtils class by looping through the media objects one by one. The lines of code implemented to perform the above-mentioned operations are provided below.

public boolean renameAlbum(final Context context, String newName) {
 found_id_album = false;
 boolean success;
 File newDir = new File(StringUtils.getAlbumPathRenamed(getPath(), newName));
 File oldDir = new File(getPath());
 success = oldDir.renameTo(newDir);
 if(success) {
    for (final Media m : media) {
       File from = new File(m.getPath());
       File to = new File(StringUtils.getPhotoPathRenamedAlbumChange(m.getPath(), newName));
       scanFile(context, new String[]{ from.getAbsolutePath() });
       scanFile(context, new String[]{ to.getAbsolutePath() }, new MediaScannerConnection.OnScanCompletedListener() {
          @Override
          public void onScanCompleted(String s, Uri uri) {
             if (!found_id_album) {
                id = MediaStoreProvider.getAlbumId(context, s);
                found_id_album = true;
             }
             Log.d(s, “onScanCompleted: “+s);
             m.setPath(s); m.setUri(uri.toString());
          }
       });
    }
    path = newDir.getAbsolutePath();
    name = newName;
 }
 return success;
}

This is how we have implemented the functionality to rename particular albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/media/MediaScannerConnection

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Media Scanner tutorial –https://stackoverflow.com/questions/13270789/how-to-run-media-scanner-in-android

Continue Reading

I2C communication in PSLab Android

PSLab device is a compact electronic device with a variety of features. One of them is the ability to integrate sensors and get readings from them. One might think that why they should use PSLab device to get sensor readings and they can use some other hardware like Arduino. In those devices, user has to create firmware and need to know how to interface them with correct sampling rates and settings. But with PSLab, they all come in the whole package. User just have to plug the device to his phone and the sensor to device and he’s ready.

The idea of this blog post is to show how this sophisticated process is actually happening. Before that, let me give you a basic introduction on how I2C communication protocol works. I2C protocol is superior to UART and SPI protocols as they are device limited or requires many wires. But with I2C, you can literally connect thousands of sensors with just 4 wires. These wires are labeled as follows;

  • VCC – Power line
  • GND – Ground line
  • SDA – Data line
  • SCL – Signal clock

It is required that the SDA and SCL lines need to be connected to VCC line using two pull up resistors. But that’s just hardware. Let’s move on to learn how I2C communicate.

Here there is this communicating concept called master and slave. To start communication, master issues a global signal like a broadcast to all the devices connected to SCL and SDA lines. This signal contains the address of the slave, master needs to address and get data from. If the slave received this call to him, he will pull down the SDA line to signal the master that he heard him and ready to communicate with him. Here communication means reading or writing data. Then the communication happens and the link between master and slave breaks giving opportunity to other masters and slaves.

One might think this is a slow process. But these signals are transmitted at high frequencies. In PSLab it is at 100 kHz and that is one millisecond.

PSLab library has a special class to handle I2C communication. That is

public class I2C {/**/}

 

Once this class is initiated, one has to call the start function to start communication. This method requires the address we wish to communicate with and the mode of operation stating if it is a read or write operation

public int start(int address, int rw) throws IOException {
   packetHandler.sendByte(commandsProto.I2C_HEADER);
   packetHandler.sendByte(commandsProto.I2C_START);
   packetHandler.sendByte((address << 1) | rw & 0xff);
   return (packetHandler.getAcknowledgement() >> 4);
}

 

Once the address is sent out, protocol requires us to stop and wait for acknowledgement.

public void wait() throws IOException {
   packetHandler.sendByte(commandsProto.I2C_HEADER);
   packetHandler.sendByte(commandsProto.I2C_WAIT);
   packetHandler.getAcknowledgement();
}

 

If there are no congestion in the lines such as reading from multiple devices, the acknowledgement will be instantaneous. Once that is complete, we can start communication either byte-wise or bulk-wise

public int send(int data) throws IOException {
   packetHandler.sendByte(commandsProto.I2C_HEADER);
   packetHandler.sendByte(commandsProto.I2C_SEND);
   packetHandler.sendByte(data);
   return (packetHandler.getAcknowledgement() >> 4);
}

 

As an example, reading sensor values at a given interval can be done using the following method call.

public ArrayList<Byte> read(int length) throws IOException {
   ArrayList<Byte> data = new ArrayList<>();
   for (int i = 0; i < length - 1; i++) {
       packetHandler.sendByte(commandsProto.I2C_HEADER);
       packetHandler.sendByte(commandsProto.I2C_READ_MORE);
       data.add(packetHandler.getByte());
       packetHandler.getAcknowledgement();
   }
   packetHandler.sendByte(commandsProto.I2C_HEADER);
   packetHandler.sendByte(commandsProto.I2C_READ_END);
   data.add(packetHandler.getByte());
   packetHandler.getAcknowledgement();
   return data;
}

 

Once we get the data bundle, either we can save them or display in a graph whatever the way it’s convenient.

Reference:

  1. https://en.wikipedia.org/wiki/I%C2%B2C
Continue Reading

Setting up environment to build PSLab Android app using Fdroid Build

Fdroid is a place for open source enthusiasts and developers to host their Free and Open Source Software (FOSS) for free and get more people onboard into their community. In order to host an app in their repository, one has to go through a several steps of builds and tests. This is to ensure that the software provided by them are as quality and safe as they can ever be. They are not allowing proprietary libraries or tools to integrate into any app or they will  be published outside the Fdroid main repository (fdroid-data) so that the users will know what they are downloading.

In a normal Linux computer where we are developing Android apps and have setup Android Studio will not be able to run the build command using:

$ fdroid build -v -l org.fossasia.pslab

The reason behind this is that we have not installed gradle and build tools required by the “fdroid build” because they are not useful in our day today activities for standalone activities. First thing we need to do is, install gradle separately. This will include adding gradle to $PATH as well.

Download the latest gradle version zip file or the version your project is using with the following command. In PSLab Android app, we are using 4.5.1 version and the snippet below include that version.

$ wget https://services.gradle.org/distributions/gradle-4.5.1-bin.zip

Next step is to install this in a local folder. We can select any path we want, but /opt/ folder is generally used in such scenarios.

sudo mkdir /opt/gradle
sudo unzip -d /opt/gradle gradle-4.5.1-bin.zip

Then we can add gradle to our $PATH variable using the following command:

$ export PATH=$PATH:/opt/gradle/gradle-4.5.1/bin

Now we are all set with gradle settings. Next step is to verify that the fdroid server is properly configured and up to date. When you run the build command after setting up the gradle in PC, it will throw an error similar to “failed to find any output apks”. This causes if the installed fdroid server version is old.

Fdroid server is running on python 3 and it will require some additional libraries pre-installed to properly function.

$ sudo apt-get install vagrant virtualbox git python3-certifi python3-libvirt python3-requestbuilder python3-yaml python3-clint python3-vagrant python3-paramiko python3-pyasn1 python3-pyasn1-modules

Once these libraries are installed, remove the previous instance of fdroidserver by using the following command:

$ sudo apt-get remove fdroidserver

Then we can reinstall the latest version of fdroid server from git using the following command:

$ git clone https://gitlab.com/fdroid/fdroidserver.git
export PATH="$PATH:$PWD/fdroidserver"

Now we are all set to do a brand new lint build on our PC to make our app ready to be published in Fdroid repository!

Reference:

  1. Install gradle : https://www.vultr.com/docs/how-to-install-gradle-on-ubuntu-16-10
  2. Gradle versions : https://gradle.org/releases
  3. Setting up Fdroid-server : https://f-droid.org/en/docs/Build_Server_Setup/

Installing fdroidserver : https://gitlab.com/fdroid/fdroiddata/blob/master/README.md#quickstart

Continue Reading

Retrofit2 Rxjava2 Error Response Handling in Open Event Organizer App

In the Open Event Organizer Android app the challenge is to provide user, a readable error description for the input requests. The Organizer App was showing a coded message which was understandable only to a programmer making it unfriendly to the common user. The blog describes how we tackled this problem and implemented a mechanism to provide user friendly error messages for user requests (if any).

Let’s consider the scenario when an organizer want to create an Event or maybe a Ticket so what he has to do is fill out a form containing some user input fields and click on submit button to send the data to the server which in turn sends a response for the request. If the information is valid the server sends a HTTP 201 Response and user gets feedback that the Event/Ticket is created. But if the information is not valid the user gets feedback that there is HTTP 422 error in your request. There is no readable description of error provided to the user.

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Further, we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVP architecture. Let’s move on to the code.

Retrofit to the rescue

Now we will see how we can extract the details about the error response and provide user a better feedback rather than just throwing the error code.

Firstly let’s make the Request to our REST API Server using Retrofit2.

@POST(“faqs”)
Observable<Faq> postFaq(@Body Faq faq);

Here we are making a POST request and expecting a Observable<Faq> Response from the server.The  @Body annotation indicates the Request Body is an Faq object.

Please note that Observable used here is ReactiveX Observable so don’t confuse it with java.util Observable.

public class Faq {
@Id(LongIdHandler.class)
public Long id;  @Relationship(“event”)
@ForeignKey(stubbedRelationship = true,

onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String question;
public String answer;
}

Let’s say the API declares both question and answer as mandatory fields  for creation of an Faq. We supply the following input to the app.

question = “Do I need to buy a Ticket to get In ?”;
answer = null

We used RxJava to make an asynchronous request to server. In case of error we will get a Retrofit throwable and we will pass that on to ErrorUtils.java which will do all the processing and return a readable error message.

faqRepository
.createFaq(faq)
.compose(dispose(getDisposable()))
.compose(progressive(getView()))  .doOnError(throwable ->

getView().showError(ErrorUtils.getMessage(throwable)))
.subscribe(createdFaq -> {
getView().onSuccess(“Faq Created”);
getView().dismiss();
}, Logger::logError);

Now we will extract the ResponseBody from the Retrofit throwable.

ResponseBody responseBody = ((HttpException) throwable)

.response().errorBody();
return getErrorDetails(responseBody);

The ResponseBody is a JSON containing all the information about the error.

{
“errors”: [
{
“status”: “422”,
“source”: {
“pointer”: “/data/attributes/answer”
},
“detail”: “Missing data for required field.”,
“title”: “Validation error”
}
],
“jsonapi”: {
“version”: “1.0”
}
}

In order to provide better feedback to user regarding the error we have to parse the response JSON and extract the pointed field (which in this case is – “answer”) and then combine it with the value corresponding to the “detail” key. Following is the relevant section from ErrorUtils.java (for viewing the complete ErrorUtils.java please refer here).

public static String getErrorDetails(ResponseBody responseBody) {
try {
JSONObject jsonObject = new JSONObject(responseBody.string());
JSONObject jsonArray = new    JSONObject(jsonObject.getJSONArray(“errors”).get(0).toString());
JSONObject errorSource =

new JSONObject(jsonArray.get(“source”).toString());

try {
String pointedField =

getPointedField(errorSource.getString(“pointer”));
if (pointedField == null)
return jsonArray.get(“detail”).toString();
else
return jsonArray.get(“detail”).toString()

.replace(“.”, “”) + “: ” + pointedField;
} catch (Exception e) {
return jsonArray.get(“detail”).toString();
}

} catch (Exception e) {
return null;
}
}

public static String getPointedField(String pointerString) {
if (pointerString == null || Utils.isEmpty(pointerString))
return null;
else {
String[] path = pointerString.split(“/”);
if (path.length > 3)
return path[path.length – 1];
else
return null;
}
}

The final error message returned by ErrorUtils in case of HTTP 422 –

Missing data for required field: answer

Similar approach can be followed for extracting the error “title” or “status” .

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Codebase for Open Event Organizer App on Github https://github.com/fossasia/open-event-orga-app
Continue Reading

Implementing Custom Date and Time Picker with 2-way Data Binding Support

The Data binding library is one of the most popular libraries among the android developers. We have been using it in the Open Event Organiser Android app for building interactive UI’s for some time now. The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. This blog explains how we implemented our own custom Date and Time picker with 2-way data binding support using the Data binding framework.

Why custom picker ?

One specific requirement in the app is to have a button, clicking on that button should open a DatePicker which would allow the user to select the date. A similar behaviour was required to allow the user to select the time as well. In order to handle this requirement we were using Binding Adapters on Button. For eg. the following Binding Adapter allowed us to define a property date on a button and set an Observable String as it’s value. We implemented a similar Binding Adapter for selecting time as well.

@BindingAdapter("date")
public static void bindDate(Button button, ObservableField<String> date) {
    String format = DateUtils.FORMAT_DATE_COMPLETE;

    bindTemporal(button, date, format, zonedDateTime ->
        new DatePickerDialog(button.getContext(), (picker, year, month, dayOfMonth) ->
                setPickedDate(
                    LocalDateTime.of(LocalDate.of(year, month + 1, dayOfMonth), zonedDateTime.toLocalTime()),
                    button, format, date),
                zonedDateTime.getYear(), zonedDateTime.getMonthValue() - 1, zonedDateTime.getDayOfMonth()));
  }

It calls the bindTemporal method which takes in a function along with the button, date and the format and does two things. First, it sets the value of the date as the text of the button. Secondly, it attaches a click listener to the button and applies the function passed in as the argument when clicked. Below is the bindTemporal method for reference:

private static void bindTemporal(Button button, ObservableField<String> date, String format, Function<ZonedDateTime, AlertDialog> dialogProvider) {
        if (date == null)
            return;

        String isoDate = date.get();
        button.setText(DateUtils.formatDateWithDefault(format, isoDate));

        button.setOnClickListener(view -> {
            ZonedDateTime zonedDateTime = ZonedDateTime.now();
            try {
                zonedDateTime = DateUtils.getDate(isoDate);
            } catch (DateTimeParseException pe) {
                Timber.e(pe);
            }
            dialogProvider.apply(zonedDateTime).show();
        });
    }

It was working pretty well until recently when we started getting deprecation warnings about using Observable fields as a parameter of Binding Adapter. Below is the full warning:

Warning:Use of ObservableField and primitive cousins directly as method parameters is deprecated and support will be removed soon. Use the contents as parameters instead in method org.fossasia.openevent.app.common.app.binding.DateBindings.bindDate

The only possible way that we could think of was to pass in regular String in place of Observable String. Now if we pass in a regular String object then the application won’t be reactive. Hence we decided to implement our own custom view to resolve this problem.

Custom Date and Time Picker

We decided to create an Abstract DateTimePicker class which will hold all the common code of our custom Date and Time pickers. It is highly recommended that you go through this awesome blog post first before reading any further. We won’t be going through the details already explained in the post.

Following are the important features of this Abstract class:

  1. It extends the AppCompatButton class.
  2. It stores an ObservableString named value and an OnDateTimeChangedListener as it’s field. We will discuss the change listener later in the article.
  3. It implements the three mandatory constructors and calls it’s super method. It also calls the init method which sets the current date and time as the default.
  4. It has a bindTemporal method which is the same as we discussed earlier.
  5. It has a setPickedDate method which sets the selected date/time as the text for the button so that users can see the selected date/time on the button itself. Moreover it notifies the change listener about the change in date if attached.
  6. It has an abstract method called setValue. It will be implemented in the sub classes and used to set the date or time value for the field named value.

You can check the full implementation here.

The OnDateTimeChangedListener which we mentioned above is an extremely simple interface. It defines a simple method onDateChanged which takes in the selected date as the argument.

public interface OnDateTimeChangedListener {
    void onDateChanged(ObservableString newDate);
}

Let’s have a look at the implementation of the DatePicker class. The key features of this class are:

  1. It extends the AbstractDateTimePicker class and implements the necessary constructors calling the corresponding super constructor.
  2. It implements the method setValue which sets the date or time passed in to the field value. It also calls the bindTemporal method of the super class.

public class DatePicker extends AbstractDateTimePicker {
    public DatePicker(Context context) {
        super(context);
    }

    public DatePicker(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DatePicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

  public void setValue(String value) {
        ObservableString observableValue = getValue();
        if (observableValue.get() == null || !TextUtils.equals(observableValue.get(), value)) {
            observableValue.set(value);
            String format = DateUtils.FORMAT_DATE_COMPLETE;

            bindTemporal(value, format, zonedDateTime ->
                new DatePickerDialog(this.getContext(), (picker, year, month, dayOfMonth) ->
                    setPickedDate(
                        LocalDateTime.of(LocalDate.of(year, month + 1, dayOfMonth), zonedDateTime.toLocalTime()), format),
                    zonedDateTime.getYear(), zonedDateTime.getMonthValue() - 1, zonedDateTime.getDayOfMonth()));
        }
    }
}

Next we discuss the BindingAdapter and the InverseBindingAdapter for the custom DatePicker which allows the data binding framework to set the action to be performed when date changes and get the date from the view respectively.

@BindingAdapter(value = "valueAttrChanged")
public static void setDateChangeListener(DatePicker datePicker, final InverseBindingListener listener) {
        if (listener != null) {
            datePicker.setOnDateChangedListener(newDate -> listener.onChange());
        }
    }

@InverseBindingAdapter(attribute = "value")
public static String getRealValue(DatePicker datePicker) {
    return datePicker.getValue().get();
}

Now in order to use our view, we can simply define it in the layout file as shown below:

<org.fossasia.openevent.app.ui.views.DatePicker
                    style="?attr/borderlessButtonStyle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/purple_500"
                    app:value="@={ date }" />

The key thing to notice is the use of @= instead of @ which denotes two way data binding.    

Conclusion

The Android Data binding framework is extremely powerful and flexible at the same time. We can use it for our custom requirements as shown in this article.

References

 

 

 

 

 

Continue Reading

Testing Endpoints on Local Server

All servlets in SUSI.AI have a BaseUserRole defined. It represents the access level you need to access the endpoint corresponding to that servlet. The lowermost BaseUserRole a SUSI.AI servlet can have is ANONYMOUS, which means that anyone can access the endpoint corresponding to these endpoints. But if the BaseUserRole is higher than that, then you need an access token to access the endpoint. This blog post explains how you can get access token to access the endpoints on a local server.

What are endpoints in an API?

An endpoint in API is one end of a communication channel. When an API interacts with another system, the touchpoints of this communication are considered endpoints. For APIs, an endpoint can include a URL of a server or service. Each endpoint is the location from which APIs can access the resources they need to carry out their function.

APIs work using ‘requests’ and ‘responses.’ When an API requests information from a web application or web server, it will receive a response. The place that APIs send requests and where the resource lives, is called an endpoint.

For example, the endpoint for https://api.susi.ai/cms/getSkillRating.json?queryParameters would be /cms/getSkillRating.json.

Servlets and Endpoints in SUSI.AI

All servlets in our SUSI project define an endpoint and also define a BaseUserRole, that is, the amount of privileges required to access the information on those endpoints. If the BaseUserRole defined is ANONYMOUS, then anyone can access the endpoint directly. But if the BaseUserRole is anything higher than that, then we would need an access token to access that.

How to get Access Token?

If you’re trying to access the endpoints with BaseUserRole higher than ANONYMOUS on the actual hosted server, then you can simply login to https://chat.susi.ai and get the access token from the Network tab of the Developers Tool. We can then use that token and pass that as a query parameter along with the other parameters of that particular endpoint. For example,

http://localhost:4000/aaa/listUserSettings.json?access_token=6O7cqoMbzlClxPwg1is31Tz5pjVwo3

 

But, the problem arises when you are trying to access such endpoints on local server. The local User data is completely different from the server User data. Hence, we need to generate an access token in localhost itself.

To generate access token for local server, we need to follow these steps :

  1. First, we need to hit the /aaa/signup.json endpoint with a new account credentials which we want to register for the localhost session. This is done as shown in below example:

http://localhost:4000/aaa/signup.json?signup=anyEmail&password=anyPassword

 

  1. Then, we need to hit the /aaa/login.json endpoint with the same credentials you registered in the previous step. This is done as shown in below example:

http://localhost:4000/aaa/login.json?login=yourEmail&type=access-token&password=yourPassword

 

If you’ve entered the registered credentials correctly, then the output of the /aaa/login.json endpoint would be a JSON as shown below:

{
  "accepted": true,
  "valid_seconds": 604800,
  "access_token": "7JPi7zNwemg1YYnr4d9JIdZMaIWizV",
  "message": "You are logged in as anyemail",
  "session": {"identity": {
    "type": "host",
    "name": "127.0.0.1_4e75edbb",
    "anonymous": true
  }}
}

 

As it can be seen from the above JSON response, we get the access token which we needed. Hence, copy this access token and store it somewhere because you can now use this access token to access the endpoints with BaseUserRole as User for this localhost session.

Note that you’ll have to follow all the above steps again if you start a fresh localhost session.

Resources

Continue Reading

Displaying all the images from storage at once in Phimpme Android Application

In the Phimpme Android application, the images are displayed in the albums in which they are indexed in the device’s storage. However, there is also an “All photos” section in the application where all the images present in the device’s storage are displayed at once irrespective of the folder they’re indexed in. So in this post, I will be discussing how we achieved the “All Photos”  functionality.

Android framework provides developers with a media content provider class called MediaStore. It contains metadata for all available media on both internal and external storage devices. With the help of particular methods we can obtain metadata for all the images stored in the device’s storage.

Step 1

So First we need to get the Uri from MediaStore content provider pointing to the media(here media refers to the photos stored on the device). This can be done by the following snippet of code.

Uri uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

Step 2

Now retrieving a cursor object containing the data column for the image media is required to be performed. The data column will contain the path to the particular image files on the disk. This can be done by querying the MediaColumns table of the MediaStore class, which can be performed by the use of the content resolver query method. The mentioned functionality can be achieved by the following lines of code.

String[] projection = {MediaStore.MediaColumns.DATA};
Cursor cursor = activity.getContentResolver().query(uri, projection, null, null, null);

Step 3

In the final step, we would retrieve the path of all the images by iterating through the cursor object obtained in the previous step and keep adding those paths to an ArrayList<String>. Creating Media objects passing in the image path and concurrently adding those Media objects to an ArrayList<Media> to be done thereafter. Finally, the dataset(ArrayList<Media> in this case) containing Media objects for all the images is required to be passed to the Media Adapter in order to display all the photos in the UI. The code snippets used for the final steps are provided below.

while (cursor.moveToNext()) {
  absolutePathOfImage = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA));
  listOfAllImages.add(absolutePathOfImage);
}
ArrayList<Media> list = new ArrayList<>();

for (String path : listOfAllImages) {
  list.add(new Media(new File(path)));
}
return list;

This is how we achieved the functionality to display all the images from the storage on a single screen(at once) in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

The screenshot for displaying the “All photos” section is provided below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/provider/MediaStore

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Displaying all the images from gallery in android – https://deepshikhapuri.wordpress.com/2017/03/20/get-all-images-from-gallery-in-android-programmatically/

Continue Reading

Building PSLab Android app with Fdroid

Fdroid is a place for open source enthusiasts and developers to host their Free and Open Source Software (FOSS) for free and get more people onboard into their community. Hosting an app in Fdroid is not a fairly easy process just like hosting one in Google Play. We need to perform a set of build checks prior to making a merge request (which is similar to pull request in GitHub) in the fdroid-data GitLab repository. PSLab Android app by FOSSASIA has undergone through all these checks and tests and now ready to be published.

Setting up the fdroid-server and fdroid-data repositories is one thing. Building our app using the tools provided by fdroid is another thing. It will involve quite a few steps to get started. Fdroid requires all the apps need to be built using:

$ fdroid build -v -l org.fossasia.pslab

 

This will output a set of logs which tell us what went wrong in the builds. The usual one in a first time app is obviously the build is not taking place at all. The reason is our metadata file needs to be changed to initiate a build.

Build:<versioncode>,<versionname>
    commit=<commit which has the build mentioned in versioncode>
    subdir=app
    gradle=yes

 

When a metadata file is initially created, this build is disabled by default and commit is set to “?”. We need to fill in those blanks. Once completed, it will look like the snippet above. There can be many blocks of “Build” can be added to the end of metadata file as we are advancing and upgrading through the app. As an example, the latest PSLab Android app has the following metadata “Build” block:

Build:1.1.5,7
    commit=0a50834ccf9264615d275a26feaf555db42eb4eb
    subdir=app
    gradle=yes

 

In case of an update, add another “Build” block and mention the version you want to appear on the Fdroid repository as follows:

Auto Update Mode:Version v%v
Update Check Mode:Tags
Current Version:1.1.5
Current Version Code:7

 

Once it is all filled, run the build command once again. If you have properly set the environment in your local PC, build will end successfully assuming there were no Java or any other language syntax errors.

It is worth to mention few other facts which are common to Android software projects. Usually the source code is packed in a folder named “app” inside the repository and this is the common scenario if Android Studio builds up the project from scratch. If this “app” folder is one level below the root, that is “android/app”, the build instructions shown above will throw an error as it cannot find the project files.

The reason behind this is we have mentioned “subdir=app” in the metadata file. Change this to “subdir=android/app” and run the build again. The idea is to direct the build to find where the project files are.

Apart from that, the commit can be represented by a tag instead of a long commit hash. As an example, if we had merge commits in PSLab labeled as “v.<versioncode>”, we can simply use “commit=v.1.1.5” instead of the hash code. It is just a matter of readability.

Happy Coding!

Reference:

  1. Metadata : https://f-droid.org/docs/Build_Metadata_Reference/#Build
  2. PSLab Android app Fdroid : https://gitlab.com/fdroid/fdroiddata/merge_requests/3271/diffs
Continue Reading
Close Menu
%d bloggers like this: