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:

 

Continue ReadingImporting the Open Event format in Giraffe

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:
Continue ReadingOpen Event Server: Testing Image Resize Using PIL and Unittest

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:
Continue ReadingCreating Unit Tests for File Upload Functions in Open Event Server with Python Unittest Library

Implementing Settings API on Open Event Frontend to View and Update Admin Settings

This blog article will illustrate how the admin settings are displayed and updated on the admin settings page in Open Event Frontend, using the settings API. It will also illustrate the use of the notification service to display corresponding notifications on whether the update operation is successful or not. Our discussion primarily will involve the admin/settings/index route to illustrate the process, all other admin settings route work exactly the same way.

The primary end point of Open Event API with which we are concerned with for fetching tickets for an event is

GET /v1/settings

Since there are multiple  routes under admin/settings  including admin/settings/index, and they all will share the same setting model, it is efficient to make the call for Event on the settings route, rather than repeating it for each sub route, so the model for settings route is:

model() {
 return this.store.queryRecord(setting, {});
}

It is important to note that, we need not specify the model for index route or in fact for any of the sub routes of settings.  This is because it is the default behaviour of ember that if the model for a route is not found, it will automatically look for it in the parent  route.  

And hence all that is needed to be done to make the model available in the system settings form  is to pass it while calling the form component.

<div class="ui basic {{if isLoading 'loading' ''}} segment">
 {{forms/admin/settings/system-form save='updateSettings' settings=model}}
</div>

Thus the model properties will be available in the form via settings alias. Next, we need to bind the value property  of the input fields to the corresponding model properties.  Here is a sample snippet on so as to how to achieve that, for the full code please refer to the codebase or the resources below.

<div class="field">
 {{ui-radio label=(t 'Development') current=settings.appEnvironment name='environment' value='development' onChange=(action (mut settings.appEnvironment))}}
</div>
<div class="field">
 {{ui-radio label=(t 'Staging') current=settings.appEnvironment name='environment' value='staging'}}
</div>
<div class="field">
 {{ui-radio label=(t 'Production') current=settings.appEnvironment name='environment' value='production'}}
</div>
<div class="field">
 <label>
   {{t 'App Name'}}
 </label>
 {{input type='text' name='app_name' value=settings.appName}}
</div>
<div class="field">
 <label>
   {{t 'Tagline'}}
 </label>
 {{input type='text' name='tag_line' value=settings.tagline}}
</div>

In the example above, appName, tagLine and appEnvironment are binded to the actual properties in the model. After the required changes have been done, the user next submits the form which triggers the submit action. If the validation is successful, the action updateSettings residing in the controller of the route is triggered, this is where the primary operations happen.

updateSettings() {
 this.set('isLoading', true);
 let settings = this.get('model');
 settings.save()
   .then(() => {
     this.set('isLoading', false);
     this.notify.success(this.l10n.t('Settings have been saved successfully.'));
   })
   .catch(()=> {
     this.set('isLoading', false);
     this.notify.error(this.l10n.t('An unexpected error has occured. Settings not saved.'));
   });
}

The controller action first sets the isLoading property to true. This adds the semantic UI class loading to the segment containing the form, and it and so the form goes in the loading state, to let the user know the requests is being processed. Then the save()  call occurs and this makes a PATCH request to the API to update the values stored inside the database. And if the PATCH request is successful, the .then() clause executes, which in addition to setting the isLoading as false.

However, in case there is an unexpected error and the PATCH request fails, the .catch() executes. After setting isLoading to false, it notifies the user of the error via an error notification.

Resources

Continue ReadingImplementing Settings API on Open Event Frontend to View and Update Admin Settings

Mailing Attachments Using Terminal in Open Event Android

The latest version of Open Event Android App Generator, v2 lacked the feature of mailing the generated APK to the email ID that is entered at the start of the app generation process. This also included mailing the error logs in case of APK failure.

This is an important feature for app generator because the process of app generation is a time taking one. The users have to wait for the app to be generated so that they can download the generated APK. To avoid this, the generator can automatically email the APK as soon as it is generated.

