Creating a notification dropdown in semantic UI for Open Event Frontend

Semantic UI comes packaged with highly responsive components to cater to all front end needs. The area of front-end development is so large, it is never possible to cover all the possible requirements of a developer with pre built components. Currently there is no means to display notifications on the navbar in Open Event Front-end project. In this article we are going to build a notification dropdown from scratch which will be used there to display notifications. So we begin by generating a new component via ember CLI

$ ember generate component notification-dropdown

This should generate the boiler-plate code for our component, with the template file located at: templates/components/notification-dropdown.hbs and the JS file located at components/notification-dropdown.js  It is assumed that you already have a basic ember app with at least a navbar set up. The notification drop down will be integrated with the navbar as a separate component. This allows us great flexibility in terms of location of the navbar, and also helps us  in not cluttering the code in one file.

We will use the popup component of semantic ui as the underlying structure of our dropdown. I have used some dummy data stored in a separate file, you can use any dummy data you wish, either  by directly hardcoding it or importing it from a js file stored somewhere else. It’s preferred if the mock data is called from a js file, because it helps in simulating the server response in a much more genuine way.

We will make use of the floating label of semantic UI to display the number of unread notifications. A mail outline icon should make for a good choice to use the primary icon to denote the notifications. Also, the floating label will require additional styling to make it overlap with the icon perfectly.

For the header in the dropdown we can give a ‘mark all as read’ button aligned to the right and the ‘notification’ header to the left. Also for best user experience even on small devices, we will make each notification item clickable as a whole instead of individual clickable elements in it. A selection link list of semantic UI should be perfect to display individual notifications as it gives a hovering effect and also, allows us to display a header. Moving onto individual notification items, it will have 3 sub parts

  • A header
  • Description
  • Human friendly notification time

For the header we will use the ‘header’ class predefined in semantic UI for list items.We will use ‘content’ class for description which is again a predefined semantic UI class, And finally the time can be displayed via moment-from-now helper of ember to display the time in a human friendly format.

<.i class="mail outline icon">
<./i>
<.div class="floating ui teal circular mini label">{{notifications.length}}<./div>
<.div class="ui wide notification popup bottom left transition ">
 <.div class="ui basic inverted horizontal segments">
   <.div class="ui basic left aligned segment weight-800">
     <.p>{{t 'Notifications'}}<./p>
   <./div>
   <.div class="ui basic right aligned segment weight-400">
     <.a href="#">{{t 'Mark all as Read'}}<./a>
   <./div>
 <./div>
 <.div class="ui fluid link celled selection list">
   {{#each notifications as |notification|}}
     <.div class="item">
       <.div class="header">
         {{notification.title}}
       <./div>
       <.div class="content weight-600">
         {{notification.description}}
       <./div>
       <.div class="left floated content">
         {{moment-from-now notification.createdAt}}
       <./div>
     <./div>
   {{/each}}
 <./div>
<./div>

 

Now the next challenge is to make the popup scrollable, they are not scrollable by default and may result in an error if their height exceeds that of the view port. So we apply some styling now. While applying such custom styles we have to be really careful so as to not to apply the styling in general to all of semantic UI’s components. It is very easy to overlook,  and may cause some unwanted changes. It is best to wrap it in a container class, in this case we have chosen to go ahead with notification as the class name. Also, since the notification dropdown should work consistently across all mobile devices, we need to set its maximum height not in terms of pixels but in terms of viewport height. The following styling code takes care of that as well as the icon which we are using to display the notification count.

.notification.item {
 margin: 0 !important;
 .label {
   top: 1em;
   padding: 0.2em;
   margin: 0 0 0 -3.2em !important;

 }
}

.ui.notification.popup {
 padding: 2px;
 .list {
   width: auto;
   max-height: 50vh;
   overflow: hidden;
   overflow-y: auto;
   padding: 0;
   margin: 0;
   .header {
     margin-bottom:5px;
   }
   .content {
     margin-bottom:2px;
   }
   }
 }

 

All of this takes care of the styling. Next, we need to take care of initialising the notification popup. For this we need to go to the navbar component as it is the one who calls the notification dropdown component. And add this to it:

didInsertElement() {
   this._super.call(this);
   this.$('.notification.item').popup({
     popup : '.popup',
     on    : 'click'
   });
 },

 willDestroyElement() {
   this._super.call(this);
   this.$('.notification.item').popup('destroy');
 }

 

The didInsertElement() makes sure that notification pop up is not rendered or initialised before the navbar is. On the other hand, willDestoroyElement() makes sure to clean up and destroy the pop up initialisation. Attached below are some screenshots of what the notification dropdown should look like.

On a wide screen
On mobile screens

Resources

Continue ReadingCreating a notification dropdown in semantic UI for Open Event Frontend

How to add Markers in Map Fragment of Open Event Android App

The Open Event Android project helps event organizers to generate Apps (apk format) for their events/conferences by providing API endpoint or zip generated using Open Event server. In the  Open Event Android App, we have a map for showing all locations of sessions. In this map users should be able to see different locations with multiple markers. In this post I explain how to add multiple markers in the Google map fragment and set the bound in the map so that all markers are visible on one screen with specified padding.

Create Map Fragment

The first step to do is to create a simple xml file and add a fragment element in it. Then create MapsFragment.java file and find fragment element added in the xml file using findFragmentById() method.

SupportMapFragment supportMapFragment = ((SupportMapFragment)
           getChildFragmentManager().findFragmentById(R.id.map));

1. Create fragment_map.xml file

In this file add FrameLayout as a top level element and add fragment element inside FrameLayout with the name “com.google.android.gms.maps.SupportMapFragment”.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">

       <fragment
           android:id="@+id/map"
           android:name="com.google.android.gms.maps.SupportMapFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent" />

</FrameLayout>

2. Create MapsFragment.java

MapsFragment.java extends SupportMapFragement and implements LocationListener, OnMapReadyCallback. Make instance of GoogleMap object. In onViewCreated method inflate fragment_map.xml file using inflater. Now find the fragment element added in the xml file using findFragmentById() method and assign it to SupportMapFragement instance.

public class MapsFragment extends SupportMapFragment implements LocationListener, OnMapReadyCallback {

    private GoogleMap mMap;

    @Override
    public void onViewCreated(View view, @Nullable Bundle     savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_map, container, false);
        SupportMapFragment supportMapFragment = ((SupportMapFragment)
        getChildFragmentManager().findFragmentById(R.id.map));
supportMapFragment.getMapAsync(this);

        return view;
    }

    @Override
    public void onMapReady(GoogleMap map) {  }
    ...
}

Create list of locations

Location object has three variables name, latitude & longitude. Create a list of locations for which we want to add markers to the map.

public class Location {
    private String name;
    private float latitude;
    private float longitude;
}

List<Microlocation> mLocations= new ArrayList<>();

 

Add location objects in mLocations in onViewCreated() method

mLocations.add(location);

 

You can add multiple locations using for loop or fetching from the database.

Add markers

Add following code in onMapReady(GoogleMap map) method. onMapReady(GoogleMap map) is called when map is ready to be used. setMapToolbarEnabled(true) used to show toolbar for marker. If the toolbar is enabled users will see a bar with various context-dependent actions, including ‘open this map in the Google Maps app’ and ‘find directions to the highlighted marker in the Google Maps app’.

if(map != null){
    mMap = map;
    mMap.getUiSettings().setMapToolbarEnabled(true);
}
showEventLocationOnMap();

 

Create showEventLocationOnMap() method and add following code

private void showLocationsOnMap(){

   float latitude;
   float longitude;
   Marker marker;

   //Add markers for all locations
   for (Location location : mLocations) {

       latitude = location.getLatitude();
       longitude = location.getLongitude();
       latlang = new LatLng(latitude, longitude);

       marker = mMap.addMarker(new MarkerOptions()
                .position(latlang)
                .title(location.getName()));
   }
}

 

So what we are doing here?

For each location object in mLocations list, we are creating LatLng(latitude, longitude)  object and adding it to the map using

marker = mMap.addMarker(new MarkerOptions().position(latlang).title(location.getName()));

Setting bound

We want to set the zoom level so that all markers are visible. To do this, we can use LatLngBounds.Builder. Add the position of the marker in the builder object using include method while adding the marker in the map.

LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(marker.getPosition());

 

Then make the LatLngBounds object from builder.

LatLngBounds bounds = builder.build();
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, dpToPx(40));
mMap.moveCamera(cameraUpdate);

