Implementing Speakers Call API in Open Event Frontend

This article will illustrate how to display the speakers call details on the call for speakers page in the Open Event Frontend project using the Open Event Orga API. The API endpoints which will be mainly focussing on for fetching the speaker call details are:

GET /v1/speakers-calls/{speakers_call_id}

In the case of Open Event, the speakers are asked to submit their proposal beforehand if they are interested in giving some talk. For the same purpose, we have a section on the event’s website called as Call for Speakers on the event’s public page where the details about the speakers call are present along with the button Submit Proposal which redirects to the link where they can upload the proposal if the speakers call is open. Since the speakers call page is present on the event’s public page so the route which will be concerned with will be public/index route and its subroute public/index/cfs in the application. As the call for speakers details are nested within the events model so we need to first fetch the event and then from there we need to fetch the speaker-calls detail from the model.

The code to fetch the event model looks like this:

model(params) {
return this.store.findRecord('event', params.event_id, { include: 'social-links' });
}

The above model takes care of fetching all the data related to the event but, we can see that speakers call is not included as the parameter. The main reason behind this is the fact that the speakers is not required on each of the public route, rather it is required only for the subroute public/index/cfs route. Let’s see how the code for the speaker-call modal work to fetch the speaker calls detail from the above event model.  

model() {
    const eventDetails = this.modelFor('public');
    return RSVP.hash({
      event        : eventDetails,
      speakersCall : eventDetails.get('speakersCall')
    });
}

In the above code, we made the use of this.modelFor(‘public’) to make the use of the event data fetched in the model of the public route, eliminating the separate API call for the getting the event details in the speaker call route. Next, using the ember’s get method we are fetching the speakers call data from the eventDetails and placing it inside the speakersCall JSON object for using it lately to display speakers call details in public/index subroute.

Until now, we have fetched event details and speakers call details in speakers call subroute but we need to display this on the index page of the sub route. So we will pass the model from file cfs.hbs to call-for-speakers.hbs the code for which looks like this:

{{public/call-for-speakers speakersCall=model.speakersCall}}  

The trickiest part in implementing the speakers call is to check whether the speakers call is open or closed. The code which checks whether the call for speaker has to be open or closed is:

isOpen: computed('startsAt', 'endsAt', function() {
     return moment().isAfter(this.get('startsAt')) && moment().isBefore(this.get('endsAt'));
})

In the above-computed property isOpen of speakers-call model, we are passing the starting time and the ending time of the speakers call. We are then comparing if the starting time is after the current time and the current time is before the ending time than if both conditions satisfy to be true then the speakers call is open else it will be closed.  