I took up this issue a few days back and started working on it. I started with thinking about the ways through which it will be implemented. This required some discussions with the mentors and co-developers. We finalised on the following ways:

  • Using Sendgrid
  • Using SMTP

I will be discussing the implementation of both of them in this blog. The code for APK mailing starts with the function call Notification.send in generator.py

if completed and apk_path and not error:
   Notification.send(
       to=self.creator_email,
       subject='Your android application for %s has been generated ' % self.event_name,
       message='Hi,<br><br>'
               'Your android application for the \'%s\' event has been generated. '
               'And apk file has been attached along with this email.<br><br>'
               'Thanks,<br>'
               'Open Event App Generator' % self.event_name,
       file_attachment=apk_path,
       via_api=self.via_api
   )
else:
   Notification.send(
       to=self.creator_email,
       subject='Your android application for %s could not generated ' % self.event_name,
       message='Hi,<br><br> '
               'Your android application for the \'%s\' event could not generated. '
               'The error message has been provided below.<br><br>'
               '<code>%s</code><br><br>'
               'Thanks,<br>'
               'Open Event App Generator' % (self.event_name, str(error) if error else ''),
       file_attachment=apk_path,
       via_api=self.via_api
   )

This leads me to the class Notification.py. It has three functions:-

1. send(to, subject, message, file_attachment, via_api)
2. send_mail_via_smtp(payload):
3. send_email_via_sendgrid(payload):

As the name suggests, the first function:

send(to, subject, message, file_attachment, via_api)

mainly decides which service (out of smtp and sendgrid) should be used to send the email, on the basis of the input parameters (especially, the ‘EMAIL_SERVICE’ parameter that has to be set in config.py).
The function looks like as follows:

send(to, subject, message, file_attachment, via_api)

It is in the send() that the other two functions are called. If the email_service is smtp, it calls the Notification.send_mail_via_smtp(payload). Otherwise, the Notification.send_email_via_sendgrid(payload) is called.
The sendgrid function is pretty straightforward:

@staticmethod
def send_email_via_sendgrid(payload):

   key = current_app.config['SENDGRID_KEY']
   if not key:
       logger.info('Sendgrid key not defined')
       return
   headers = {
       "Authorization": ("Bearer " + key)
   }
   requests.post(
       "https://api.sendgrid.com/api/mail.send.json",
       data=payload,
       headers=headers
   )

It requires a personalised sendgrid key which is accessed from the config.py file. Apart from that it handles some errors by giving logs in celery tasks. The main line in the function that initiates the email is a POST request made using the python library ‘requests’. The request is made as follows:

 requests.post(
       "https://api.sendgrid.com/api/mail.send.json",
       data=payload,
       headers=headers
   )

The send_mail_via_smtp(payload): function looks for some configurations before sending the mail:

@staticmethod
def send_mail_via_smtp(payload):
   """
   Send email via SMTP
   :param config:
   :param payload:
   :return:
   """
   smtp_encryption = current_app.config['SMTP_ENCRYPTION']
   if smtp_encryption == 'tls':
       smtp_encryption = 'required'
   elif smtp_encryption == 'ssl':
       smtp_encryption = 'ssl'
   elif smtp_encryption == 'tls_optional':
       smtp_encryption = 'optional'
   else:
       smtp_encryption = 'none'
   config = {
       'host': current_app.config['SMTP_HOST'],
       'username': current_app.config['SMTP_USERNAME'],
       'password': current_app.config['SMTP_PASSWORD'],
       'encryption': smtp_encryption,
       'port': current_app.config['SMTP_PORT'],
   }
   mailer_config = {
       'transport': {
           'use': 'smtp',
           'host': config['host'],
           'username': config['username'],
           'password': config['password'],
           'tls': config['encryption'],
           'port': config['port']
       }
   }

   mailer = Mailer(mailer_config)
   mailer.start()
   message = Message(author=payload['from'], to=payload['to'])
   message.subject = payload['subject']
   message.plain = strip_tags(payload['message'])
   message.rich = payload['message']
   message.attach(payload['attachment'])
   mailer.send(message)
   mailer.stop()

It is using the Marrow Mailer Python library to email with attachments(APK). This Python library can be installed using
pip install marrow.mailer
To use Marrow Mailer you instantiate a marrow.mailer.Mailer object with the configuration, then pass Message instances to the Mailer instance’s send() method.