private int dpToPx(int dp) {
    return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}

 

dpTopx converts dp to px so that it set the same padding in all devices.

Conclusion

Markers in map give very nice user experience for an event or a conference because they show the relative position of places and we now offer this feature in Open Event Android.

Additional resources:

Continue ReadingHow to add Markers in Map Fragment of Open Event Android App

GlobalSearchAdapter Setup in Open Event Android App

In this blog post I describe how the GlobalSearchAdapter in Open Event Android was made which enabled users to search quickly within the app. This post also outlines how to create Recycler Views with heterogenous layouts and explains how to write ViewHolders.

Adapter Logic

A custom adapter was built for the population of views in the Recycler View in the SearchActivity.

private List<Object> filteredResultList = new ArrayList<>();
//ViewType Constants
private final int TRACK = 0;
private final int SPEAKER = 2;
private final int LOCATION = 3;
private final int DIVIDER = 4;

The DIVIDER constant was assigned to the Result Type Header View.

In a gist all the item types such as Speaker, Track, Location, Divider etc have been designated some constants.

Getting the ItemViewType

@Override
public int getItemViewType(int position) {

   if(filteredResultList.get(position) instanceof Track){
       return TRACK;
   }
   else if(filteredResultList.get(position) instanceof String){
       return DIVIDER;
   }
   ...Similarly for other ItemTypes such as Session or Location
   else{
       return 1;
   }
}

As the filteredResultList is of type Object we can insert objects of any type into the list as Object is a superclass of all classes. We would want a view which represents a TRACK if we have an object of type Track in the filteredResultList. And similarly for the other result types we could insert objects of type LOCATION, SPEAKER types in this list. getItemViewType() basically determines the type of the item that is visible to us. If the list consists of an item of type SPEAKER, in the RecyclerView.

Speaker Item Type
Track Item Type
Divider Item Type
Location Item Type

Code for onCreateViewHolder in GlobalSearchAdapter for the Recycler View

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

   RecyclerView.ViewHolder resultHolder = null;
   LayoutInflater inflater = LayoutInflater.from(parent.getContext());

   switch(viewType) {
       case TRACK:
           View track = inflater.inflate(R.layout.item_track, parent,   false);
           resultHolder = new TrackViewHolder(track,context);
           break;
       case SPEAKER:
           View speaker = inflater.inflate(R.layout.search_item_speaker, parent, false);
           resultHolder = new SpeakerViewHolder(speaker,context);
           break;
       //Similarly for other types
       default:
           break;
   }
   return resultHolder;
}

Depending upon the the viewType returned the desired layout is inflated and the desired ViewHolder is returned.

Code for onBindViewHolder in GlobalSearchAdapter for the Recycler View

@Override
 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
 
    switch (holder.getItemViewType()){
        case TRACK:
            TrackViewHolder trackSearchHolder = (TrackViewHolder)holder;
            final Track currentTrack = (Track)getItem(position);
            trackSearchHolder.setTrack(currentTrack);
            trackSearchHolder.bindHolder();
            break;
         //Similarly for all the other View Types
        default:
            break;
    }
 }

These functions are being used to bind the data to the layouts that have been inflated already in the earlier snippet of code of onCreateViewHolder.

The bindHolder functions of each ViewHolder type are being used to do the view binding i.e converting the information in the Object Track into what we see in the TrackViewHolder as seen in TrackViewFormat.

All ViewHolders have been defined as separate classes in order to enable re usability of these classes.

ViewHolder Implementation

There are 4 main ViewHolders that were made to enable such a search. I’ll be talking about the TrackViewHolder in detail.

public class TrackViewHolder extends RecyclerView.ViewHolder {
    
    @BindView(R.id.imageView)
    ImageView trackImageIcon;
    @BindView(R.id.track_title)
    TextView trackTitle;
    @BindView(R.id.track_description)
    TextView trackDescription;
 
    private Track currentTrack;
    private Context context;
    private TextDrawable.IBuilder drawableBuilder = TextDrawable.builder().round();
 
    public void setTrack(Track track) {
        this.currentTrack = track;
    }
 
    public TrackViewHolder(View itemView,Context context) {
        super(itemView);
        ButterKnife.bind(this, itemView);
        this.context = context;
    }
    public void bindHolder(){
 
        //Set all Views to their correct configurations
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(context,   TrackSessionsActivity.class);
                intent.putExtra(ConstantStrings.TRACK,   currentTrack.getName());
 
                // Send Track ID to Activity to leverage color cache
                intent.putExtra(ConstantStrings.TRACK_ID,   currentTrack.getId());
                context.startActivity(intent);
            }
        });
} }

Those @BindView annotations that we can see are the result of a library called as Butterknife which is used to reduce standard boilerplate findViewById lines.

@BindView(R.id.imageView) ImageView trackImageIcon;
IS THE SAME AS THIS  
ImageView trackImageIcon = (ImageView)findViewById(R.id.imageView);

The advantage of such a ViewHolder is that it knows what kind of data it stores as compared to traditional ViewHolders which do not know the kind of data it stores.

By making ViewHolders separate from the RecyclerViewAdapter we are essentially decoupling classes and are enabling reusability of code. Also we make the ViewHolder a bit more intelligent by storing the object it binds in the ViewHolder itself. In the above example we are storing an object of Track which is bind to the ViewHolder. We also see that we do the view binding inside the viewholder itself. All this helps us to reduce code inside the adapter class.

A recent addition to the app was custom colors for all TRACKS in the app that improved the visual feel of the app. So basically, for example if a SESSION has been associated with the track of Blockchain it would be given a color such as purple. onClickListeners are also being set with some extras which are self-descriptive in nature. Similarly the other ViewHolders have been implemented.

Resources

Continue ReadingGlobalSearchAdapter Setup in Open Event Android App

Implementing Barcode Scanning in Open Event Android Orga App using RxJava

One of the principal goals of Open Event Orga App (Github Repo) is to let the event organizer to scan the barcode identifier of an attendee at the time of entry in the event, in order to quickly check in that attendee. Although there are several scanning APIs available throughout the web for Android Projects, we chose Google Vision API as it handles multitude of formats and orientations automatically with little to no configuration, integrates seamlessly with Android, and is provided by Google itself, ensuring great support in future as well. Currently, the use case of our application is:

  • Scan barcode from the camera feed
  • Detect and show barcode information on screen
  • Iterate through the attendee list to match the detected code with unique attendee identifier
  • Successfully return to the caller with scanned attendee’s information so that a particular action can be taken

There are several layers of optimisations done in the Orga Application to make the user interaction fluid and concise. I’ll be sharing a few in this blog post regarding the configuration and handling of bursty data from camera source efficiently. To see the full implementation, you can visit the Github Repository of the project using the link provided above.

Configuration

The configuration of our project was done through Dagger 2 following the dependency injection pattern, which is not the focus of this post, but it is always recommended that you follow separation of concerns in your project and create a separate module for handling independent works like barcode scanner configuration. Normally, people would create factories with scanner, camera source and processors encapsulated. This enables the caller to have control over when things are initialized.

Our configuration provides us two initialised objects, namely, CameraSource and BarcodeDetector

@Provides
BarcodeDetector providesBarCodeDetector(Context context, Detector.Processor<Barcode> processor) {
    BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context)
        .setBarcodeFormats(Barcode.QR_CODE)
        .build();

    barcodeDetector.setProcessor(processor);

    return barcodeDetector;
}

@Provides
CameraSource providesCameraSource(Context context, BarcodeDetector barcodeDetector) {
    return new CameraSource
        .Builder(context, barcodeDetector)
        .setRequestedPreviewSize(640, 480)
        .setRequestedFps(15.0f)
        .setAutoFocusEnabled(true)
        .build();
}

The fields needed to create the objects are provided as arguments to the provider functions as all the dependencies required to construct these objects. Now focusing on the Detector.Processor requirement of the BarcodeDetector is the classic example on non injectable code. This is because the processor is to be supplied by the activity or any other object which wants to receive the callback with the detected barcode data. This means we could inject it at the time of creation of the Activity or Presenter itself. We could easily overcome by adding a constructor to this dagger module containing the Barcode.Processor at the time of injection, but that would violate our existing 0 configuration based model where we just get the required component from the Application class and inject it. So, we wrapped the the processor into a PublishSubject

@Provides
@Singleton
@Named("barcodeEmitter")
PublishSubject<Notification<Barcode>> providesBarcodeEmitter() {
    return PublishSubject.create();
}

@Provides
@Singleton
Detector.Processor<Barcode> providesProcessor(@Named("barcodeEmitter") PublishSubject<Notification<Barcode>> emitter) {
    return new Detector.Processor<Barcode>() {
        @Override
        public void release() {
            // No action to be taken
        }

        @Override
        public void receiveDetections(Detector.Detections<Barcode> detections) {
            SparseArray<Barcode> barcodeSparseArray = detections.getDetectedItems();
            if (barcodeSparseArray.size() == 0)
                emitter.onNext(Notification.createOnError(new Throwable()));
            else
                emitter.onNext(Notification.createOnNext(barcodeSparseArray.valueAt(0)));
        }
    };
}

This solves 2 of our problems, not only now all these dependencies are injectable with 0 configurations, but also our stream of barcodes is now reactive.

Note that not everyone is in favour of using Singleton, but you can decrease the scope using your own annotation. We prefer not creating life cycle bound objects, those are hard to manage and can cause potential memory leaks, and the creation of an anonymous inner class object every time listener activates is not good for memory too.

Also, note that Singleton classes will cause memory leaks too if you don’t release their reference at the time of destruction of life cycle bound object

Notice how the type of PublishSubject is not just a barcode, but Notification which wraps the bar code. That’s because we want to send both the value and error streams down uninterrupted to the caller. Otherwise, the data stream would have stopped on the emission of first onError call. Here, we detect the barcodeSparseArray size and accordingly send error or first value to the PublishSubject which will be accordingly subscribed by the activity or presenter

Handling Bursty Data

barcodeEmitter.subscribe(barcodeNotification -> {
    if (barcodeNotification.isOnError()) {
        presenter.onBarcodeDetected(null);
    } else {
        presenter.onBarcodeDetected(barcodeNotification.getValue());
    }
});

Here is how we are subscribing the notification emitter and passing the appropriate value to the presenter to handle, null if it is an error and the value if it is the next emission.

Note that you must dispose the disposable returned by the subscribe method on the subject when the Activity is to be destroyed or else it will keep the reference to the anonymous inner class created with the lambda for barcodeNotification and cause a memory leak

Now, let’s see how the presenter handles this data for:

  • Hiding and showing the barcode panel when barcode is on the screen accordingly
  • Showing the data extracted from the barcode scanner

These things can be implemented in a very standard way with a few conditionals, but most developers forget the fact that the data emission rate is enormous when concerning with live feed of data. In the Open Event Orga app, we have reduced it to 15 FPS as it is more than enough to scan barcodes for our use case, but it is still huge. The continuous stream of nulls and barcode data is useless to us unless it changes.

A little explanation about nulls and values here: You must have noticed above the conditions when we pass null and value, but I’ll explain again. A value will be passed if there is a detected barcode on screen, and null will be passed if there is no barcode detected. The Google Vision API will keep sending the same value for barcode at 15 FPS and so we’ll get this redundant stream of nulls and values which we should not concern with processing as this will load the CPU unnecessarily.

There are only 2 cases where we need to process it:

  • Null changes to Value -> Show barcode panel
    Value changes to null -> Hide barcode panel
  • Value changes irrespective of nulls -> Show barcode data on UI and search through the attendee identifiers

So, here too we’ll create 2 PublishSubject objects

private PublishSubject<Boolean> detect = PublishSubject.create();
private PublishSubject<String> data = PublishSubject.create();

And we’ll configure them both in this way to receive data on each barcode emission in the presenter:

public void onBarcodeDetected(Barcode barcode) {
   detect.onNext(barcode == null);
   if (barcode != null)
       data.onNext(barcode.displayValue);
}

This will make data only receive non-null changes and detect receive a boolean notifying if the current detected barcode was null or not.

Now, we see how each of these subjects is configured to pass the emissions downstream:

data.distinctUntilChanged()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(this::processBarcode);

This one is pretty straightforward. It’ll only send data downstream if its value has changed from the previous emission, disregarding nulls. So, for a stream like this

A A A A A null null null null A A A A null B B B B B B null null null B B A A null A

It will only emit:

A                                                                         B                                                  A

Which is actually what we want, as we only need to process and show the distinct barcode data and match it with our attendee list. Now, with thousands of attendees, the first method would have triggered unnecessary and time-consuming computations over and over again on same identifiers with little gaps of time, which would have created mediocre results even in a multi-threaded environment. The second one saves us from repetitive calculations and also gives us enormous gaps between emissions, which is optimal for our use case.

The second case is not so obvious because we can’t ignore nulls here as we have to show and hide UI based on them. This means that unlike our previous stream if we just use distinctUntilChanged, it will look like this:

A A A A A null null null null A A A A null B B B B B B null null null B B A A null A

f                 t                               f               t      f                    t                        f             t       f

This is because, if you remember, we were sending down emissions of barcode == null on each emission for this Subject. So, in this case, as you may see, some of the values are so close enough that it will not be discernable in UI and also annoy users who’ll see the panel pop up for milliseconds before vanishing or vice-versa. The perfect operation for this case will be debounce

detect.distinctUntilChanged()
    .debounce(150, TimeUnit.MILLISECONDS)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(receiving -> scanQRView.showBarcodePanel(!receiving));

This operator will drop any emission in the window of 150ms succession and only pick up those emissions which are 150ms apart from each other. Now, 150 ms is not a magic number, it is picked through hit and trial and what works best for your case. Lower the value and you will pick up more changes downstream, increase the value and you might miss the required events.

This makes our stream somewhat like this, cleaning out the cluttered events

f                 t                               f                                           t                        f

This is the screenshot of the implementation:  

 

And an animated gif of the scanning process:

This is all for this blog, you may use many other operators from the arsenal of RxJava, whatever fits your use case. As I have presumed the knowledge about Subjects, MVP and a little bit of Dagger in this post, I’ll link some of the resources where you can find more information about these:

http://reactivex.io/documentation/subject.html

https://github.com/ReactiveX/RxJava/wiki/Subject

http://reactivex.io/documentation/operators/distinct.html

http://www.vogella.com/tutorials/Dagger/article.html

https://antonioleiva.com/mvp-android/

Continue ReadingImplementing Barcode Scanning in Open Event Android Orga App using RxJava

Global Search in Open Event Android

In the Open Event Android app we only had a single data source for searching in each page that was the content on the page itself. But it turned out that users want to search data across an event and therefore across different screens in the app. Global search solves this problem. We have recently implemented  global search in Open Event Android that enables the user to search data from the different pages i.e Tracks, Speakers, Locations etc all in a single page. This helps the user in obtaining his desired result in less time. In this blog I am describing how we implemented the feature in the app using JAVA and XML.

Implementing the Search

The first step of the work is to to add the search icon on the homescreen. We have done this with an id R.id.action_search_home.

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
   super.onCreateOptionsMenu(menu, inflater);
   inflater.inflate(R.menu.menu_home, menu);
   // Get the SearchView and set the searchable configuration
   SearchManager searchManager = (SearchManager)getContext().    getSystemService(Context.SEARCH_SERVICE);
   searchView = (SearchView) menu.findItem(R.id.action_search_home).getActionView();
  // Assumes current activity is the searchable activity
 searchView.setSearchableInfo(searchManager.getSearchableInfo(
getActivity().getComponentName()));
searchView.setIconifiedByDefault(true); 
}

What is being done here is that the search icon on the top right of the home screen  is being designated a searchable component which is responsible for the setup of the search widget on the Toolbar of the app.

@Override
 public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_home, menu);
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    searchView = (SearchView) menu.findItem(R.id.action_search_home).getActionView();
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));
 
    searchView.setOnQueryTextListener(this);
    if (searchText != null) {
        searchView.setQuery(searchText, true);
    }
    return true; }

We can see that a queryTextListener has been setup in this function which is responsible to trigger a function whenever a query in the SearchView changes.

Example of a Searchable Component

<?xml version="1.0" encoding="utf-8"?>
 <searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="@string/global_search_hint"
    android:label="@string/app_name" />

For More Info : https://developer.android.com/guide/topics/search/searchable-config.html

If this searchable component is inserted into the manifest in the required destination activity’s body the destination activity is set and intent filter must be set in this activity to tell whether or not the activity is searchable.

Manifest Code for SearchActivity

<activity
        android:name=".activities.SearchActivity"
        android:launchMode="singleTop"
        android:label="Search App"
        android:parentActivityName=".activities.MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEARCH" />
    </intent-filter>
    <meta-data
        android:name="android.app.searchable"
        android:resource="@xml/searchable" />
 </activity>

And the attribute  android:launchMode=”singleTop is very important as if we want to search multiple times in the SearchActivity all the instances of our SearchActivity would get stored on the call stack which is not needed and would also eat up a lot of memory.

Handling the Intent to the SearchActivity

We basically need to do a standard if check in order to check if the intent is of type ACTION_SEARCH.

if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
    handleIntent(getIntent());
 }
@Override
 protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    handleIntent(intent);
 }
 public void handleIntent(Intent intent) {
    final String query = intent.getStringExtra(SearchManager.QUERY);
    searchQuery(query);
 }

The function searchQuery is called within handleIntent in order to search for the text that we received from the Homescreen.

SearchView Trigger Functions

Next we need to add two main functions in order to get the search working:

  • onQueryTextChange
  • onQueryTextSubmit

The function names are self-explanatory. Now we will move on to the code implementation of the given functions.

@Override
 public boolean onQueryTextChange(String query) {
    if(query!=null) {
        searchQuery(query);
        searchText = query;
    }
    else{
        results.clear();
        handleVisibility();
    }
   return true;
 }
 
 @Override
 public boolean onQueryTextSubmit(String query) {
    searchView.clearFocus();
    return true;
 }

The role of the searchView.clearFocus() inside the above code snippet is to remove the keyboard popup from the screen to enable the user to have a clear view of the search result.

Here the main search logic is being handled by the function called searchQuery which I’ll talking about now.

Search Logic

private void searchQuery(String constraint) {
 
    results.clear();
 
    if (!TextUtils.isEmpty(constraint)) {
        String query = constraint.toLowerCase(Locale.getDefault());
        addResultsFromTracks(query);
        addResultsFromSpeakers(query);
        addResultsFromLocations(query);
    }
 }
//THESE ARE SOME VARIABLES FOR REFERENCE
//This is the custom recycler view adapter that has been defined for the search
private GlobalSearchAdapter globalSearchAdapter;
 //This stores the results in an Object Array
 private List<Object> result

We are assuming that we have POJO’s(Plain Old Java Objects) for Tracks , Speakers , and Locations and for the Result Type Header.

The code posted below performs the function of getting the required results from the list of tracks. All the results are being fetched asynchronously from Realm and here we have also attached a header for the result type to denote whether the result is of type Track , Speaker or Location. We also see that we have added a changeListener to notify us if any changes have occurred in the set of results.

Similarly this is being done for all the result types that we need i.e Tracks, Locations and Speakers.

public void addResultsFromTracks(String queryString) {

   RealmResults<Track> filteredTracks = realm.where(Track.class)
                                        .like("name", queryString,                     Case.INSENSITIVE).findAllSortedAsync("name");
      filteredTracks.addChangeListener(tracks -> {

       if(tracks.size()>0){
            results.add("Tracks");
        }
       results.addAll(tracks);
       globalSearchAdapter.notifyDataSetChanged();
       Timber.d("Filtering done total results %d", tracks.size());
       handleVisibility();
});}

