Using wrapper div around HTML buttons to add extra functionality in Open Event Server

Open Event server had a bug wherein clicking on the notification of an invitation caused a server error. When invitations for a role in an event were sent, they showed up in the notifications header. Clicking on the notification there took the user to the notification page where there were options to Accept or Decline the invitation. The bug was that when the user clicked on either of the Accept/Decline button, the notification was not being marked read which semantically it should have been. Since the invite link expires after acceptance/decline, due to the persistence of the invitation in the notifications page, when the user clicked on the Accept/Decline button, it ran into a 404 error.

The Accept/Decline buttons already have a href attached to each one of them which triggered functions of invitation manager class. The aim here was to add one other thing to happen when any of these buttons was clicked. This bug was resolved by adding a wrapper around these buttons and adding the same functionality to this as that of the ‘Mark as Read’ button.

Adding a class to both the buttons

<a href='{accept_link}' class='btn btn-success btn-sm invite'>Accept</a>
<a href='{decline_link}' class='btn btn-danger btn-sm invite'>Decline</a>


Adding JavaScript to the invite button

if ($(e.target).is('.invite')) {
            var read_button = $(e.target).parents(".notification").find('a.read-btn');
            $.getJSON(read_button.attr('href'), function (data) {
                       read_button.parents('.notification').removeClass('info'); // show notification as read
                read_button.remove(); // delete mark as read button
});
Using parseInt() with Radix

Another error [comment] in the same issue was that sometimes the notification count went in negatives. This was resolved by adding a simple clause to check when notification count is greater than 0.

notif_count = ((notif_count - 1) > 0 ) ? (notif_count - 1) : 0;

 

To set count as the innerHTML of a div, which in this case was the notification count bubble, one uses parseInt();

div.innerHTML = parseInt(notif_count);

This might work but codacy gives an error. The error here is because of a radix not being passed to the parseInt() function.

What is a radix?
Radix simply denotes the integer value of the base of the numeration system. It is basically the value of a single digit in a given number.

For example, numbers written in binary notation have radix 2 while those written in octal notation have radix 8.

Passing radix to the parseInt() function specifies the number system in which the input is to be parsed. Though the radix can be hinted at by other means too, it is always a good practice to pass the radix explicitly.

// leading 0 => radix 8 (octal) 
var number = parseInt('0101');
// leading ‘0x’ => radix 16 (hexadecimal) 
var number = parseInt('0x0101');
// Numbers starting with anything else assumes a radix of 10 
var number = parseInt('101');
// specifying the radix explicitly, here radix of 2 => (binary) 
var number = parseInt('0101', 2);


If you ignore this argument, parseInt() will try to choose the most proper numeral system, but this can back-fire due to browser inconsistencies. For example:

parseInt("023");  // 23 in one browser (radix = 10)
parseInt("023");  // 19 in other browser (radix = 8)


Providing the radix is vital if you are to guarantee accuracy with variable input (basic range, binary, etc). This can be ensured by using a JavaScript linter for your code, which will throw an error for unintended results.

Issues :
Error on clicking on notification

Pull Request :
Fix error on clicking on notification

Additional Resources:

Continue ReadingUsing wrapper div around HTML buttons to add extra functionality in Open Event Server

CSS Trick: How to Object-fit an Image inside its Container Element in the Open Event Front-end

I came across this piece of css when the Nextgen Conference Logo on eventyay home-page had its aspect ratio not maintained. As you can see in this picture that the image is stretched to fill in the parent container’s size.

The CSS behind this image was:

.event-holder img {
    width: 100%;
    height: 165px;
    border: none;
}

Let’s see how object fit helped me to fix this problem.

What is object-fit ?

The object-fit property of an element describes how it is fitted or placed inside its container element. This container box has its boundaries defined by the max-height and max-width attribute of the object in question.
In the html code for the above logo we had:

img {
    width: 100%;
    height: 165px; 
}

The object in this example is an image ( img ) which is to be fitted inside a box of height 165px with 100% width.

The object-fit property can refer to any element like video or embedded item in the page, but it’s mostly applied to images.

object-fit provides us with fine grained control over how the object resizes to fill inside its container div. Essentially object-fit lets the image ( in this context, but can be applied to any object ) fill the box withmaintaining aspect ratio and/or filling up the entire area established by height and weight.

Here’s a short example for different values of this attribute:
( The image
 used here is a 4096px*2660px image, placed inside a div of height 100px and width 300px. )

 
  object-fit: fill
   src="download.png" class="fill"/>
  object-fit: contain
   src="download.png" class="contain"/>
  object-fit: cover
   src="download.png" class="cover"/>
  object-fit: none
   src="download.png" class="none"/>
  object-fit: scale-down
   src="download.png" class="scale-down"/>

img {
  width: 300px;
  height: 100px;
  border: 1px solid yellow;
  background: blue;
}
.fill {
  object-fit: fill;
}
.contain {
  object-fit: contain;
}
.cover {
  object-fit: cover;
}
.none {
  object-fit: none;
}
.scale-down {
  object-fit: scale-down;
}

As from the above illustration, it is evident that what I needed to fix aspect ratio on home page was to use object-fit: cover. We got this result by just adding one line of code. Here’s the final CSS:

.event-holder img {
    width: 100%;
    height: 165px;
    object-fit: cover;
    border: none;
}

 

And the final image, which is pleasing and aesthetic:


Quick cheat-sheet for object-fit values

fill

  • stretches the image to fit the content box
  • aspect-ratio disregarded

contain

  • increases or decreases the size of the image to fill the box
  • aspect-ratio preserved

cover

  • fill the height and width of box
  • aspect ratio preserved
  • often the image gets cropped

none

  • height and width of the container box ignored
  • image retains its original size

scale-down

  • image takes smallest concrete object size between none and contain

Additional Resources

Continue ReadingCSS Trick: How to Object-fit an Image inside its Container Element in the Open Event Front-end

Creating a Responsive Menu in Open Event Frontend

Open Event Frontend uses semantic ui for creating responsive HTML components, however there are some components that are not responsive in nature like buttons & menus. Therefore we need to convert tabbed menus to a one column dropdown menu in mobile views. In this post I describe how we make menus responsive. We are creating a semantic UI custom styling component in Ember to achieve this.

In Open Event we are using the tabbed menus for navigation to a particular route as shown below.

Menu (Desktop)

As you can see there is an issue when viewing the menu on mobile screens.

Menu (Mobile)

Creating custom component for menu

To make the menu responsive we created a custom component called tabbed-navigation which converts the horizontal menu into a vertical dropdown menu for smaller screens. We are using semantic ui styling components for the component to implement the vertical dropdown for mobile view.

tabbed-navigation.js

currentRoute: computed('session.currentRouteName', 'item', function() {
  var path = this.get('session.currentRouteName');
  var item = this.get('item');
  if (path && item) {
    this.set('item', this.$('a.active'));
    this.$('a').addClass('vertical-item');
    return this.$('a.active').text().trim();
  }
}),
didInsertElement() {
  var isMobile = this.get('device.isMobile');
  if (isMobile) {
    this.$('a').addClass('vertical-item');
  }
  this.set('item', this.$('a.active'));
},
actions: {
  toggleMenu() {
    var menu = this.$('div.menu');
    menu.toggleClass('hidden');
  }
}

In the component we check if the device is mobile & change the classes accordingly. For mobile devices we add the vertical-item class to all the items in the menu. We set a property called item in the component which stores the selected item of the menu.

We add a computed property called currentRoute which observes the current route and the selected item, and sets the item to currently active route, and returns the name of the current route.

We add an action toggleMenu which is used to toggle the display of the vertical menu for mobile devices.

tabbed-navigation.hbs

We add a vertical menu dynamically for mobile devices with a button and the name of the current selected item which is stored in currentRoute variable, we also toggle between horizontal & vertical menu based on the screen size.

{{#if device.isMobile}}
  <div role="button" class="ui segment center aligned" {{action 'toggleMenu'}}>
    {{currentRoute}}
  </div>
{{/if}}
<div role="button" class="mobile hidden ui fluid stackable {{unless isNonPointing (unless device.isMobile 'pointing')}} {{unless device.isMobile (if isTabbed 'tabular' (if isVertical 'vertical' 'secondary'))}} menu" {{action 'toggleMenu'}}>
  {{yield}}
</div>

tabbed-navigation.scss

.tabbed-navigation {
  .vertical-item {
    display: block !important;
    text-align: center;
  }
}

Our custom component must look like an item of the menu, to ensure this we use display block property which will allow us to place the menu appear below the toggle button. We also center the menu items so that it looks more like a vertical dropdown.

{{#tabbed-navigation}}
  {{#link-to 'events.view.index' class='item'}}
    {{t 'Overview'}}
  {{/link-to}}
  {{#link-to 'events.view.tickets' class='item'}}
    {{t 'Tickets'}}
  {{/link-to}}
  <a href="#" class='item'>{{t 'Scheduler'}}</a>
  {{#link-to 'events.view.sessions' class='item'}}
    {{t 'Sessions'}}
  {{/link-to}}
  {{#link-to 'events.view.speakers' class='item'}}
    {{t 'Speakers'}}
  {{/link-to}}
  {{#link-to 'events.view.export' class='item'}}
    {{t 'Export'}}
  {{/link-to}}
{{/tabbed-navigation}}

To use this component all we need to do is wrap our menu inside the tabbed-navigation component and it will convert the horizontal menu to the vertical menu for mobile devices.

The outcome of this change on the Open Event Front-end now looks like this:

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

Resources

Continue ReadingCreating a Responsive Menu in Open Event Frontend

Handling soft and hard deletes in the Open Event server API

Really, handling soft and hard deletes can be a mess, if you think of it.

Earlier in the Open Event server project, we had a Boolean field called is_trashed which was set to true if a record was soft-deleted. That worked just fine, until there came a requirement to get the time at which the record was deleted. So duh… we added another column called deleted_at which would store the time at which the record was soft-deleted. And it all started working fine again.

But, shortly we realised it was bad design to have a redundant Boolean field is_trashed. So it was decided to remove the is_trashed field and only keep the deleted_at column at all places. If the deleted_at field contained a date, it would mean that the record has been soft deleted at that point of time. If the field was still NULL, then the record has not been soft deleted. That ends up the database aspect of implementing soft-deletes. Let’s move on to the API part then.

We are currently in the process of decoupling our front-end and back-end. And the API server for the same is in active development. We’ve been using flask-rest-jsonapi for the same purpose. So, the first thing that popped up in our minds, when we got around handling soft-deletes was the following.

Should the API framework implement soft-deletes for each API by itself, or should the individual API logic take care of it ?

After some discussion, it was decided to let the framework handle it for each API, so that the implementation remains uniform and obviously a little less headache for the developers. In our custom copy of flask-rest-jsonapi, we also added an option to turn off the soft deletes across the whole API. Turning it off for each resource is also in our road map and would be soon implemented in the future.

Now talking about the API itself, for GET endpoints by default soft-deleted records should not be retrieved. Retrieving all the records irrespective of whether it is soft-deleted or not and letting client figure out which records are deleted is a sign of bad design. If the client wants to retrieve the deleted records, it can do so by passing a query parameter is_trashed set to true.

Following is the URL pattern followed for the same, for the sake of the example, assume that the event with id 1 is soft-deleted:

GET /events?with_trashed=true   # get all events including the soft-deleted events
GET /events/1                       # send a 404 exception
GET /events/1?with_trashed=true # retrieve relevant data 

For DELETE request:

DELETE /events/1                 # soft-delete the event
DELETE /events/1?permanent=true   # hard-delete the event

Relevant links:

Continue ReadingHandling soft and hard deletes in the Open Event server API

Integrating Selenium Testing in the Open Event Webapp

Enter Selenium. Selenium is a suite of tools to automate web browsers across many platforms. Using Selenium, we can control the browser and instruct it to do an ‘action’ programmatically. We can then check whether that action had the appropriate reaction and make our test cases based on this concept. There are various implementations of Selenium available in many different languages: Java, Ruby, Javascript, Python etc. As the main language used in the project is Javascript, we decided to use it.

https://www.npmjs.com/package/selenium-webdriver
https://seleniumhq.github.io/selenium/docs/api/javascript/index.html

After deciding on the framework to be used in the project, we had to find a way to integrate it in the project. We wanted to run the tests on every PR made to the repo. If it failed, the build would be stopped and it would be shown to the user. Now, the problem was that the Travis doesn’t natively support running Selenium on their virtual machine. Fortunately, we have a company called Sauce Labs which provides automated testing for the web and mobile applications. And the best part, it is totally free for open source projects. And Travis supports Sauce Labs. The details of how to connect to Sauce Labs is described in detail on this page:

https://docs.travis-ci.com/user/gui-and-headless-browsers/

Basically, we have to create an account on Sauce Labs and get a sauce_username and sauce_access_key which will be used to connect to the sauce cloud. Travis provides a sauce_connect addon which creates a tunnel which allows the Sauce browsers to easily access our application. Once the tunnel is established, the browser in the Sauce cloud can use it to access the localhost where we serve the pages of the generated sites. A little code would make it more clear at this stage:

Here is a short excerpt from the travis.yml file :-

addons:
 sauce_connect:
   username: princu7
 jwt:
   secure: FslueGK2gtPHkRANMpUlGyCGsr1jTVuaKpP+SvYUxBYh5zbz73GMq+VsqlE29IZ1ER1+xMfWuCCvg3VA7HePyN6hzoZ/t0LADureYVPur6R5ZJgqgQpBinjpytIjo2BhN3NqaNWaIJZTLDSAT76R7HuNm01=

As we can see from the code, we have installed the sauce_connect addon and then added the sauce_username and sauce_access_key which we got when we registered on the cloud. Now, what is this gibberish we are seeing? Well, that is actually the sauce_access_key. It is just in its encrypted form. Generally, it is not a good practice to show the access keys in the source code. Anyone else can then use it and can cause harm to the resources allocated to us. You can read all about encrypting environment variables and JWT (JSON Web Tokens) here:-

https://docs.travis-ci.com/user/environment-variables/
https://docs.travis-ci.com/user/jwt

So, this sets up our tunnel to the Sauce Cloud. Here is one of the screenshots showing that our tunnel is opened and tests can be run through it.

sauce.png

After this, our next step is to make our test scripts run in the Sauce Cloud through the tunnel. We already use a testing framework mocha in the project. We can easily use mocha to run our client-side tests too. Here is a link to study it in a little more detail:

http://samsaccone.com/posts/testing-with-travis-and-sauce-labs.html

This is a short excerpt of the code from the test script

describe("Running Selenium tests on Chrome Driver", function() {
 this.timeout(600000);
 var driver;
 before(function() {
   if (process.env.SAUCE_USERNAME !== undefined) {
     driver = new webdriver.Builder()
       .usingServer('http://'+    process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+'@ondemand.saucelabs.com:80/wd/hub')
       .withCapabilities({
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
         build: process.env.TRAVIS_BUILD_NUMBER,
         username: process.env.SAUCE_USERNAME,
         accessKey: process.env.SAUCE_ACCESS_KEY,
         browserName: "chrome"
       }).build();
   } else {
     driver = new webdriver.Builder()
       .withCapabilities({
         browserName: "chrome"
       }).build();
   }
 });

 after(function() {
   return driver.quit();
 });

 describe('Testing event page', function() {

   before(function() {
     eventPage.init(driver);
           eventPage.visit('http://localhost:5000/live/preview/a@a.com/FOSSASIASummit');
   });

   it('Checking the title of the page', function(done) {
     eventPage.getEventName().then(function(eventName) {
       assert.equal(eventName, "FOSSASIA Summit");
       done();
     });
   });
 });
});

Without going too much into the detail, I would like to offer a brief overview of what is going on. At a high level, before starting any tests, we are checking whether the test is being run in a Travis environment. If yes, then we are appropriately setting up the webdriver for it to run on the Sauce Cloud through the tunnel which we opened previously. We also specify the browser on which we would like to run, which in this care here, is Chrome.

After this preliminary setup is done, we move on to the actual tests. Currently, we only have a basic test to check whether the title of the event site generated is correct or not. We had generated FOSSASIA Summit in the earlier part of the test script. So we just run the site and check its title which should obviously be ‘FOSSASIA Summit’. If due to some error it is not the case, then an error will be thrown and the Travis build we fail. Here is the screenshot of a successful passing test:

6c0a0775-6ad4-45f9-bfa1-a8baef4e6401.png

More tests will be added over the upcoming weeks.

Resources:

Continue ReadingIntegrating Selenium Testing in the Open Event Webapp

Using AutoCompleteTextView for interactive search in Open Event Android App

Providing a search option is essential in the Open Event Android app to make it easy for the user to see the desired results only. But sometimes it becomes difficult to implement this with a good performance if the data set is large, so providing simply a list to scroll through may not be enough and efficient. AutoCompleteTextView provides a way to search data by offering the suggestions after a user types in some initial letters of the search query.

How does it work? Actually we feed the data to an adapter which is attached to the view. So, when a user starts typing the query the suggestions starts appearing with similar names in the form of the list.

For example see above. Typing “Hall” gives the user suggestion to pick up the entry which have word “Hall” in it. Making it easier for user to search.

Let’s see how to implement it. For the first step declare the view in XML layout like this. Where our view goes by the id “map_toolbar” and white text colour for the text that will be appearing in it. Input type signifies that the autocomplete and auto correct is enabled.

<AutoCompleteTextView
       android:id="@+id/map_toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:ems="12"
       android:hint="@string/search"
       android:shadowColor="@color/white"
       android:imeOptions="actionSearch"
       android:inputType="textAutoComplete|textAutoCorrect"
       android:textColorHint="@color/white"
       android:textColor="@color/white"
/>

Now initialise the adapter in the fragment/activity with the list “searchItems” containing the information about the location. This function is in a fragment so modifying things accordingly. “textView” is the AutoCompleteTextView that we initialised. To explain this function further when a user clicks on any item from the suggestions the soft keyboard hides. You can do define desired operation here. 

Setting up AutoCompleteTextView with the locations

ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_dropdown_item_1line, searchItems);
textView.setAdapter(adapter);

textView.setOnItemClickListener((parent, view, position, id) -> {

Things you want to do on clicking the item

View mapView = getActivity().getCurrentFocus();

if (mapView != null) {
  InputMethodManager imm =     (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
  imm.hideSoftInputFromWindow(mapView.getWindowToken(), 0);
}
});

See the complete code here to find the implementation of AutoCompleteTextView in the map fragment of Open Event Android app.

Continue ReadingUsing AutoCompleteTextView for interactive search in Open Event Android App

The Joy of Testing with MVP in Open Event Orga App

Testing applications is hard, and testing Android Applications is harder. The natural way an Android Developer codes is completely untestable. We are born and molded into creating God classes – Activities and perform every bit of logic inside them. Some thought that introduction to Fragments will introduce a little bit of modularity, but we proved otherwise by shifting to God Fragments. When the natural urge of an Android Developer to

  • apply logic,
  • load UI,
  • handle concurrency (hopefully, not using AsyncTask),
  • load data – from the network; disk; and cache,
  • and manage the state of the Activity/Fragment

finally, meets with a new form of revelation that he/she should test his/her application, all of the concepts acquired are shattered in a moment. The person goes to Android Docs to see why he started coding that way and realizes that even the system is flawed – Android Documentation is full of examples promoting God Activities, which they have used to show the use of API for only one reason – brevity. It’s not uncommon for us to steal some code from StackOverflow or Android Docs, but we don’t realize that they are presented there without an application environment or structure, all the required component just glued together to get it functionally complete, so that reader does not have to configure it anymore. Why would an answer from StackOverflow load a barcode scanner using a builder? Or build a ContentProvider to show how to query sorted data from SQLite? Or use a singleton for your provider classes? The simple answer is, they won’t. But this doesn’t mean you shouldn’t too.

The first thought that enters developer’s mind when he gets his hand on a piece of code is to paste it in the correct position and get it to work. There is always this moment of hesitation where conscience rings a bell saying, “What are you doing? Is it the right way to do it? How many lines till you stop overloading this Activity? It’s 2000 lines already”. But it dies as soon as we see the feature is working. And why test something which we have confirmed to work, right? I just wrote this code to show a progress bar while the data loads and hide it when it is done. I can see this working, what will I achieve in painfully writing a test for it, mocking the loading conditions and all. Wrong! Unit tests which test your trivial utils like Date Modification, String Parsing, etc are good and needed but are so trivial that they are hard to go wrong, and if they are, they are easy to fix as they have single usage throughout the app, and it is easy to spot bugs and fix them.

The real problem is testing of your app over dynamic conditions, where you have to emulate them so you can see if your app is making the right decisions. The progress bar working example may work for now, but what if it breaks over refactoring, or you wrote the same code elsewhere and forget to hide the progress bar? Simply copying a well-written test and changing 2-3 class names will fail the build and tell you what’s wrong. A well-contracted app can even contain tests to check that there’ll be no memory leaks. But none of this is possible if everything is jumbled into single Activity with callbacks, loaders, UI handling, business logic, etc. You can’t even think that where to begin. In this post, I will briefly discuss, how to design and test applications using MVP pattern.

MVP to the Rescue

Before moving ahead, I must put a disclaimer saying that MVP is a design pattern which follows a very opinionated implementation. The extent to which you want to refactor your app and the number of abstractions you are willing to do are in your hand. There aren’t any golden rules where your implementation will fail to be called as MVP. Remember, the client doesn’t care about architecture, it’s for you, so whatever makes your life and testing easier, works. Also, I’ll use an axiom related to testing here, “Test until fear turns into boredom”. You can apply the same to your MVP implementation. Testing and design patterns are here to eradicate your fear of failure, not to bore you.

So, in this guide, I’ll be using a use case of a QR Code Scanner Activity built using MVP pattern which we have employed in Open Event Orga Application (Github Repo). Because we are focusing on the test pattern and how to make it easy for us to test our app logic, I have omitted the model part from the MVP equation. The reason being that the possible model in the application would have been the camera loader or barcode initializer and the caveats associated with following this is that both these modules rely heavily on the Android specific view classes, namely, SurfaceView and other lifecycle methods. You could always create your way around it to include them in a separate model, but it won’t help us in writing unit tests for them, because firstly, they aren’t our logic to test, and secondly, they can’t be tested in a unit test (those models would have probably just implemented certain setters and getters).

So, the main purpose of our QR Code Scanner class is to scan a QR code and match it with a list of identifiers, and if there is a match, return it successfully to the caller. In this specific example, the identifiers will the ticket IDs of event attendees and the caller will be event organizer scanning QR codes to check the attendee in. The use case sounds simple, but has several mini use cases and dependencies of itself, which we have to take into our account while designing the View and Presenter class. Let’s discuss them one by one:

App State – Start: Activity starts, loads camera

  • Permission Granted: detects that the app already has Camera Permission,
    • starts scanning
  • Permission Absent: detects that the app doesn’t have Camera Permission,
    • Denied: asks for it, denied, shows error
    • Accepted: asks for it, granted, starts scanning

App State – QR Code Detected: Starts parsing them

  • Attendees are not present: Attendees aren’t present because of some reason, either due to internal error or have not loaded yet
    • stops parsing
  • Attendees are present:
    • QR Code does not match with any attendee : Do nothing
    • QR Code matches with one of the attendee : Send attendee to caller

App State: Camera or containing View is getting destroyed

  • Release Camera

There can be much more internal data flows, but this much is sufficient for our example. So, let’s start defining our contracts using the above knowledge. First, we will design our View. So what should be our strategy? Always think of presenters and views to be mapped in a 1:1 relation. They can have conversations with each other, the difference is that the view is only allowed to talk to the presenter, but presenter may talk to models too. So, the view is going to get all of its information from the presenter and can only take action when presenter tells it to, essentially saying that the view is dumb and passive. The presenter can talk to view and model(s), meaning the collection of information presenter has does not have to come from the view only. In fact, the less dependent presenter has to be on view, the better. The main motive of MVP is to make our logic less dependent on views.

Presenter Contract

In our example, presenter relies on the view to tell it when a certain event happens, they can be lifecycle callbacks, permission grants/denies, camera load/destroy or anything purely related to the Android implementation. So, we generally know from the start what kind of information presenter needs from a View, so let’s design our presenter first.

public interface IScanQRPresenter {

    void attach(long eventId, IScanQRView scanQRView);

    void start();

    void detach();

    void cameraPermissionGranted(boolean granted);

    void onBarcodeDetected(Barcode barcode);

    void onScanStarted();

    void onCameraLoaded();

    void onCameraDestroyed();

}

 

The methods are self-explanatory. Don’t worry if you didn’t get why we defined certain hooks like onScanStarted() or why we didn’t define onScanStopped(). These kinds of details will reveal themselves as you develop your components. You can skip to next section if you don’t want to know why we did it.

Basically, you should define a callback for events which are not reliable to be synchronized. What? Let me explain. Let’s say you got your camera permission and requested the view to start scanning, but you have to wait till the camera is loaded as it is not a synchronous call (and it shouldn’t be or your main thread will block). So, instead of that, our presenter will request the camera to load, go in an idle state, wait for the onCameraLoaded() call, and then request the scan to start, and since it is also not a synchronous work, it will go into idle state again and wait for onScanStarted() and then further its work. Whenever the camera is destroyed, the onCameraDestroyed() callback will be called and we will stop the scanning, and since there is nothing to be done after that, we won’t wait till scanning has stopped, thus dropping the need of onScanStopped() callback.

View Contract

View contract will come naturally to you once you have understood the data flow. It will contain the commands presenter will issue on the view and also, the requests for data that view holds. So, this will be our view.

public interface IScanQRView {

    boolean hasCameraPermission();

    void requestCameraPermission();

    void showPermissionError(String error);

    void onScannedAttendee(Attendee attendee);

    void showBarcodePanel(boolean show);

    void showBarcodeData(@NonNull String data);

    void showProgressBar(boolean show);

    void loadCamera();

    void startScan();

    void stopScan();

}

 

Almost all commands are straightforward but let me quickly explain showBarcodePanel(boolean) and showBarcodeData(String). They are used to display the currently visible barcode data to the user.

So, with our implementations set and data flow in place, let’s start writing tests for the feature. Yes, we’ll write tests without implementation and then you’ll see how easy it will be to write your views and presenters with only one goal to mind, to make the tests pass. If your tests are written correctly and cover everything, you should feel confident that your app will work without even seeing the actual implementation, because that is what tests are for. And by making passive and dumb views, imagine how light your instrumentation tests will be. Just check that the individual methods in view implementation are working as expected and you are done! No need to test logic or complex interactions, etc because you have got it covered in the unit tests themselves. This is not only a benefit of data flow tests but also a best practice. You always want to follow DRY, Don’t Repeat Yourself, even while testing.

Tests

So, we will start writing our tests now and you’ll realize how easy it is and all the hard work of abstraction and designing will pay off.

Attach Tests

So, firstly, we will test if the presenter calls appropriate methods when it is attached

@Test
public void shouldLoadAttendeesAutomatically() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    verify(eventRepository).getAttendees(eventId, false);
}

@Test
public void shouldLoadCameraAutomatically() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    verify(scanQRView).loadCamera();
}

 

Here, we are using Mockito to mock our EventDataRepository to return locally defined attendees instead of doing an actual network call. Then we are calling attach on presenter in each method and then we verify in the first test that the presenter is calling getAttendees on the EventDataRepository, and in the second test that it is requesting the view to load the camera.

Note that in the implementation of attach function, both loading of Camera and attendee loading will take place, but it is best practice to test them separately so that when a test fails, we know why it did

Detach Tests

@Test
public void shouldDetachViewOnStop() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    assertNotNull(scanQRPresenter.getView());

    scanQRPresenter.detach();

    assertNull(scanQRPresenter.getView());
}

@Test
public void shouldNotAccessViewAfterDetach() {
    scanQRPresenter.detach();

    scanQRPresenter.start();
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(false);
    scanQRPresenter.cameraPermissionGranted(true);
    scanQRPresenter.onScanStarted();
    scanQRPresenter.onBarcodeDetected(barcode2);
    scanQRPresenter.onCameraDestroyed();

    verifyZeroInteractions(scanQRView);
}

 

In detach tests, we are verifying that after attaching and view not being null, the detach method call makes the presenter leave the reference to the view making it null. And in the second test, we do all possible interactions after detaching and confirm that no call whatsoever was made on the view. Tests like these enforce to avoid memory leaks and check for any NullPointerExceptions that may happen after the view was made null.

Note: This does not mean that memory leaks will not happen if this test passes. You can cause memory leaks by giving the view reference to any long living object, not just the presenter. This just ensures that presenter will not hold the view reference after detach and won’t reference to the null view in future.

Permission Tests

@Test
public void shouldStartScanOnCameraLoadedIfPermissionPresent() {
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.onCameraLoaded();

    verify(scanQRView).startScan();
}

@Test
public void shouldAskPermissionOnCameraLoadedIfPermissionsAbsent() {
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.onCameraLoaded();

    verify(scanQRView).requestCameraPermission();
}

@Test
public void shouldStartScanningOnPermissionGranted() {
    scanQRPresenter.cameraPermissionGranted(true);

    verify(scanQRView).startScan();
}

@Test
public void shouldShowErrorOnPermissionDenied() {
    scanQRPresenter.cameraPermissionGranted(false);

    verify(scanQRView).showPermissionError(matches("(.*permission.*denied.*)|(.*denied.*permission.*)"));
}

The permission tests are straightforward:

Implicit Permission Handling

  1. If the view already has the camera permission and camera has loaded, start scanning
  2. If the view does not have camera permission and the camera has loaded, request the permission

Request Handling

  1. If the request was granted, start scanning
  2. If the permission was denied, show the permission error. Here, I have used regex to match that the error message contains permission and denied words, you can use anyString() from Mockito for more flexibility or a specific message for more tight testing

Camera Destroy Test

@Test
public void shouldStopScanOnCameraDestroyed() {
    scanQRPresenter.onCameraDestroyed();

    verify(scanQRView).stopScan();
}

Pretty simple, stop the scan on destruction of camera

Flow Tests

You can also test that the callback flow happens in order so that not only the unit tests work but also the implementation logic is correct.

/**
 * Checks that the flow of commands happen in order
 */
@Test
public void shouldFollowFlowOnImplicitPermissionGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).loadCamera();
    scanQRPresenter.onCameraLoaded();
    inOrder.verify(scanQRView).startScan();
}

@Test
public void shouldShowProgressInBetweenImplicitPermissionGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.onScanStarted();
    inOrder.verify(scanQRView).showProgressBar(false);
}

Here, we are verifying two things, first that if we have the request granted implicitly, we load the camera and start the scan in order. This test isn’t that useful as we already tested that loading of the camera is done on attach and scan is started when the camera is loaded. In fact, this is an example of breaking the DRY rule. Even though it doesn’t hurt to include this, it also doesn’t help as it does not cover anything that hasn’t already been tested.

The second test is important and tests that progress bar is correctly shown and hidden after certain communications have taken place and the scan has started. Similarly, we can also test the progress bar behavior over all of the possible combinations of cases that can happen. The code snippets below show the tests:

@Test
public void shouldShowProgressInBetweenImplicitPermissionDenyRequestGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(true);
    scanQRPresenter.onScanStarted();
    inOrder.verify(scanQRView).showProgressBar(false);
}

@Test
public void shouldShowProgressInBetweenImplicitPermissionDenyRequestDeny() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(false);
    inOrder.verify(scanQRView).showProgressBar(false);
}

QR Code Detection Tests

@Test
public void shouldNotSendAnyBarcodeIfAttendeesAreNull() {
    sendNullInterleaved();

    verify(scanQRView, never()).onScannedAttendee(any(Attendee.class));
}

@Test
public void shouldNotSendAttendeeOnWrongBarcodeDetection() {
    scanQRPresenter.setAttendees(attendees);
    sendNullInterleaved();

    verify(scanQRView, never()).onScannedAttendee(any(Attendee.class));
}

@Test
public void shouldSendAttendeeOnCorrectBarcodeDetection() {
    // Somehow the setting in setUp is not working, a workaround till fix is found
    RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());

    scanQRPresenter.setAttendees(attendees);

    barcode1.displayValue = "test4-91";
    scanQRPresenter.onBarcodeDetected(barcode1);

    verify(scanQRView).onScannedAttendee(attendees.get(3));
}

 

Lastly, the core tests of our presenter. To explain the tests, let me show you the sendNullInterleaved() method

private void sendNullInterleaved() {
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode2);
    sendBarcodeBurst(barcode2);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
}

 

So what it basically does is send some barcodes interleaved with null (no barcode detected) to the presenter using the onBarcodeDetected method to emulate the real camera sending barcode values with sometimes sending null whenever no barcode is in view.

The first test simply checks that no attendee is sent if the attendee list is null. Seems pretty obvious. The second one checks that if barcode does not match with any attendee’s identifier, it should not send any attendee as well. It does this by setting an arbitrary list of attendees with different identifiers and sending non-matching barcodes to the presenter. Lastly, the successful test, where a single matching barcode is sent to the presenter and it should send that particular attendee with matching identifier.

Phew! Quite a lot of tests and we are done. The tests were not very large and mostly self-explanatory. Now, you just have to implement the view and presenter methods till all the tests light up to be green and you are done! You have created a feature using MVP design and implemented it using test driven development.

So start testing and get a seal of reliability and confidence about your code and the satisfaction of seeing the green bar fill up.

Continue ReadingThe Joy of Testing with MVP in Open Event Orga App

Implementing Responsive Designs in Open Event Front-end

Screen size differs from user to user, from device to device and thus we must provide responsive user interface so that our design doesn’t break on any of the devices. At Open-Event-Frontend we have used Semantic-UI elements to implement responsive designs. Semantic-UI has classes like grid, stackable, container etc to maintain the responsiveness of the designs. Semantic also gives us functions to check the size of the screen we are currently in and thus depending upon the screen size, we can design our user-interface.

Let’s take /events/<event_identifier>  for an example

Two components have been added to this page

  1. The following four buttons that we are using, are ‘ui buttons’ and have icons.
  1. Preview – This button allows us to check how our event would look before it is published.
  2. Publish – When we are ready we can make our event available for the world
  3. Copy – Using this we can copy all the details of our event and create a new event  from it
  4. Delete – If something went wrong we can delete our event at any time

From the above mentioned buttons we have grouped Publish and Copy together.

It is done to add responsive behaviour. In case of mobile ,our buttons only have icon and no text ,and buttons are no longer right aligned. For this we have used device.isMobile to check whether we are on mobile or not to ensure the responsive behaviour is carried over to mobile too.

<div class=“twelve wide column {{unless device.isMobile ‘right aligned’}}”>
       {{#if device.isMobile}}
         <div class=“ui icon fluid buttons”>
           <button class=“ui button”><i class=“unhide icon”></i></button>
           <button class=“ui button”><i class=“check icon”></i></button>
           <button class=“ui button”><i class=“copy icon”></i></button>
           <button class=“ui red button” {{action ‘openDeleteEventModal’}}><i class=“trash icon”></i></button>
         </div>
       {{else}}
           <button class=”ui button labeled icon small”>
             <i class=”unhide icon”></i>
             {{t ‘Preview’}}
           </button>
           <div class=”ui small labeled icon buttons”>
             <button class=”ui button “>
               <i class=”check icon”></i>
               {{t ‘Publish’}}
             </button>
             <button class=”ui button “>
               <i class=”copy icon”></i>
               {{t ‘Copy’}}
             </button>
           </div>
           <button class=“ui red button labeled icon small” {{action ‘openDeleteEventModal’}}>
             <i class=“trash icon”></i>
             {{t ‘Delete’}}
           </button>
       {{/if}}
     </div>

Here class `right aligned` is added dynamically depending upon whether we are viewing on mobile or not.

  1. We have added tabs for all types of elements of events like tickets, speakers, scheduler. Also, we have made these tabs responsive. For mobile these tabs converts to stackable menu.


Here also classes are added depending on some condition.

<div class=“ui fluid pointing stackable menu {{unless device.isMobile ‘secondary’}}”>

Conditional addition of classes is a very useful and powerful feature of handlebars.

<div class=“row” style=“padding-top: 15px”>
   <div class=“sixteen wide column”>
     <div class=“ui fluid pointing stackable menu {{unless device.isMobile ‘secondary’}}”>
       {{#link-to ‘events.view.index’ class=’item’}}
         {{t ‘Overview’}}
       {{/link-to}}
       {{#link-to ‘events.view.tickets’ class=’item’}}
         {{t ‘Tickets’}}
       {{/link-to}}
       <a href=“#” class=‘item’>{{t ‘Scheduler’}}</a>
       {{#link-to ‘events.view.sessions’ class=’item’}}
         {{t ‘Sessions’}}
       {{/link-to}}
       {{#link-to ‘events.view.speakers’ class=’item’}}
         {{t ‘Speakers’}}
       {{/link-to}}
       {{#link-to ‘events.view.export’ class=’item’}}
         {{t ‘Export’}}
       {{/link-to}}
     </div>
   </div>
 </div>

Class fluid has been used so that our menu items takes the whole width of the screen.

Now we have added all the responsive elements to the page and it’s good to go with all devices.

Additional Resources

Continue ReadingImplementing Responsive Designs in Open Event Front-end

Enhance the Map Fragment in the Open Event Android app

The usual map fragment in the Open Event Android App didn’t match to the other UI enhancements in the app. Therefore it was needed to enhance it in order for the users to take full advantages of the map based services that the app offered. The enhancements primarily included the following features –

  • Better marker drawables – The usual markers were dull and didn’t match the app theme of the rest of the app.
  • Location searching with autocomplete dropdown – Initially, there was no way the user could search for the location of the sessions happening in the event. Also, it was decided that it was best if the search auto suggested locations for easy navigation.
  • Navigation to the location marker – The google map class comes bundled with in built UI features such as floor recognition, location pointer etc. Navigation to the location is one of those features which appears on a marker click. The user gets directed to the Google Maps android application for navigation from the current location.

Implementation

Add the following dependencies to your app’s Gradle build file. Do update the libraries if a newer version is available.

dependencies {
  compile ‘com.google.maps.android:android-maps-utils:0.4+’
  compile ‘com.google.android.gms:play-services-location:8.1.0’
  compile ‘com.google.android.gms:play-services-maps:8.1.0’
}

Use vector drawables instead of raster icons for the marker

<!– drawable/map_marker.xml –>
<vector xmlns:android=“http://schemas.android.com/apk/res/android”
  android:height=“40dp”
  android:width=“40dp”
  android:viewportWidth=“40”
  android:viewportHeight=“40”>
  <path android:fillColor=“#000” android:pathData=“M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z” />
</vector>

Customize the marker events including color selections onclick and title display.

private Marker handleMarkerEvents(LatLng location, String locationTitle) {
  if (mMap != null) {
      locationMarker = mMap.addMarker(new MarkerOptions().position(location).title(locationTitle)
              .icon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey)));
      mMap.setOnMarkerClickListener(marker -> {
          locationMarker.setIcon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey));
          locationMarker = marker;
          marker.setIcon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.color_primary));
          return false;
      });
      mMap.setOnMapClickListener(latLng -> locationMarker.setIcon(
              vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey)));
  }
  return locationMarker;
}

//Vector currently cannot be directly used with the markers hence it is necessary to convert it into bitmap first

private BitmapDescriptor vectorToBitmap(Context context, @DrawableRes int id, int color) {
  Drawable vectorDrawable = ResourcesCompat.getDrawable(context.getResources(), id, null);
  assert vectorDrawable != null;
  Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
          vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
  DrawableCompat.setTint(vectorDrawable, ContextCompat.getColor(context, color));
  vectorDrawable.draw(canvas);
  return BitmapDescriptorFactory.fromBitmap(bitmap);
}

Enable the google maps UI enhancements by getting the UI settings and subsequently setting true to google map toolbar

mMap.getUiSettings().setMapToolbarEnabled(true);

Conclusion

The google maps is an important service for the open event android project since events are all about happenings and happenings are best served with accurate locations of their whereabouts.

For more information on the code, please visit the link here.

Continue ReadingEnhance the Map Fragment in the Open Event Android app

Managing Edge Glow Color in Nested ScrollView in Open Event Android App

After the introduction of material design by Google many new UI elements have been introduced. Material design is based on the interaction and movement of colours and objects in real world rather than synthetic unnatural phenomenon. Sometimes it gets messy when we try to keep up with our material aesthetic. The most popular example of it is edge glow colour. The edge glow colour of ListView, RecyclerView, ScrollView and NestedScrollView is managed by the accent colour declared in the styles. We cannot change the accent colour of a particular activity as it is a constant, so it is same across entire app but in popular apps like Contacts by Google it appears different for every individual contacts. Fixing an issue in Open Event Android app, I came across the same problem. In this tutorial I solve this problem particularly for NestedScrollView. See the bottom of screenshots for comparison.

   
  • You need to pre check if the Android version is above or equal to LOLLIPOP before doing this as the setColor() function for edge glow is introduced in LOLLIPOP
  • The fields are declared as “mEdgeGlowTop” and “mEdgeGlowBottom” that we have to modify.
  • We get the glow property of NestedScrollView through EdgeEffectCompat class instead of EdgeEffect class directly, unlike for ListView due its new introduction.

Let’s have a look at the function which accepts arguments color as an integer and nested scroll view of which the color has to be set.

First you have to get the fields “mEdgeGlowTop” and “mEdgeGlowBottom” that signifies the bubbles that are generated when you scroll up and down from Nested Scroll View Class. Similarly “mEdgeGlowLeft”  and “mEdgeGlowRight” for the horizontal scrolling.

public static void changeGlowColor(int color, NestedScrollView scrollView) {
   try {

       Field edgeGlowTop = NestedScrollView.class.getDeclaredField("mEdgeGlowTop");

       edgeGlowTop.setAccessible(true);

       Field edgeGlowBottom = NestedScrollView.class.getDeclaredField("mEdgeGlowBottom");

       edgeGlowBottom.setAccessible(true);

Get the reference to edge effect which is the different part unlike recycler view or list view of setting the edge glow color in nested scrollview.

EdgeEffectCompat edgeEffect = (EdgeEffectCompat) edgeGlowTop.get(scrollView);

       if (edgeEffect == null) {
           edgeEffect = new EdgeEffectCompat(scrollView.getContext());
           edgeGlowTop.set(scrollView, edgeEffect);
       }

       Views.setEdgeGlowColor(edgeEffect, color);

       edgeEffect = (EdgeEffectCompat) edgeGlowBottom.get(scrollView);
       if (edgeEffect == null) {
           edgeEffect = new EdgeEffectCompat(scrollView.getContext());
           edgeGlowBottom.set(scrollView, edgeEffect);
       }

       Views.setEdgeGlowColor(edgeEffect, color);

   } catch (Exception ex) {

       ex.printStackTrace();
   }

}

Finally set the edge glow color. This only works for the versions that are above or equal to LOLLIPOP as edge effect was introduced in the android beginning from those versions.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)

public static void setEdgeGlowColor(@NonNull EdgeEffectCompat edgeEffect, @ColorInt int color) throws Exception {
        Field field = EdgeEffectCompat.class.getDeclaredField("mEdgeEffect");

        field.setAccessible(true);
        EdgeEffect effect = (EdgeEffect) field.get(edgeEffect);
        
        if (effect != null)
            effect.setColor(color);
    }

 

(Don’t forget to catch any exception. You can monitor them by using Log.e(“Error”, ”Message”, e ); for debugging and testing).

Resources

  • https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html
Continue ReadingManaging Edge Glow Color in Nested ScrollView in Open Event Android App