You can refer to the following guides for more information about sending emails through command line:
https://github.com/marrow/mailer is the official repo of Marrow Mailer repository.
https://pypi.python.org/pypi/marrow.mailer
More detailled information on sending emails using Sendgrid can be found here https://www.daveperrett.com/articles/2013/03/19/setting-up-sendmail-with-sendgrid-on-ubuntu/

Continue ReadingMailing Attachments Using Terminal in Open Event Android

Dynamic Base URL Support in the Open Event Organizer App

Open Event API Server acts as a backend for Open Event Organizer Android App. The server has a development instance running on the web for developers. Developers use this instance to try out new feature additions, bug fixings and other such changes in the source code. And when confirmed working, these changes are updated to the main running instance which is kept live throughout for the users. Similarly for Android app developers, to test the app with both the instances, we have implemented the dynamic base URL support in the app. The app has a default base URL set to development instance or main instance dependent on the debug mode. That means the app will use a server on developer instance when used under debug mode and will use a main instance server if used under release mode. The app also provides an option to enter an alternate URL while login in the app which replaces default base URL in the app for the session.

In the organizer app, we are using Retrofit + Okhttp for handling network requests and dagger for dependency injection. The OkhttpClient provider in NetworkModule class looks like:

@Provides
@Singleton
OkHttpClient providesOkHttpClient(HostSelectionInterceptor interceptor) {
   return new OkHttpClient.Builder()
       .addNetworkInterceptor(new StethoInterceptor())
       .build();
}

 

Retrofit had a support for mutable base URL in the earlier versions but the feature is no longer available in the recent versions. We are using Interceptor class for changing base URL. The class has a method named intercept, which gets called at each network request. In this method, base URL is reset to the new URL.

So first you have to extend Interceptor class and reset base URL in the intercept method. The Interceptor class in the app looks like:

public final class HostSelectionInterceptor implements Interceptor {
   private String host;
   private String scheme;

   public HostSelectionInterceptor(){
       //Intentionally left blank
   }

   public void setInterceptor(String url) {
       HttpUrl httpUrl = HttpUrl.parse(url);
       scheme = httpUrl.scheme();
       host = httpUrl.host();
   }

   @Override
   public Response intercept(Chain chain) throws IOException {
       Request original = chain.request();

       // If new Base URL is properly formatted then replace the old one
       if (scheme != null && host != null) {
           HttpUrl newUrl = original.url().newBuilder()
               .scheme(scheme)
               .host(host)
               .build();
           original = original.newBuilder()
               .url(newUrl)
               .build();
       }
       return chain.proceed(original);
   }
}

 

The class has a private string field host to save base URL. The method setInterceptor is used to change the base URL. Once the base URL is changed, thereafter all the network requests use changed URL to call. So now our interceptor is ready which can be used to support dynamic base URL in the app. This interceptor is added to Okhttp builder using its method addInterceptor.

@Provides
@Singleton
HostSelectionInterceptor providesHostSelectionInterceptor() {
   return new HostSelectionInterceptor();
}

@Provides
@Singleton
OkHttpClient providesOkHttpClient(HostSelectionInterceptor interceptor) {
   return new OkHttpClient.Builder()
       .addInterceptor(interceptor)
       .addNetworkInterceptor(new StethoInterceptor())
       .build();
}

 

And now you are able to change base URL just by using the setInterceptor method of Interceptor class from anywhere in the app. And by then all the network calls use the updated base URL.

Application

I will show you here, how exactly this works in the Open Event Organizer app. On the login page, we have provided an option to enter an alternate base URL.

                                

We have kept a default URL checked. The default URL is set as per debug mode. This is done by setting the fields in the build.gradle. The code looks like:

buildTypes {
       release {
           ...
           buildConfigField "String", "DEFAULT_BASE_URL", '"https://www.eventyay.com/api/v1/"'
       }
       debug {
           buildConfigField "String", "DEFAULT_BASE_URL", '"https://open-event-dev.herokuapp.com/api/v1/"'
       }
   }

 

The field is used in the app as:

private final String DEFAULT_BASE_URL = BuildConfig.DEFAULT_BASE_URL;

 

On login, the loginPresenter calls setInterceptor method of the Interceptor to update the URL according to the user’s input. And the base URL is changed in the app for further network requests.

Links:
1. Gist link for Interceptor implementation code – https://gist.github.com/swankjesse/8571a8207a5815cca1fb
2. Google dagger dependency injector Github Repo
3. Retrofit http client Github Repo
4. Okhttp client Github Repo

Continue ReadingDynamic Base URL Support in the Open Event Organizer App

Displaying a Comments dialogfragment on a Button Click from the Feed Adapter in the Open Event Android App

Developing the live feed of the event page from Facebook for the Open Event Android App, there were questions how best to display the comments in the feed.  A dialog fragment over the feeds on the click of a button was the most suitable solution. Now the problem was, a dialogfragment can only be called from an app component (eg- fragment or an activity). Therefore, the only challenge which remained was to call the dialogfragment from the adapter over the feed fragment with the corresponding comments of the particular post on a button click.

What is a dialogfragment?

A dialogfragment displays a dialog window, floating on top of its activity’s window. This fragment contains a Dialog object, which it displays as appropriate based on the fragment’s state. Control of the dialog (deciding when to show, hide, dismiss it) should be done through the API here, not with direct calls on the dialog (Developer.Android.com).

Solution

The solution which worked on was to define a adapter callback interface with a onMethodCallback method in the feed adapter class itself with the list of comment items fetched at runtime on the button click of a particular post. The interface had to be implemented by the main activity which housed the feed fragment that would be creating the comments dialogfragment with the passed list of comments.

Implementation

Define an interface adapterCallback with the method onMethodCallback parameterized by the list of comment items in your adapter class.

public interface AdapterCallback {
   void onMethodCallback(List<CommentItem> commentItems);
}

 

Create a constructor of the adapter with the adapterCallback as a parameter. Do not forget to surround it with a try/catch.

public FeedAdapter(Context context, AdapterCallback adapterCallback, List<FeedItem> feedItems) {
     this.mAdapterCallback = adapterCallback;
}

 

On the click of the comments button, call onMethodCallback method with the corresponding comment items of a particular feed.

getComments.setOnClickListener(v -> {
   if(commentItems.size()!=0)
       mAdapterCallback.onMethodCallback(commentItems);
});

 

Finally implement the interface in the activity to display the comments dialog fragment populated with the corresponding comments of a feed post. Pass the comments with the help of arraylist through the bundle.

@Override
public void onMethodCallback(List<CommentItem> commentItems) {
   CommentsDialogFragment newFragment = new CommentsDialogFragment();
   Bundle bundle = new Bundle();
   bundle.putParcelableArrayList(ConstantStrings.FACEBOOK_COMMENTS, new ArrayList<>(commentItems));
   newFragment.setArguments(bundle);
   newFragment.show(fragmentManager, "Comments");
}

 

Conclusion

The comments generated with each feed post in the open event android app does complement the feed well. The pagination is something which is an option in the comments and the feed both however that is something for some other time. Until then, keep coding!

Resources

Continue ReadingDisplaying a Comments dialogfragment on a Button Click from the Feed Adapter in the Open Event Android App

Create an App Widget for Bookmarked Sessions for the Open Event Android App

What is an app widget?

App Widgets are miniature application views that can be embedded in other applications (such as the Home screen) and receive periodic updates. These views are referred to as Widgets in the user interface, and you can publish one with an App Widget provider. – (Android Documentation).

Android widget is an important functionality that any app can take advantage of. It could be used to show important dates, things that the user personalizes on the app etc. In the context of the Open Event Android App, it was necessary to create a bookmark widget for the Android phones so that the user could see his bookmarks on the homescreen itself and need not open the app for the same. In the open event android app, the widget was already created but it needed bug fixes and UI enhancements due to migration to the Realm database migration. Therefore, my majority of work circled around that.

Implementation

Declare the app widget in the manifest. All the updates in the application would be received by the class which extends the AppWidgetProvider if it needs to be reflected in the widget.

<receiver
   android:name=".widget.BookmarkWidgetProvider"
   android:enabled="true"
   android:label="Bookmarks">
   <intent-filter>
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
       <action android:name="${applicationId}.ACTION_DATA_UPDATED" />
       <action android:name="${applicationId}.UPDATE_MY_WIDGET" />
   </intent-filter>
   <meta-data
       android:name="android.appwidget.provider"
       android:resource="@xml/widget_info" />
</receiver>

 

Create a layout for the widget that is to be displayed on the homescreen. Remember to use only the views defined in the documentation. After the creation of the layout, create a custom widget updater which will broadcast the data from the app to the receiver to update the widget.

public class WidgetUpdater {
   public  static  void updateWidget(Context context){
       int widgetIds[] = AppWidgetManager.getInstance(context.getApplicationContext()).getAppWidgetIds(new ComponentName(context.getApplicationContext(), BookmarkWidgetProvider.class));
       BookmarkWidgetProvider bookmarkWidgetProvider = new BookmarkWidgetProvider();
       bookmarkWidgetProvider.onUpdate(context.getApplicationContext(), AppWidgetManager.getInstance(context.getApplicationContext()),widgetIds);
       context.sendBroadcast(new Intent(BookmarkWidgetProvider.ACTION_UPDATE));
   }
}

 

Next, create a custom RemoteViewService to update the views in the widget. The reason this is required is because the app widget does not operate in the usual lifecycle of the app. And therefore a remote service is required which acts as the remote adapter to connect to the remote views. In your class, override the onGetViewFactory() method and create a new remoteViewsFactory object to get the the data from the app on updation of the bookmark list. To populate the remote views, override the getViewsAt() method.

public class BookmarkWidgetRemoteViewsService extends RemoteViewsService {

@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {

return new RemoteViewsFactory() {
   private MatrixCursor data = null;

   @Override
   public void onCreate() {
       //Called when your factory is first constructed.
   }

   @Override
   public void onDataSetChanged() {
       }

   @Override
   public RemoteViews getViewAt(int position) {
       } 
   }
}

 

Finally, create a custom AppWidgetProvider which parses the relevant fields out of the intent and updates the UI. It acts like a broadcast receiver, hence all the updates by the widgetUpdater is received here.

public class BookmarkWidgetProvider extends AppWidgetProvider {

   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
	  RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.bookmark_widget);
             setRemoteAdapter(context, views);

   }

   @Override
   public void onReceive(@NonNull Context context, @NonNull Intent intent) {
       super.onReceive(context, intent);
   }

   private void setRemoteAdapter(Context context, @NonNull final RemoteViews views) {
       views.setRemoteAdapter(R.id.widget_list,
               new Intent(context, BookmarkWidgetRemoteViewsService.class));
   }

}

 

Conclusion

For any event based apps, it is crucial that it regularly provide updates to its users and therefore app widget forms an integral part of that whole experience.

References

 

 

Continue ReadingCreate an App Widget for Bookmarked Sessions for the Open Event Android App

Implementing Sponsors API in Open Event Frontend to Display Sponsors

This article will illustrate how the sponsors have been displayed on the public event page in Open Event Frontend using the Open-Event-Orga sponsors API. As we know that the project is an ember application so, it uses Ember data to consume the API. For fetching the sponsors, we would be mainly focusing on the following API endpoint:

GET /v1/events/{event_identifier}/sponsors

 

In the application we need to display the sponsors is the event’s public page which contains the event details, ticketing information, speaker details etc. along with the list of sponsors so, we will be only concerned with the public/index route in the application. As the sponsors details are nested within the events model so we need to first fetch the event and then from there we need to fetch the sponsors list from the model.

The model to fetch the event details looks like this:

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

 

But we can easily observe that there is no parameter related to sponsor in the above model. The reason behind this is the fact that we want our sponsors to be displayed only on the event’s index route rather than displaying them on all the sub routes under public route. To display the sponsors on the public/index route our modal looks like this:

model() {
    const eventDetails = this._super(...arguments);
    return RSVP.hash({
      event   : eventDetails,
      sponsors: eventDetails.get('sponsors')
   });
}

 

As we can see in the above code that we have used this._super(…arguments) to fetch the event details from the event’s public route model which contains all the information related to the event thereby eliminating the need of another API call to fetch sponsors. Now using the ember’s get method we are fetching the sponsors from the eventDetails and putting it inside the sponsors JSON object for using it lately to display sponsors in public/index route.

Till now, we’ve fetched and stored the sponsors now our next task is to display the sponsors list on the event’s index page. The code for displaying the sponsors list on the index page is

{{public/sponsor-list sponsors=model.sponsors}} 

 

The sample user interface without  for displaying the sponsors looks like this:  

Fig. 1: The sample user interface for displaying the sponsors

After replacing the real time data with the sample one, the user interface (UI) for the sponsors looks like this.

Fig. 2: The user interface for sponsors with real time data

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

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

Resources:

Continue ReadingImplementing Sponsors API in Open Event Frontend to Display Sponsors

Presenter Abstraction Layer in Open Event Organizer Android App

Open Event Organizer App design follows Model View Presenter (MVP) architecture which enables heavy unit testing. MVP is a trending architecture design followed these days. If you are not aware of MVP architecture, then please refer any of the tutorial (few links are given at the end of this blog) about it before reading this. In the design, the code becomes little repetitive as the application size increases due to so many presenters and views, which degrades the code readability. So to avoid this and keep the functionality code clean in the App, we have created a Presenter Abstraction Layer which contains the repetitive code and the layer is extended wherever required in the app. I will be talking about the Presenter Abstraction Layer implementation through the App in this blog.

First of all, create a base interface. The base interface contains methods which every presenter will have. The base interface for presenter in the App looks like:

public interface IBasePresenter {
   void start();
   void detach();
}

 

In the method start, presenter loads all the required data from the model and sends it to the view. And releases all the resources in detach. These two methods are required in all the presenters. This interface is extended by other two interfaces which will be actually used by the views. The relevant code is:

public interface IPresenter<V> extends IBasePresenter {
   void attach(V view);
}

public interface IDetailPresenter<K, V> extends IBasePresenter {
   void attach(K key, V view);
}

 

Method attach is used to attach view and the data id (if required) to the presenter. In the app, most of the presenters require an extra data which is used in loading data from the model. Hence two interfaces are extended from the base interface. Now comes the implementation part.

public abstract class BasePresenter<V> implements IPresenter<V> {
   private V view;
   private CompositeDisposable compositeDisposable;

   @Override
   @CallSuper
   public void attach(V view) {
       this.view = view;
       this.compositeDisposable = new CompositeDisposable();
   }

   @Override
   @CallSuper
   public void detach() {
       view = null;
       compositeDisposable.dispose();
   }

   protected V getView() {
       return view;
   }

   protected CompositeDisposable getDisposable() {
       return compositeDisposable;
   }
}

 

The App uses ReactiveX Observables for async operations which contain fragment/activity context hence these need to be disposed at some lifecycle of fragment/activity. detach nulls the view and disposes the compositeDisposable. This method is called at the onStop lifecycle of fragment/activity. The observable subscriptions are one of the major reasons for memory leaks if not disposed at correct lifecycle in Android. So the detach method is called at onStop lifecycle when activity goes into background or fragment is switched by FragmentTransaction. Another base presenter class looks like:

public  abstract class BaseDetailPresenter<K, V> extends BasePresenter<V> implements IDetailPresenter<K, V> {
   private K id;

   @Override
   @CallSuper
   public void attach(K id, V view) {
       super.attach(view);
       this.id = id;
   }

   protected K getId() {
       return id;
   }
}

 

This class extends the previous one except for the attach method. As the presenters extending this, require an extra data id which is passed through this method. So the id can be used in the presenter extending this class using getId. The presenters in the app extend one of these two classes. This helps in making a firm app structure and the development process easier. Abstraction layer should be used wherever same code is repeated. This increases code readability and decreases the chances of creating bugs especially when a team is working on the same project.

Links:
1. MVP for Android: how to organize the presentation layer, by Antonio Leiva
2. Android Code That Scales, With MVP, by Nathan Barraille
3. Ted Mosby – Software Architect, by Hannes Dorfmann

Continue ReadingPresenter Abstraction Layer in Open Event Organizer Android App