We now have a “Global Search” feature in the Open Event Android app. Users had asked for this feature and a learning for us is, that it would have been even better to do more tests with users when we developed the first versions. So, we could have included this feedback and implemented Global Search earlier on.

Resources

Continue ReadingGlobal Search in Open Event Android

Adding a Two Pane Layout to the Open Event Android App

Android gadgets are found in various screen sizes and densities. It is here where fragments make planning the layouts for phones and tablets simple.  We can progressively include and expel sections from an activity which makes it conceivable to plan adaptable UIs.

Generally a single pane design is favored for telephones and multi-pane formats are utilized for tablets with a specific end goal to use the additional space which is found in tablets. The Open Event Android App on tablet view appeared to be identical for a versatile client and accordingly it had a considerable measure of free space which could be used for effective UI for tablet clients. A two-pane design is utilized for the application where the navigation view is on the left side while the item clicked on the navigation view is on the right side.

  1. Steps involved for incorporating a two-pane layout

The steps that need to be followed for the app to support a two-pane layout are as follows:

  1. Create a layout-sw600dp (for small tablet users) or layout-sw720dp (for large tablet users) in the res directory.
  2. The name of files should be same for those within layout/ and layout-sw600dp/ to provide a support for a two-pane layout.

The layout/activity_main.xml structure of the app looks like this:

The layout-sw600dp/activity_main.xml structure of the app looks like this:

  1. Now in the MainActivity.java file of the app we took a boolean variable mTwoPane which was true if during runtime findViewById(R.id.drawer) didn’t exist as a drawerlayout is used as a root view for the activity_main.xml in layout/ folder but not in layout-sw600dp folder.
  2. All the parts of the MainActivity.java which had a drawerlayout variable were now put in an “if” condition checking if the mTwoPane boolean value is false then execute the statement otherwise set the drawerlayout to lock mode.

This helped us accomplishing a two-pane layout in the tablet layout of the app thus helping in using the extra space in a more efficient manner.

Related Links:
Continue ReadingAdding a Two Pane Layout to the Open Event Android App

Using Getter and Setter to set Properties and Manipulate Responses to Templates in Ember JS on Open Event Front-end

In the Open Event Front-end our aim is to make it work on all devices regardless of screen size. We need the UI to be responsive. One feature of the system is that users can change system images. The idea in this issue was to enable users to select a ‘topic’ from the left side menu that would trigger an action which dynamically changes the right part of the page. For example, we could show ‘subTopics’ and their respective images based on the data given in the dictionary in Open Event Frontend project. Open Event Frontend has some static data files which are called dictionaries. They contain Javascript Objects which are used throughout the project. The solution I am implementing here is to get the data from the template, set it as a property of the Ember object with the help of setter, and use it again with the help of getter to manipulate our response to the template. Thus in a controller, we can perform manipulation of the data which we want to return to the template.

In Open Event Front-end we have a dictionary (event.js) which we are using to load our topics into the left side menu.

 

export const eventTopics = {
'Auto, Boat & Air': [
'Air', 'Auto', 'Boat', 'Motorcycle/ATV', 'Other'
],
'Business & Professional': [
'Career', 'Design', 'Educators', 'Environment & Sustainability',
'Finance', 'Media', 'Non Profit & NGOs', 'Other', 'Real Estate',
'Sales & Marketing', 'Startups & Small Business'
],
'Charity & Causes': [
'Animal Welfare', 'Disaster Relief', 'Education',
'Environment', 'Healthcare', 'Human Rights',
'International Aid', 'Other', 'Poverty'
],
'Community & Culture': [
'City/Town', 'County', 'Heritage', 'LGBT', 'Language',
'Medieval', 'Nationality', 'Other', 'Renaissance', 'State'
]
};

                                                           event.js

To achieve this, on the click action of the topic in the left side menu, we have to somehow remember the topic and return the subtopics according to it from the dictionary dynamically.

Following this approach: A controller is needed to remember the topic selected from the left side rail. We set the properties on the controller to achieve this thing. This is where the ‘Getters’ and ‘Setters’ come to help.

 

import Ember from 'ember';
import { eventTopics } from 'open-event-frontend/utils/dictionary/event';
import { keys, uniqBy } from 'lodash';

const { Controller, computed } = Ember;

export default Controller.extend({
imageTopic: keys(eventTopics)[0],

topics: computed(function() {
return keys(eventTopics);
}),

imageObjects: computed('imageTopic', 'model', function() {
let subTopics = eventTopics[this.get('imageTopic')];
let filteredImageArray = this.get('model').filter(function(obj) {
return (subTopics.includes(obj.name));
});
filteredImageArray = uniqBy(filteredImageArray, 'name');
return filteredImageArray;
}),

actions: {
setImagePart(imageTopic) {
this.set('imageTopic', imageTopic);
},
openModal() {
this.set('isModalOpen', true);
}
}
});

                                             system-images.js(controller)

 

{{#tabbed-navigation isVertical=true}}
{{#each topics as |topic|}}
<a href="#" class="link item {{if (eq imageTopic topic) 'active'}}" {{action 'setImagePart' topic}}>{{topic}}</a>
{{/each}}
{{/tabbed-navigation}}

                                          system-images.hbs(template)

When a link from the left side menu is clicked, an action named “setImagePart” is triggered and the controller listens to the action. Here, we are passing a parameter called ‘topic’ from the template and getting it in the controller as ‘imageTopic’. The next part of the action is to set the property of the object Controller i.e the “setImagePart” action sets the imageTopic (in this case overrides) property and sets it to the selected one. Thus, we now have the controller with the data selected from the left side menu saved in a property called imageTopic.

Now as we can see we have imported the eventTopics object from the dictionary.

Next task is to get the respected subtopics present in the dictionary. Now since we have set the ‘imageTopic’ property, we can access it to get the subtopics simply by getter “this.get(‘imageTopic’)”. Thus the subTopics (values of the eventTopics object) are returned.

Resources: https://guides.emberjs.com/v1.10.0/object-model/computed-properties/

Continue ReadingUsing Getter and Setter to set Properties and Manipulate Responses to Templates in Ember JS on Open Event Front-end

Open Event Server: Working with Migration Files

FOSSASIA‘s Open Event Server uses alembic migration files to handle all database operations and updations.  From creating tables to updating tables and database, all works with help of the migration files.
However, many a times we tend to miss out that automatically generated migration files mainly drops and adds columns rather than just changing them. One example of this would be:

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.add_column('session', sa.Column('submission_date', sa.DateTime(), nullable=True))
    op.drop_column('session', 'date_of_submission')

Here, the idea was to change the has_session_speakers(string) to is_session_speakers_enabled (boolean), which resulted in the whole dropping of the column and creation of a new boolean column. We realize that, on doing so we have the whole data under  has_session_speakers lost.

How to solve that? Here are two ways we can follow up:

  • op.alter_column:
    ———————————-

When update is as simple as changing the column names, then we can use this. As discussed above, usually if we migrate directly after changing a column in our model, then the automatic migration created would drop the old column and create a new column with the changes. But on doing this in the production will cause huge loss of data which we don’t want. Suppose we want to just change the name of the column of start_time to starts_at. We don’t want the entire column to be dropped. So an alternative to this is using op.alter_column. The two main necessary parameters of the op.alter_column is the table name and the column which you are willing to alter. The other parameters include the new changes. Some of the commonly used parameters are:

  1. nullable Optional: specify True or False to alter the column’s nullability.
  2. new_column_name – Optional; specify a string name here to indicate the new name within a column rename operation.
  3. type_Optional: a TypeEngine type object to specify a change to the column’s type. For SQLAlchemy types that also indicate a constraint (i.e. Boolean, Enum), the constraint is also generated.
  4. autoincrement –  Optional: set the AUTO_INCREMENT flag of the column; currently understood by the MySQL dialect.
  5. existing_typeOptional: a TypeEngine type object to specify the previous type. This is required for all column alter operations that don’t otherwise specify a new type, as well as for when nullability is being changed on a column.

    So, for example, if you want to change a column name from “start_time” to “starts_at” in events table you would write:
    op.alter_column(‘events’, ‘start_time’, new_column_name=’starts_at’)
def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('sessions_version', 'end_time', new_column_name='ends_at')
    op.alter_column('sessions_version', 'start_time', new_column_name='starts_at')
    op.alter_column('events_version', 'end_time', new_column_name='ends_at')
    op.alter_column('events_version', 'start_time', new_column_name='starts_at')


Here,
session_version and events_version are the tables name altering columns start_time to starts_at and end_time to ends_at with the op_alter_column parameter new_column_name.

  • op.execute:
    ——————–

Now with alter_column, most of the alteration in the column name or constraints or types is achievable. But there can be a separate scenario for changing the column properties. Suppose I change a table with column “aspect_ratio” which was a string column and had values “on” and “off” and want to convert the type to Boolean True/False. Just changing the column type using alte_column() function won’t work since we need to also modify the whole data. So, sometimes we need to execute raw SQL commands. To do that, we can use the op.execute() function.
The way it is done:

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.execute("ALTER TABLE image_sizes ALTER full_aspect TYPE boolean USING CASE 
            full_aspect WHEN 'on' THEN TRUE ELSE FALSE END", execution_options=None)

    op.execute("ALTER TABLE image_sizes ALTER icon_aspect TYPE boolean USING CASE 
            icon_aspect WHEN 'on' THEN TRUE ELSE FALSE END", execution_options=None)

    op.execute("ALTER TABLE image_sizes ALTER thumbnail_aspect TYPE boolean USING CASE 
            thumbnail_aspect WHEN 'on' THEN TRUE ELSE FALSE END"execution_options=None)

For a little more advanced use of op.execute() command will be:

op.alter_column('events', 'type', new_column_name='event_type_id')
    op.alter_column('events_version', 'type', new_column_name='event_type_id')
    op.execute('INSERT INTO event_types(name, slug) SELECT DISTINCT event_type_id, 
                lower(replace(regexp_replace(event_type_id, \'& |,\', \'\', \'g\'),
                \' \', \'-\')) FROM events where not exists (SELECT 1 FROM event_types 
                where event_types.name=events.event_type_id) and event_type_id is not
                null;')
    op.execute('UPDATE events SET event_type_id = (SELECT id FROM event_types WHERE 
                event_types.name=events.event_type_id)')
    op.execute('ALTER TABLE events ALTER COLUMN event_type_id TYPE integer USING 
                event_type_id::integer')

In this example:

  • op.alter_column() renames the column type to event_type_id of events table
  • op.execute() does the following:
  • Inserts into column name of event_types table the value of event_type_idN (which previously contained the name of the event_type) from events table, and
  • Inserts into slug column of event_types table the value of event_type_id where all letters are changed to lowercase; “& ” and “,” to “”; and spaces to “-”.
    1. Checks whether a type with that name already exists so as to disallow any duplicate entries in the event_types table.
    2. Checks whether the event_type_id is null because name of event_types table cannot be null.

You can learn more on Alembic migrations here: http://alembic.zzzcomputing.com/en/latest/ops.html

Continue ReadingOpen Event Server: Working with Migration Files

Formatting of ISO8601Date.java class in Open Event Android App

ISO8601Date.java is an util class in Open Event Android App which comprises of all the required date manipulation functionalities required by the application to parse the dates from the api given in ISO8601 format. Previous implementation of the class consisted of functions which needed multiple lines of function call in the main code. Due to this reason the util class was formatted in a way so that the function call can be simplified and improve the quality of our codebase.

1. Previous Implementation of ISO8601Date.java class

The previous implementation of the ISO8601Date.java class had a number of functions needed for date manipulations in it. However when we used to call them in the code they used to take up several lines which demonstrated that we didn’t use the possibility of an util class to the best of our utilization.

An example illustrating this fact is given below:

Example 1: (In SessionListAdapter.java)

String date = ISO8601Date.getTimeZoneDateString(ISO8601Date.getDateObject(session.getStartTime())).split(",")[0] + "," +ISO8601Date.getTimeZoneDateString(ISO8601Date.getDateObject(session.getStartTime())).split(",")[1];

The principal case is from SessionListAdapter.java class where we are utilizing the ISO8601Date.java util class for finding the start time. It can be perceived how various lines including the split functions are utilized for the date manipulation.

2. Current Implementation of ISO8601Date.java class

The factors taken into account for the current implementation of the util class were:

  1. To ensure we write generic functions which can be used in multiple places in the app.
  2. The calling of the function is enough to do what is required.

These factors were the key points for rewriting the util class and ensuring the class is easy to use with enough commenting to explain what was going on in each function.  The idea was to write all the complex function calling code within the function itself.

With the present execution of the class the codebase got more basic as for each situation a straightforward one line call was sufficient to give the output we needed as demonstrated below:

Example 1: (In SessionListAdapter.java)

String date = ISO8601Date.getDateFromStartDateString(session.getStartTime());

Example 2: (In SessionDetailActivity.java)

String startTime = ISO8601Date.getTimeFromStartDateString(session.getStartTime());

The advantage now is, whenever we have to use a new function related to date manipulation we simply have to create one in the util class and provide a simple one line way of calling it in our main code to make the codebase look straightforward. The examples shown above are the same ones which were illustrated before. The multiple lines which they took for calling of a function didn’t make the util class to the best of our use. As we can see with current examples, the new class ensured the function handled the complex stuff within itself without delegating it to the main code.

Related Links

Continue ReadingFormatting of ISO8601Date.java class in Open Event Android App

Nested Routes in Ember JS in Open Event Frontend

When defining routes in ember, there is one very important thing to keep in mind. We should only nest our routes when our UI is nested. Sometimes it is necessary to display a template inside another template and here we use nested routes.

Let’s now learn how to create nested routes. We can take a look at the notifications page in our Open Event Frontend for better understanding.

Here the part in red is common to all the sub-routes of notification, thus one method of achieving this could be copy-pasting the same code in all the sub-routes. Obviously this is not the correct method and it increases the code redundancy.

The correct method here is, using nested routes.

ember generate route notifications
ember generate route notifications/index
ember generate route notifications/all

Here notification is the parent route and index, all are it’s nested or sub-routes. It can be seen in router.js in the given form

this.route(‘notifications’, function() {
   this.route(‘all’);
});

But wait we see no route for notifications/index…. did something go wrong ?

 

No. At every level of nesting, ember automatically creates a route for the path ‘/’ named index. So the above code snippet in router.js is equivalent to

this.route(‘notifications’, function() {

   this.route(‘index’,{ path: ‘/’});
   this.route(‘all’);
});

Now let’s have a look at our parent route ie /notifications. As stated above only the part in red has to be present here, So we have added semantic-ui’s header, buttons and used link-to helper to link them to respective route. An interesting thing to note here is that ‘Mark all read’ is not present in both its sub routes, so we need to check the route before displaying this button. We have checked the current route using session.currentRouteName and only when the current route is not notifications/all we display the button.

<h1 class=“ui header”>{{t ‘Notifications’}}</h1>
<div class=”ui divider”></div>
<div class=“row”>
 {{#link-to ‘notifications’}}
   <button class=“ui button”>{{t ‘Unread’}}</button>
 {{/link-to}}
 {{#link-to ‘notifications.all’}}
   <button class=“ui button”>{{t ‘All’}}</button>
 {{/link-to}}
 {{#if (not-includes session.currentRouteName ‘notifications.all’)}}
   <button class=“ui button right floated”>{{t ‘Mark all read’}}</button>
 {{/if}}
</div>
{{outlet}}

We have added the {{outlet}} helper in our notifications.hbs where we want our nested templates to be displayed. For understanding we can imagine this as the whole code of notifications/all is being copied into this outlet helper i.e notifications/all.hbs is rendered into the {{outlet}} of notifications.hbs

Let’s now have a look at our sub-route i.e notifications/all.hbs. We now know that this will be rendered in the red part stated below

We have added Semantic-Ui’s segments class for every notification. A blue colour has been added to the segments if the notification is not read. We have used Semantic-Ui’s grid class to structure the content inside each notifications. We are getting date object from js and to convert it into human readable format we have used moment like {{moment-from-now notification.createdAt}}

{{#each model as |notification|}}
 <div class=“ui segment {{unless notification.isRead ‘blue’}}”>
   <div class=“ui grid”>
     <div class=“row”>
       <div class=“eight wide column”>
         <h4 class=“ui header”>{{notification.title}}</h4>
         <p>{{notification.description}}</p>
       </div>
       <div class=“eight wide column right aligned”>
         <i class=“checkmark box icon”></i>
       </div>
     </div>
     <div class=“row”>
       <div class=“ten wide column”>
         <button class=“ui blue button”>{{t ‘View Session’}}</button>
       </div>
       <div class=“six wide column right aligned”>
         <p>{{moment-from-now notification.createdAt}}</p>
       </div>
     </div>
   </div>
 </div>
{{/each}}

We have reused notifications/all.hbs for index route by explicitly defining templateName: ‘notifications/all’, in notifications/index.js.

Additional Resources

Continue ReadingNested Routes in Ember JS in Open Event Frontend