Now, we need a template file where we will define how the user interface for call-for-speakers based on the above property, isOpen. The code for displaying UI based on its open or closed status is

  {{#if speakersCall.isOpen}}
    <a class="ui basic green label">{{t 'Open'}} </a>
    <div class="sub header">
      {{t 'Call for Speakers Open until'}} {{moment-format speakersCall.endsAt 'ddd, MMM DD HH:mm A'}}
    </div>
  {{else}}
    <a class="ui basic red label">{{t 'Closed'}}</a>
  {{/if}}

In the above code, we are checking is the speakersCall is open then we show a label open and display the date until which speakers call is opened using the moment helper in the format “ddd, MMM DD HH:mm A” else we show a label closed. The UI for the above code looks like this.

Fig. 1: The heading of speakers call page when the call for speakers is open

The complete UI of the page looks like this.

Fig. 2: The user interface for the speakers call page

The entire code for implementing the speakers call API can be seen here.

To conclude, this is how we efficiently fetched the speakers call details using the Open-Event-Orga speakers call API, ensuring that there is no unnecessary API call to fetch the data.  

Resources:

Adding JSON API support to ember-models-table in Open Event Front-end

Open Event Front-end project uses ember-models-table for handling all the table components in the application. Although ember-models-table is great for handling server requests for operations like pagination, sorting & filtering, but it does not support JSON API used in the Front-end project.

In this blog we will see how we integrated JSON API standards to ember-models-table. Lets see how we added support for JSON API to table and made requests to the Open Event Orga-server.

Adding JSON API support for filtering & sorting

The JSON API specs follow a strict structure for supporting meta data & filtering options, the server expects an array of objects for specifying the name of the field, operation and the value for filtering. The name attribute specifies the column for which we need to apply the filter. eg we use `name` for the events name in the. `op` attribute specifies the operation to be used for filtration, `val` attribute is used to provide a value for comparison. You can check the list of all the supported operations here.

For implementation of filter we will check if the column filter is being used i.e if the filter string is empty or not, if the string is not empty we add a filter object of the column using the specified specs, else we remove the filter object of the column.

if (filter) {
  query.filter.pushObject({
    name : filterTitle,
    op   : 'ilike',
    val  : `%${filter}%`
  });
} else {
  query.filter.removeObject({
    name : filterTitle,
    op   : 'ilike',
    val  : `%${filter}%`
  });
}

For sort functionally we need to pass a query parameter called `sort` which is a string value in the URL. Sorting can be done in ascending or descending order for which the server expects different values. We pass `sort=name` & `sort=-name` for sorting in ascending order & descending order respectively.

const sortSign = {
  none : '',
  asc  : '-',
  desc : ''
};
let sortedBy = get(column, 'sortedBy');
if (typeOf(sortedBy) === 'undefined') {
  sortedBy = get(column, 'propertyName');
}

Adding support for pagination

The pagination in JSON API is implemented using query parameters `page[size]` & `page[number]` which specify the size of the page & the current page number respectively eg

page[size]=10&page[number]=1

This will load the first ten events from the server in the application.

Once the data is loaded in the application we calculate the number of pages to be rendered. The response from the server has attached meta-data which contains the total number of the events in the following structure:

meta: {
  count: 100
}

We calculate the number of pages by dividing the total count by the size of the page. We check if the number of items is greater than the pageSize, and calculate the number of the pages using the formula `items / pagesize + (items % pagesize ? 1 : 0)`. If the items are less than the pageSize we do not have to calculate the pages and we simply hide the pagination in the footer.

if (pageSize > items) {
  this.$('.pagination').css({
    display: 'none'
  });
} else {
  this.$('.pagination').removeAttr('style');
  pages = parseInt((items / pageSize));
  if (items % pageSize) {
    pages = pages + 1;
  }
}

Adding dynamic routing support to ember-models-table

We may want to use the ember-models-table for dynamic routes like `events/list` route, where we load live, drafted & past events based on the current route. The ember-models-table by default do not support the dynamic routes. To add this we override the didReceiveAttrs() method of the component which is executed every time the component updates. We add reset the pageSize, currentPageNumber and the content of the table, as the routes change.

didReceiveAttrs() {
  set(this, 'pageSize', 10);
  set(this, 'currentPageNumber', 1);
  set(this, 'filteredContent', get(this, 'data'));
}

The result of this we now have tables supporting JSON API in the Open Event Front-end application

Thank you for reading the blog, you can check the source code for the example here.

Resources

Binding Images Dynamically in Open Event Orga App

In Open Event Orga App (Github Repo), we used Picasso to load images from URLs and display in ImageViews. Picasso is easy to use, lightweight, and extremely configurable but there has been no new release of the library since 2015. We were using Picasso in binding adapters in order to dynamically load images using POJO properties in the layout XML itself using Android Data Binding. But this configuration was a little buggy.

The first time the app was opened, Picasso fetched the image but it was not applied to the ImageView. When the device was rotated or the activity was resumed, it loaded just fine. This was a critical issue and we tried many things to fix it but none of it quite fit our needs. We considered moving on to other Image Loading libraries like Glide, etc but it was too heavy on the size and functionality for our needs. The last resort was to update the library to develop version using Sonatype’s snapshots Repository. Now, the Picasso v2.6.0-SNAPSHOT is very stable but not released to the maven central repository, and a newer develop version v3.0.0-SNAPSHOT was launched too. So we figured we should use that. This blog will outline the steps to include the develop version of Picasso, configuring it for our needs and making it work with Android Data Binding.

Setting up Dependencies

Firstly, we need to include the sonatype repository in the repositories block of our app/build.gradle

repositories {
   ...
   maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

 

Then we need to replace the Picasso dependency entry to this:

compile 'com.squareup.picasso:picasso:3.0.0-SNAPSHOT'

 

Note that if you used Jake Wharton’s OkHttp3 Downloader for Picasso, you won’t need it now, so you need to remove it from the dependency block

And you need to use this to import the downloader

import com.squareup.picasso.OkHttp3Downloader;

 

Next, we set up our Picasso DI this way

Picasso providesPicasso(Context context, OkHttpClient client) {
   Picasso picasso = new Picasso.Builder(context)
       .downloader(new OkHttp3Downloader(client))
       .build();
   picasso.setLoggingEnabled(true);
   return picasso;
}

 

Set the singleton instance in our application:

Picasso.setSingletonInstance(picasso);

 

And we are ready to use it.

Creating Adapters

Circular Image Adapter

We show event logos as circular images, so we needed to create a binding adapter for that:

@BindingAdapter("circleImageUrl")
public static void bindCircularImage(ImageView imageView, String url) {
   if(TextUtils.isEmpty(url)) {
       imageView.setImageResource(R.drawable.ic_photo_shutter);
       return;
   }

   Picasso.with()
       .load(Uri.parse(url))
       .error(R.drawable.ic_photo_shutter)
       .placeholder(R.drawable.ic_photo_shutter)
       .transform(new CircleTransform())
       .tag(MainActivity.class)
       .into(imageView);
}

 

If the URL is empty, we just show the default photo, and otherwise we load the image into the view using standard CircleTransform

Note that there is no context argument in the with method. This was implemented in Picasso recently where they removed the need for context for loading images. Now, they use a Dummy ContentProvider to get application context, which is inspired by how Firebase does it.

Now, we can just normally use this binding in layout to load the event thumbnail like this

<ImageView
   android:layout_width="@dimen/image_small"
   android:layout_height="@dimen/image_small"
   android:contentDescription="@string/event_thumbnail"
   app:circleImageUrl="@{event.thumbnailImageUrl}" />

 

This gives us a layout like this:

Next we need to load the header image with a deafult image.

Default Image Adapter

For this, we write a very simple adapter without CircleTransform

@BindingAdapter(value = { "imageUrl", "placeholder" }, requireAll = false)
public static void bindDefaultImage(ImageView imageView, String url, Drawable drawable) {
   if(TextUtils.isEmpty(url)) {
       if (drawable != null)
           imageView.setImageDrawable(drawable);
       return;
   }

   RequestCreator requestCreator = Picasso.with().load(Uri.parse(url));

   if (drawable != null) {
       requestCreator
           .placeholder(drawable)
           .error(drawable);
   }

   requestCreator
       .tag(MainActivity.class)
       .into(imageView);
}

 

As imageUrl or placeholder can be null, we check for both, and setting correct images if they are not. We use this in our header layout with both the url and default image we need to show:

<ImageView
   android:scaleType="centerCrop"
   app:imageUrl="@{ event.largeImageUrl }"
   app:placeholder="@{ @drawable/header }"
   android:contentDescription="@string/event_background" />

 

And this gives us a nice dynamic header like this:

This wraps up the blog on Picasso’s latest develop version and Binding Adapters. If you want to know more about Picasso and Android Data Binding, check these links:

Implementing Copyright API in Open Event Frontend

This article illustrates how the copyright details have been displayed in the Open Event Frontend project using the Open Event Orga API. The API endpoints which will be mainly focussing on for fetching the copyright details are:

GET /v1/event-copyright/{event_copyright_id}

The events have copyrights which give the creator of the event exclusive rights for its use and distribution. In the Open Event application, the copyright details can be seen on the public event page. The public event page contains the events details like description, venue, tickets, speakers, sponsors along with the copyright details and these details are present on the public/index route in the application. Apart from index route, we have multiple subroutes to display the detailed information of speakers, sessions and schedule. The one thing which remains common to all the public event pages is the copyright information. Most of the time the copyright details are event specific so they are nested within the event model so if we want to display them we need to fetch the event model first.

The code to fetch the event model looks like this:

model(params) {
return this.store.findRecord('event', params.event_id, { include: 'social-links, event-copyright' });
}

If we try to comprehend the code then we can see that ‘event-copyright’ details are included inside the model. The reason behind this is the fact that the copyright information is not specific to a particular route and is displayed on the all the public event pages. After fetching the copyright details the next step we need to perform is to display them on the event’s index page.

The code to display the copyright details looks like this:

{{#if model.event.copyright}}
  <div class="copyright">
    {{public/copyright-item copyright=model.event.copyright}}
  </div>
{{/if}}

In the first line, we have an if conditional statement which verifies whether the copyright data exists or not. If the data does not exist then the copyright class will not be visible on the page and if the model is not empty then it will be displayed with the help of model.event.copyright which is responsible for displaying the fetched data on the page.

If we see in the third line, we have called an another template ‘copyright-item’ which is responsible for how the data will look or in simpler words the UI of the copyright data.

The code which determines UI of the copyright details looks like this:

<img src="{{copyright.logoUrl}}" class="copyright-image" alt="{{copyright.licence}}">
<br>
<div class='copyright text'>
  <p>
    {{t 'This event is licenced under'}} <a href="{{copyright.licenceUrl}}"> {{copyright.licence}} </a>.
  </p>
</div>

In the first line of code, we are providing the src to the image which is stored in ‘logoUrl’ variable of the copyright object. If we hover the image we can see the copyright license which is stored in the ‘license’ variable. Then finally we have copyright license’s URL which is stored under ‘licenceUrl’ variable of copyright object. The resulting UI from the above source code looks like this :

Fig. 1: The user interface of the copyright details

Now we need to test whether the copyright details are completely displayed or not. To test it we created an integration test in which we created a sample ember object to check the correctness of the code. The sample ember object for copyright details looks like this:

To view the complete code regarding the copyright API integration check this.

const copyright = EmberObject.create({
  holder     : 'Creative Commons',
  holderUrl  : 'https://creativecommons.org',
  licence    : 'Public Domain Dedication (CC0)',
  licenceUrl : 'https://creativecommons.org/publicdomain/zero/1.0/',
  year       : 2007,
  logoUrl    : 'http://image.ibb.co/gt7q7v/pdd.png'
});

To conclude, this is how we integrated copyright information inside the Open Event Frontend project using the Open Event Orga API efficiently.

Resources:

Image Source : https://libsource.com/understanding-creative-commons-licensing/

Testing Errors and Exceptions Using Unittest in Open Event Server

Like all other helper functions in FOSSASIA‘s Open Event Server, we also need to test the exception and error helper functions and classes. The error helper classes are mainly used to create error handler responses for known errors. For example we know error 403 is Access Forbidden, but we want to send a proper source message along with a proper error message to help identify and handle the error, hence we use the error classes. To ensure that future commits do not mismatch the error, we implemented the unit tests for errors.

There are mainly two kind of error classes, one are HTTP status errors and the other are the exceptions. Depending on the type of error we get in the try-except block for a particular API, we raise that particular exception or error.

Unit Test for Exception

Exceptions are written in this form:

@validates_schema
    def validate_quantity(self, data):
        if 'max_order' in data and 'min_order' in data:
            if data['max_order'] < data['min_order']:
                raise UnprocessableEntity({'pointer': '/data/attributes/max-order'},
                                          "max-order should be greater than min-order")

 

This error is raised wherever the data that is sent as POST or PATCH is unprocessable. For example, this is how we raise this error:

raise UnprocessableEntity({'pointer': '/data/attributes/min-quantity'},

           "min-quantity should be less than max-quantity")

This exception is raised due to error in validation of data where maximum quantity should be more than minimum quantity.

To test that the above line indeed raises an exception of UnprocessableEntity with status 422, we use the assertRaises() function. Following is the code:

 def test_exceptions(self):
        # Unprocessable Entity Exception
        with self.assertRaises(UnprocessableEntity):
            raise UnprocessableEntity({'pointer': '/data/attributes/min-quantity'},
                                      "min-quantity should be less than max-quantity")


In the above code,
with self.assertRaises() creates a context of exception type, so that when the next line raises an exception, it asserts that the exception that it was expecting is same as the exception raised and hence ensures that the correct exception is being raised

Unit Test for Error

In error helper classes, what we do is, for known HTTP status codes we return a response that is user readable and understandable. So this is how we raise an error:

ForbiddenError({'source': ''}, 'Super admin access is required')

This is basically the 403: Access Denied error. But with the “Super admin access is required” message it becomes far more clear. However we need to ensure that status code returned when this error message is shown still stays 403 and isn’t modified in future unwantedly.

Here, errors and exceptions work a little different. When we declare a custom error class, we don’t really raise that error. Instead we show that error as a response. So we can’t use the assertRaises() function. However what we can do is we can compare the status code and ensure that the error raised is the same as the expected one. So we do this:

def test_errors(self):
        with app.test_request_context():
            # Forbidden Error
            forbidden_error = ForbiddenError({'source': ''}, 'Super admin access is required')
            self.assertEqual(forbidden_error.status, 403)

            # Not Found Error
            not_found_error = NotFoundError({'source': ''}, 'Object not found.')
            self.assertEqual(not_found_error.status, 404)


Here we firstly create an object of the error class
ForbiddenError with a sample source and message. We then assert that the status attribute of this object is 403 which ensures that this error is of the Access Denied type using the assertEqual() function, which is what was expected.
The above helps us maintain that no one in future unknowingly or by mistake changes the error messages and status code so as to maintain the HTTP status codes in the response.


Resources:

Checking Image Size to Avoid Crash in Android Apps

In Giggity app a user can create a shortcut for the event by clicking on “home shortcut” button in the navigation drawer. Open Event format provides the logo URL in the return data so we do not need to provide it separately in the app’s raw file.

Sometimes the image can be too big to be put on screen as icon for shortcut. In this blog I describe a very simple method to check if we should use the image or not to avoid the crash and pixelation due to resolution.

We can store the image received in bitmap format. A bitmap is a type of memory organization or image file format used to store digital images. The term bitmap comes from the computer programming terminology, meaning just a map of bits, a spatially mapped array of bits. By storing it in bitmap format we can easily get the necessary information about the image to check if it is suitable for use.

We can use the BitmapFactory class which provides several decoding methods like (decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating a Bitmap from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options class. Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.

So here is the particular example from Giggity app, it avoids crash on the recieving a large image for the icon. So once we store the the image in bitmap format we check if the height and width of the icon is exceeding the maximum limit.

public Bitmap getIconBitmap() {

 InputStream stream = getIconStream();
 Bitmap ret = null;

 if (stream != null) {
 ret = BitmapFactory.decodeStream(stream);
 if (ret == null) {
 Log.w("getIconBitmap", "Discarding unparseable file");
 return null;
 }
 if (ret.getHeight() > 512 || ret.getHeight() != ret.getWidth()) {
 Log.w("getIconBitmap", "Discarding, icon not square or >512 pixels");
 return null;
 }
 if (!ret.hasAlpha()) {
 Log.w("getIconBitmap", "Discarding, no alpha layer");
 return null;
 }
 }
 
 return ret;
}

If it does then we can avoid the icon. In this case we check if the icon is more than 512 pixels in height and width. If it is so then we could avoid it.

We could also check if the icon has a transparent background by using “hasAlpha” so we could have uniformity in the icons displayed on the screen. In the final result you can see the icon of the TUBIX 2017 conference added on the screen as it was following all those defined criterias.

Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:

  • Estimated memory usage of loading the full image in memory.
  • Amount of memory you are willing to commit to loading this image given any other memory requirements of your application.
  • Dimensions of the target ImageView or UI component that the image is to be loaded into.
  • Screen size and density of the current device.

For example, it’s not worth loading a 1024×768 pixel image into memory if it will eventually be displayed in a 128×96 pixel thumbnail in an ImageView.

 

References:

Upgrading the Style and Aesthetic of an Android App using Material Design

I often encounter apps as I add Open Event format support that don’t follow current design guidelines. Earlier styling an app was a tough task as the color and behaviour of the views needed to be defined separately. But now as we move forward to advanced styling methods we can easily style our app.

I recently worked on upgrading the user interface of Giraffe app after adding our Open Event support. See the repository to view the code for more reference. Here I follow the same procedure to upgrade the user interface.

First we add essential libraries to move with our material aesthetic. The Appcompat library provides backward compatibility.

//Essential Google libraries
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1

Then we define an XML file in the values folder for the style of the app which we get through Appcompat library. We could inherit same style in the entire app or separate style for the particular activity.

<resources>

   <!-- Base application theme. -->
   <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
       <!-- Customize your theme here. -->
       <item name="colorPrimary">@color/colorPrimary</item>
       <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
       <item name="colorAccent">@color/colorAccent</item>
   </style>


   <style name="AlertDialogCustom" parent="Theme.AppCompat.Light.Dialog.Alert">
       <item name="colorPrimary">@color/colorPrimary</item>
       <item name="colorAccent">@color/colorAccent</item>
   </style>

</resources>

So now we can see the views made following the same color scheme and behaviour throughout the app following current design guidelines without any particular manipulation to each of them.

Tip: Don’t define values of colors separately for different views. Define them in colors.xml to use them everywhere. It becomes easier then to change in future if needed.

The app now uses Action Bar for the frequently used operations unlike the custom layout that was made earlier.

This is how Action Bar is implemented,

First declare the action bar in XML layout,

Tip: Define color of the bar two shades lighter than the status bar.

 <android.support.design.widget.AppBarLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:background="@android:color/transparent"
             android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
             <android.support.v7.widget.Toolbar

                 xmlns:app="http://schemas.android.com/apk/res-auto"         
                 android:id="@+id/toolbar_options"
                 android:layout_width="match_parent"
                 android:layout_height="?attr/actionBarSize"
                 android:background="@color/colorPrimary"
                 app:popupTheme="@style/ThemeOverlay.AppCompat.Dark">
                 
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/options"
                     android:textColor="@color/colorAccent"
                     android:textSize="20sp" />
              </android.support.v7.widget.Toolbar>

</android.support.design.widget.AppBarLayout>

Then you can use the action bar in the activity, use onCreateOptionsMenu() method to inflate options in the toolbar.

@Override
    public void onCreate(Bundle savedInstanceState) {
        ...

        setTitle("");
        title = (TextView) findViewById(R.id.titlebar);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_main);
        setSupportActionBar(toolbar);

        ...
    }

The menu that needs to be inflated will be like this for two button at the right end of the action bar for bookmarks and filter respectively,

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
     <item
        android:id = "@+id/action_bookmark"
        android:icon = "@drawable/ic_bookmark"
        android:menuCategory = "secondary"
        android:title = "Bookmark"
        app:showAsAction = "ifRoom" />
 
     <item
         android:id = "@+id/action_filter"
         android:icon = "@drawable/ic_filter"
         android:menuCategory = "secondary"
         android:title = "Filter"
         app:showAsAction = "ifRoom" />
</menu>

To adapt the declared style further, Alert Dialogs are also modified to match the app’s theme, it’s style is defined along with the app’s style. See below

AlertDialog.Builder noFeedBuilder = new AlertDialog.Builder(context,R.style.AlertDialogCustom);
            noFeedBuilder.setMessage(R.string.main_no_feed_text)
                    .setTitle(R.string.main_no_feed_title)
                    .setPositiveButton(R.string.common_yes, new DialogInterface.OnClickListener() {
                  ...
            noFeedBuilder.show();

Here is an example of improvement, before and after we update the user interface and aesthetic of app in easy steps defined,

   

See this for all the changes made to step up the user interface of the app.

References:

 

Importing the Open Event format in Giraffe

Giraffe is a personal conference schedule tool for Android. Most conferences, bar camps and similar events offer their plan of sessions and talks in the iCal format for importing into your calendar. However importing a whole session plan into your standard calendar renders it pretty much useless for anything else. Giraffe allows users to import the schedule into a separate list giving you a simple overview on what happens on the conference. Besides the session, title, date and  time it also lists the speaker, location and description if available in the iCal URL. Sessions can be bookmarked and the list can be filtered by favourites and upcoming talks.

Recently I added the support for Open Event JSON format along with iCal. In this blog I describe the simple steps you need to follow to see the event that is created in the Open Event server in the Giraffe app. The initial steps are similar to Giggity app,

 1. Go to your event dashboard

2. Click on the export button.

3. Select sessions from the dashboard and copy the URL.

 

4. Click on the “Giraffe” button on the toolbar and paste the link in the box following. App will ask you to paste it when the first time you open it. Here the app loads the data and checks few initial character to see which kind of data is received. Find my other blog post to solve that problem here.

The app uses separate data models for iCal and JSON to store the informations received and then save them in SQL database for CRUD options. See the database activity here

   

5. Now you can see the sessions. Click on them to see more information or bookmark them if needed. The data is loaded from the database so when app is offline so we don’t need to worry about connection once the data is being loaded.

   

Resources:

 

Open Event Server: Testing Image Resize Using PIL and Unittest

FOSSASIA‘s Open Event Server project uses a certain set of functions in order to resize image from its original, example to thumbnail, icon or larger image. How do we test this resizing of images functions in Open Event Server project? To test image dimensions resizing functionality, we need to verify that the the resized image dimensions is same as the dimensions provided for resize.  For example, in this function, we provide the url for the image that we received and it creates a resized image and saves the resized version.

def create_save_resized_image(image_file, basewidth, maintain_aspect, height_size, upload_path,
                              ext='jpg', remove_after_upload=False, resize=True):
    """
    Create and Save the resized version of the background image
    :param resize:
    :param upload_path:
    :param ext:
    :param remove_after_upload:
    :param height_size:
    :param maintain_aspect:
    :param basewidth:
    :param image_file:
    :return:
    """
    filename = '{filename}.{ext}'.format(filename=get_file_name(), ext=ext)
    image_file = cStringIO.StringIO(urllib.urlopen(image_file).read())
    im = Image.open(image_file)

    # Convert to jpeg for lower file size.
    if im.format is not 'JPEG':
        img = im.convert('RGB')
    else:
        img = im

    if resize:
        if maintain_aspect:
            width_percent = (basewidth / float(img.size[0]))
            height_size = int((float(img.size[1]) * float(width_percent)))

        img = img.resize((basewidth, height_size), PIL.Image.ANTIALIAS)

    temp_file_relative_path = 'static/media/temp/' + generate_hash(str(image_file)) + get_file_name() + '.jpg'
    temp_file_path = app.config['BASE_DIR'] + '/' + temp_file_relative_path
    dir_path = temp_file_path.rsplit('/', 1)[0]

    # create dirs if not present
    if not os.path.isdir(dir_path):
        os.makedirs(dir_path)

    img.save(temp_file_path)
    upfile = UploadedFile(file_path=temp_file_path, filename=filename)

    if remove_after_upload:
        os.remove(image_file)

    uploaded_url = upload(upfile, upload_path)
    os.remove(temp_file_path)

    return uploaded_url


In this function, we send the
image url, the width and height to be resized to, and the aspect ratio as either True or False along with the folder to be saved. For this blog, we are gonna assume aspect ratio is False which means that we don’t maintain the aspect ratio while resizing. So, given the above mentioned as parameter, we get the url for the resized image that is saved.
To test whether it has been resized to correct dimensions, we use Pillow or as it is popularly know, PIL. So we write a separate function named getsizes() within which get the image file as a parameter. Then using the Image module of PIL, we open the file as a JpegImageFile object. The JpegImageFile object has an attribute size which returns (width, height). So from this function, we return the size attribute. Following is the code:

def getsizes(self, file):
        # get file size *and* image size (None if not known)
        im = Image.open(file)
        return im.size


As we have this function, it’s time to look into the unit testing function. So in unit testing we set dummy width and height that we want to resize to, set aspect ratio as false as discussed above. This helps us to test that both width and height are properly resized. We are using a creative commons licensed image for resizing. This is the code:

def test_create_save_resized_image(self):
        with app.test_request_context():
            image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg'
            width = 500
            height = 200
            aspect_ratio = False
            upload_path = 'test'
            resized_image_url = create_save_resized_image(image_url_test, width, aspect_ratio, height, upload_path, ext='png')
            resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1]
            resized_width, resized_height = self.getsizes(resized_image_file)


In the above code from
create_save_resized_image, we receive the url for the resized image. Since we have written all the unittests for local settings, we get a url with localhost as the server set. However, we don’t have the server running so we can’t acces the image through the url. So we build the absolute path to the image file from the url and store it in resized_image_file. Then we find the sizes of the image using the getsizes function that we have already written. This  gives us the width and height of the newly resized image. We make an assertion now to check whether the width that we wanted to resize to is equal to the actual width of the resized image. We make the same check with height as well. If both match, then the resizing function had worked perfectly. Here is the complete code:

def test_create_save_resized_image(self):
        with app.test_request_context():
            image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg'
            width = 500
            height = 200
            aspect_ratio = False
            upload_path = 'test'
            resized_image_url = create_save_resized_image(image_url_test, width, aspect_ratio, height, upload_path, ext='png')
            resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1]
            resized_width, resized_height = self.getsizes(resized_image_file)
            self.assertTrue(os.path.exists(resized_image_file))
            self.assertEqual(resized_width, width)
            self.assertEqual(resized_height, height)


In open event orga server, we use this resize function to basically create 3 resized images in various modules, such as events, users,etc. The 3 sizes are names – Large, Thumbnail and Icon. Depending on the one more suitable we use it avoiding the need to load a very big image for a very small div. The exact width and height for these 3 sizes can be changed from the admin settings of the project. We use the same technique as mentioned above. We run a loop to check the sizes for all these. Here is the code:

def test_create_save_image_sizes(self):
        with app.test_request_context():
            image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg'
            image_sizes_type = "event"
            width_large = 1300
            width_thumbnail = 500
            width_icon = 75
            image_sizes = create_save_image_sizes(image_url_test, image_sizes_type)

            resized_image_url = image_sizes['original_image_url']
            resized_image_url_large = image_sizes['large_image_url']
            resized_image_url_thumbnail = image_sizes['thumbnail_image_url']
            resized_image_url_icon = image_sizes['icon_image_url']

            resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1]
            resized_image_file_large = app.config.get('BASE_DIR') + resized_image_url_large.split('/localhost')[1]
            resized_image_file_thumbnail = app.config.get('BASE_DIR') + resized_image_url_thumbnail.split('/localhost')[1]
            resized_image_file_icon = app.config.get('BASE_DIR') + resized_image_url_icon.split('/localhost')[1]

            resized_width_large, _ = self.getsizes(resized_image_file_large)
            resized_width_thumbnail, _ = self.getsizes(resized_image_file_thumbnail)
            resized_width_icon, _ = self.getsizes(resized_image_file_icon)

            self.assertTrue(os.path.exists(resized_image_file))
            self.assertEqual(resized_width_large, width_large)
            self.assertEqual(resized_width_thumbnail, width_thumbnail)
            self.assertEqual(resized_width_icon, width_icon)

Resources:

Creating Unit Tests for File Upload Functions in Open Event Server with Python Unittest Library

In FOSSASIA‘s Open Event Server, we use the Python unittest library for unit testing various modules of the API code. Unittest library provides us with various assertion functions to assert between the actual and the expected values returned by a function or a module. In normal modules, we simply use these assertions to compare the result since the parameters mostly take as input normal data types. However one very important area for unittesting is File Uploading. We cannot really send a particular file or any such payload to the function to unittest it properly, since it expects a request.files kind of data which is obtained only when file is uploaded or sent as a request to an endpoint. For example in this function:

def uploaded_file(files, multiple=False):
    if multiple:
        files_uploaded = []
        for file in files:
            extension = file.filename.split('.')[1]
            filename = get_file_name() + '.' + extension
            filedir = current_app.config.get('BASE_DIR') + '/static/uploads/'
            if not os.path.isdir(filedir):
                os.makedirs(filedir)
            file_path = filedir + filename
            file.save(file_path)
            files_uploaded.append(UploadedFile(file_path, filename))

    else:
        extension = files.filename.split('.')[1]
        filename = get_file_name() + '.' + extension
        filedir = current_app.config.get('BASE_DIR') + '/static/uploads/'
        if not os.path.isdir(filedir):
            os.makedirs(filedir)
        file_path = filedir + filename
        files.save(file_path)
        files_uploaded = UploadedFile(file_path, filename)

    return files_uploaded


So, we need to create a mock uploading system to replicate this check. So inside the unittesting function we create an api route for this particular scope to accept a file as a request. Following is the code:

@app.route("/test_upload", methods=['POST'])
        def upload():
            files = request.files['file']
            file_uploaded = uploaded_file(files=files)
            return jsonify(
                {'path': file_uploaded.file_path,
                 'name': file_uploaded.filename})


In the above code, it creates an app route with endpoint test_upload. It accepts a request.files. Then it sends this object to the
uploaded_file function (the function to be unittested), gets the result of the function, and returns the result in a json format.
With this we have the endpoint to mock a file upload ready. Next we need to send a request with file object. We cannot send a normal data which would then be treated as a normal request.form. But we want to receive it in request.files. So we create 2 different classes inheriting other classes.

def test_upload_single_file(self):

        class FileObj(StringIO):

            def close(self):
                pass

        class MyRequest(Request):
            def _get_file_stream(*args, **kwargs):
                return FileObj()

        app.request_class = MyRequest


MyRequest
class inherits the Request class of Flask framework. We define the file stream of the Request class as the FileObj. Then, we set the request_class attribute of the Flask app to this new MyRequest class.
After we have it all setup, we need to send the request and see if the uploaded file is being saved properly or not. For this purpose we take help of StringIO library. StringIO creates a file-like class which can be then used to replicate a file uploading system. So we send the data as {‘file’: (StringIO(‘1,2,3,4’), ‘test_file.csv’)}. We send this as data to the /test_upload endpoint that we have created previously. As a result, the endpoint receives the function, saves the file, and returns the filename and file_path for the stored file.

 with app.test_request_context():
            client = app.test_client()
            resp = client.post('/test_upload', data = {'file': (StringIO('1,2,3,4'), 'test_file.csv')})
            data = json.loads(resp.data)
            file_path = data['path']
            filename = data['name']
            actual_file_path = app.config.get('BASE_DIR') + '/static/uploads/' + filename
            self.assertEqual(file_path, actual_file_path)
            self.assertTrue(os.path.exists(file_path))


After this is done, we need to check if the file_path that we receive is the expected file path that we should get. Secondly, we also check whether the file was really created or is this just some dummy data sent. We get the expected path by this:

actual_file_path = app.config.get('BASE_DIR') + '/static/uploads/' + filename.

Then we assert that actual_file_path is same as the resulting path we received using the assertEqual. Thirdly, we use assertTrue to ensure that there is a file in that path. That is,

self.assertTrue(os.path.exists(file_path))

Which gives a True if file exists or False if not.

So that basically sums up the unittesting.
1) If the file is saved in the correct path, and
2) The file actually exist
The the unittest passes only if both is True and is thus successful. Else we get either an error or a failure.

Following is the entire code snippet for this unit testing function:

def test_upload_single_file(self):

        class FileObj(StringIO):

            def close(self):
                pass

        class MyRequest(Request):
            def _get_file_stream(*args, **kwargs):
                return FileObj()

        app.request_class = MyRequest

        @app.route("/test_upload", methods=['POST'])
        def upload():
            files = request.files['file']
            file_uploaded = uploaded_file(files=files)
            return jsonify(
                {'path': file_uploaded.file_path,
                 'name': file_uploaded.filename})

        with app.test_request_context():
            client = app.test_client()
            resp = client.post('/test_upload', data = {'file': (StringIO('1,2,3,4'), 'test_file.csv')})
            data = json.loads(resp.data)
            file_path = data['path']
            filename = data['name']
            actual_file_path = app.config.get('BASE_DIR') + '/static/uploads/' + filename
            self.assertEqual(file_path, actual_file_path)
            self.assertTrue(os.path.exists(file_path))

Resources: