Better Bookmark Display Viewholder in Home Screen of the Open Event Android App

Earlier in the Open Event Android app we had built the homescreen with the bookmarks showing up at the top as a horizontal list of cards but it wasn’t very user-friendly in terms of UI. Imagine that a user bookmarks over 20-30 sessions, in order to access them he/she might have to scroll horizontally a lot in order to access his/her bookmarked session. So this kind of UI was deemed counter-intuitive. A new UI was proposed involving the viewholder used in the schedule page i.e DayScheduleViewHolder, where the list would be vertical instead of horizontal. An added bonus was that this viewholder conveyed the same amount of information on lesser white space than the earlier viewholder i.e SessionViewHolder.

Old Design
New Design

 

 

 

 

 

 

 

 

 

Above are two images, one in the initial design, the second in the new design. In the earlier design the number of bookmarks visible to the user at a time was at most 1 or 2 but now with the UI upgrade a user can easily see up-to 5-6 bookmarks at a time. Additionally there is more relevant content visible to the user at the same time.

Additionally this form of design also adheres to Google’s Material Design guidelines.

Code Comparison of the two Iterations

Initial Design
sessionsListAdapter = new SessionsListAdapter(getContext(), mSessions, bookmarkedSessionList);
 sessionsListAdapter.setBookmarkView(true);
 bookmarksRecyclerView.setAdapter(sessionsListAdapter);
 bookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),LinearLayoutManager.HORIZONTAL,false));

Here we are using the SessionListAdapter for the bookmarks. This was previously being used to display the list of sessions inside the track and location pages. It is again being used here to display the horizontal list of bookmarks.To do this we are using the function setBookmarkView(). Here mSessions consists the list of bookmarks that would appear in the homescreen.

Current Design
bookmarksRecyclerView.setVisibility(View.VISIBLE);
 bookMarksListAdapter = new DayScheduleAdapter(mSessions,getContext());
 bookmarksRecyclerView.setAdapter(bookMarksListAdapter);
 bookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

Now we are using the DayScheduleAdapter which is the same adapter used in the schedule page of the app. Now we use a vertical layout instead of a horizontal layout  but with a new viewholder design. I will be talking about the last line in the code snippet shortly.

ViewHolder Design (Current Design)

<RelativeLayout android:id="@+id/content_frame">
    <LinearLayout android:id="@+id/ll_sessionDetails">
        <TextView android:id="@+id/slot_title"/>
        <RelativeLayout>
            <TextView android:id="@+id/slot_start_time”/>
            <TextView android:id="@+id/slot_underscore"/>
            <TextView android:id="@+id/slot_end_time"/>

           <TextView android:id="@+id/slot_comma"/>
            <TextView android:id="@+id/slot_location”/>
            <Button android:id="@+id/slot_track"/>
            <ImageButton android:id="@+id/slot_bookmark"/>
        </RelativeLayout>

    </LinearLayout>
    <View android:id=”@+id/divider”/>
 </RelativeLayout>

This layout file is descriptive enough to highlight each element’s location.

In this viewholder we can also access to the track page from the track tag and can also remove bookmarks instantly.

The official widget to make a scroll layout in android is ScrollView. Basically, adding a RecyclerView inside ScrollView can be difficult . The problem was that the scrolling became laggy and weird.  Fortunately, with the appearance of Material Design , NestedScrollView was released and this becomes much easier.

bookmarksRecyclerView.setNestedScrollingEnabled(false);

With this small snippet of code we are able to insert a RecyclerView inside the NestedScrollView without any scroll lag.
So now we have successfully updated the UI/UX for the homescreen  to meet the requirements as given by the Material Design guidelines.

Resources

Continue ReadingBetter Bookmark Display Viewholder in Home Screen of the Open Event Android App

Multiple dynamic sub routes in Open Event Front-end

Dynamic routing is a section of the path for a route which changes based on the content of a page. Instead of having multiple routes for similar pages we have one route that changes state accordingly. Dynamic routing is extensively used in the Open Event Front-end application, for tables, events & other pages.

Sometimes we might need to have multiple dynamic routes for a particular route in this case we need to do implement a custom route redirection. Let’s see how did we implement it in the events route?

Dynamic routes in events route

The events route has two dynamic subroutes view & list. By default the dynamic route which is declared at the last will be used by ember which is list route in this case. This leads to loss of the view route which uses the ID of the event to display the event details.

this.route('events', function() {
  this.route('view', { path: '/:event_id' }, function() {
  });
  this.route('list', { path: '/:event_state' });
  this.route('import');
});

Here event_id is the ID of an event from the API response, event_state is the state of the list of events created by the user. The event_id is an alphanumeric value which is dynamic in nature and event_state is a string value which is a static value. Due to non overlapping states we can implement a simple procedure that enables us to create multiple dynamic routes.

Implementing multiple dynamic routing in ‘events’ route

As both the subroutes use different states i.e event_id & event_state, we can use this to redirect the routes accordingly. We will add a beforeModel() function in the events/list route and check if the eventState is not equal to live, draft or past which are the states of list route.

beforeModel(transition) {
   const eventState = transition.params[transition.targetName].event_state;
  if (!['live', 'draft', 'past'].includes(eventState)) {
    this.replaceWith('events.view', eventState);
  }
}

If the ‘eventState’ is not in the array we redirect the page to the events.view route using the replaceWith() function. This will ensure that the events.view route is not lost & the event details are rendered properly.

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

Resources

Continue ReadingMultiple dynamic sub routes in Open Event Front-end

Implementing Email Preferences in Open Event Front-end

Open Event Front-end lets users customise the email preferences for the notifications for the events created by the user. The user can toggle the notifications for new paper is submitted, change in schedule of sessions,  and reminder for event. In this post we will implement a functional UI for the email preferences in the application. How did we do it?

Creating email-preferences component

We create a component for the email-preferences component using ember-cli command:

ember g component settings/email-preferences-section


Mock response from server

Each event has notification preferences for three services new papers, change in schedule & new event which are stored as a boolean in the server. Each response model contains all the flags for the email preferences.


Implementing UI for email preferences

We use ui-accordion for each event created by the user. The ui-accordion lets us toggle the display for content section, similar to jquery’s collapse-in class. You can find more about semantic ui’s ui-accordion here.Implementing UI for email-preferences-section

model() {
 return [{
   name             : Techtoma,
   role             : Organiser,
   isNewPaper       : true,
   isChangeSchedule : true,
   isNewEvent       : true
 }];

Each preference can be toggled using ui-checkbox from semantic ui. When a checkbox is toggled the respective flag in the model.

<div class="row">
  <div class="column eight wide">
    {{t 'New Paper is Submitted to your Event'}}
  </div>
  <div class="ui column eight wide right aligned">
    {{ui-checkbox class='toggle' checked=event.isNewPaper onChange=(action (mut event.isNewPaper))}}
  </div>
</div>
<div class="row">
  <div class="column eight wide">
    {{t 'Change in Schedule of Sessions in your Event'}}
  </div>
  <div class="ui column eight wide right aligned">
    {{ui-checkbox class='toggle' checked=event.isChangeSchedule onChange=(action (mut event.isChangeSchedule))}}
  </div>
</div>
<div class="row">
  <div class="column eight wide">
    {{t 'Reminder for Next Event'}}
  </div>
  <div class="ui column eight wide right aligned">
    {{ui-checkbox class='toggle' checked=event.isNewEvent onChange=(action (mut event.isNewEvent))}}
  </div>
</div>

If any of the preference is not set i.e false, we toggle the status of the preferences to off. We use math helper or operation for setting the status of the preferences for each preference. We also toggle between the colour of the button between green and yellow, depending on the status of preferences setting.

<a class="ui circular label {{if (or event.isNewPaper event.isChangeSchedule event.isNewEvent) 'green' 'yellow'}}">
  {{#if (or event.isNewPaper event.isChangeSchedule event.isNewEvent)}}
    {{t 'On'}}
  {{else}}
    {{t 'Off'}}
  {{/if}}
</a> 

This results to the following changes on the Open Event Front-end:

The source code for the example is available here.

Resources

Continue ReadingImplementing Email Preferences in Open Event Front-end

Sorting Events in Open Event Organizer Android App

While working on Open Event Organizer project, we had to display events in a single list in custom order with proper sub headings. Initially, we were thinking of using tabbed activity and showing events in respective tabs. But the thing with tabs is that it requires you to nest fragments and then each of them will have adapters. Also, we have used Model View Presenter pattern in the project, so this is another reason we did not use view pager as it would increase the number of presenter and view classes for the same feature. So we decided to display events in a single list instead. The custom order decided was that events would be divided into three categories – live, upcoming and past. In each category, a recent event will be at the top of another.

Adding SubHeadings support to the Recycler View

So the first thing was adding subheading support to the recycler view. We have used timehop’s sticky header decorators library for subheadings implementation. First, your adapter should implement the interface StickyRecyclerHeadersAdapter provided by the library. In our case the implemented methods look like:

@Override
public long getHeaderId(int position) {
  Event event = events.get(position);
  return DateService.getEventStatus(event).hashCode();
}

@Override
public EventsHeaderViewHolder onCreateHeaderViewHolder(ViewGroup viewGroup) {
  return new EventsHeaderViewHolder(EventSubheaderLayoutBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false));
}

@Override
public void onBindHeaderViewHolder(EventsHeaderViewHolder holder, int position) {
  Event event = events.get(position);
  holder.bindHeader(DateService.getEventStatus(event));
}

@Override
public int getItemCount() {
  return events.size();
}

 

The first one is getHeaderId which returns a unique id for a group of items which should appear under a single subheading. In this case, DateService.getEventStatus returns status of an event (either live, past or upcoming) and so hashcode of it is returned as a unique id for that header. OnCreateHeaderViewHolder is same as onCreateViewHolder of your adapter. Return your header view here. Similarly in onBindViewHolder, bind data to the header. getItemCount returns total number of items.

Sorting Events

The important thing to do was sorting events in the order decided. We had to implement the Comparable interface to Event model which will compare any two events using our custom rules such that after sorting we get events in the order – Live, Upcoming and Past with recent one at the top in each category. The compareTo method of Event model looks like:

public int compareTo(@NonNull Event otherEvent) {
  Date now = new Date();
  try {
     Date startDate = DateUtils.getDate(getStartTime());
     Date endDate = DateUtils.getDate(getEndTime());
     Date otherStartDate = DateUtils.getDate(otherEvent.getStartTime());
     Date otherEndDate = DateUtils.getDate(otherEvent.getEndTime());
     if (endDate.before(now) || otherEndDate.before(now)) {
         // one of them is past and other can be past or live or upcoming
         return endDate.after(otherEndDate) ? -1 : 1;
     } else {
         if (startDate.after(now) || otherStartDate.after(now)) {
             // one of them is upcoming other can be upcoming or live
             return startDate.before(otherStartDate) ? -1 : 1;
         } else {
             // both are live
             return startDate.after(otherStartDate) ? -1 : 1;
         }
     }
  } catch (ParseException e) {
  e.printStackTrace();
  }
  return 1;
}

 

The compareTo method returns a positive integer value for greater than, the negative integer value for less than and 0 if equal. Accordingly, we have implemented the method as per our need. At first case, we check if one of the events is past by comparing end dates with now. So the other event can be past, live or upcoming. In all the cases we will need to have an event top of another if an end date of the event is before the end date of another. In next case, only live and upcoming events pair will reach to this case. So, in this case, we check if one of them is upcoming so that other can be either upcoming or live. In both the cases, we need to have an event with start date before another’s start date at the top. Hence just comparing start dates of them will do the trick. For the last case, we are left with both live events. So here we need an event with start date after another event at the top. Hence just comparing start date if it is after other’s start date then it comes on top of another.

Using this method, events are sorted and supplied to the adapter which implements StickyRecyclerHeadersAdapter. Hence in the list, events are displayed in Live, Upcoming and Past categories as expected with respective section headers and in each category, a recent event comes on top of another.

Links:
Sticky headers decorator library- https://github.com/timehop/sticky-headers-recyclerview

Continue ReadingSorting Events in Open Event Organizer Android App

Shrinking Model Classes Boilerplate in Open Event Android Projects Using Jackson and Lombok

JSON is the de facto standard format used for REST API communication, and for consuming any of such API on Android apps like Open Event Android Client and Organiser App, we need Plain Old Java Objects, or POJOs to map the JSON attributes to class properties. These are called models, for they model the API response or request. Basic structure of these models contain

  • Private properties representing JSON attributes
  • Getters and Setters for these properties used to change the object or access its data
  • A toString() method which converts object state to a string, useful for logging and debugging purposes
  • An equals and hashcode method if we want to compare two objects

These can be easily and automatically be generated by any modern IDE, but add unnecessarily to the code base for a relatively simple model class, and are also difficult to maintain. If you add, remove, or rename a method, you have to change its getters/setters, toString and other standard data class methods.

There are a couple of ways to handle it:

  • Google’s Auto Value: Creates Immutable class builders and creators, with all standard methods. But, as it generates a new class for the model, you need to add a library to make it work with JSON parsers and Retrofit. Secondly, there is no way to change the object attributes as they are immutable. It is generally a good practice to make your models immutable, but if you are manipulating their data in your application and saving it in your database, it won’t be possible except than to create a copy of that object. Secondly, not all database storage libraries support it
  • Kotlin’s Data Classes: Kotlin has a nice way to made models using data classes, but it has certain limitations too. Only parameters in primary constructor will be included in the data methods generated, and for creating a no argument constructor (required for certain database libraries and annotation processors), you either need to assign default value to each property or call the primary constructor filling in all the default values, which is a boilerplate of its own. Secondly, sometimes 3rd party libraries are needed to work correctly with data classes on some JSON parsing frameworks, and you probably don’t want to just include Kotlin in your project just for data classes
  • Lombok: We’ll be talking about it later in this blog
  • Immutables, Xtend Lang, etc

This is one kind of boilerplate, and other kind is JSON property names. As we know Java uses camelcase notation for properties, and JSON attributes are mostly:

  • Snake Cased: property_name
  • Kebab Cased: property-name

Whether you are using GSON, Jackson or any other JSON parsing framework, it works great for non ambiguous property names which match as same in both JSON and Java, but requires special naming attributes for translating JSON attributes to camelcase and vice versa. Won’t you want your parser to intelligently convert property_name to propertyName without having you write the tedious mapping which is not only a boilerplate, but also error prone in case your API changes and you forget to update the annotations, or make a spelling mistake, as they are just non type-safe strings.

These boilerplates cause serious regressions during development for what should be a simple Java model for a simple API response. These both kinds of boilerplate are also related to each other as all JSON parsers look for getters and setters for private fields, so there are two layers of potential errors in modeling JSON to Java Models. This should be a lot simpler than it is. So, in this blog, we’ll see how we can configure our project to be 0 boilerplate tolerant and error free. We reduced approximately 70% boilerplate using this configuration in our projects. For example, the Event model class we had (our biggest) reduced from 590 lines to just 74!

We will use a simpler class for our example here:

public class CallForPapers {

    private String announcement;
    @JsonProperty("starts-at")
    private String startsAt;
    private String privacy;
    @JsonProperty("ends-at")
    private String endsAt;

    // Getters and Setters

    @Override
    public String toString() {
        return "CallForPapers{" +
                "announcement='" + announcement + '\'' +
                ", startsAt='" + startsAt + '\'' +
                ", privacy='" + privacy + '\'' +
                ", endsAt='" + endsAt + '\'' +
                '}';
    }
}

 

Note that getters and setters have been omitted for brevity. The actual class is 57 lines long

As you can see, we are using @JsonProperty annotation to properly map the starts-at attribute to startsAt property and similarly on endsAt. First, we’ll remove this boilerplate from our code. Note that this seems a bit overkill for 2 attributes, but imagine the time you’ll save by not having to maintain 100s of attributes for the whole project.

Jackson is smart enough to map different naming styles to one another in both serializing and deserializing. This is done by using Naming Strategy class in Jackson. There is an option to globally configure it, but I found that it did not work for our case, so we had to apply it to each model. It can be simply done by adding another annotation on the top of your class declaration and removing the JsonProperty attribute from your fields

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class CallForPapers {

    private String announcement;
    private String startsAt;
    private String privacy;
    private String endsAt;

    // Getters and Setters

    @Override
    public String toString() {
        return "CallForPapers{" +
                "announcement='" + announcement + '\'' +
                ", startsAt='" + startsAt + '\'' +
                ", privacy='" + privacy + '\'' +
                ", endsAt='" + endsAt + '\'' +
                '}';
    }
}

 

Our class looks like this now. But be careful to properly name your getters and setters because now, Jackson will map attributes by method names, so if you name the setter for startsAt -> setStartsAt it will automatically understand that the attribute to be mapped is “starts-at”. But, if the method name is something else, then it won’t be able to correctly map the fields. If your properties are not private, then Jackson may instead use them to map fields, so be sure to name your public properties in a correct manner.

Note: If your API does not use kebab case, there are plenty of other options or naming strategies present in Jackson, one example will be

  • PropertyNamingStrategy.SnakeCaseStrategy for attributes like “starts_at”

 Needless to say, this will only work if your API uses a uniform naming strategy

Now we have removed quite a burden from the development lifecycle, but 70% of class is still getters, setters, toString and other data methods. Now, we’ll configure lombok to automatically generate these for us. First, we’ll need to add lombok in our project by adding provided dependency in build.gradle and sync the project

provided 'org.projectlombok:lombok:1.16.18'

 

And now you’d want to install Lombok plugin in Android Studio by going to Files > Settings > Plugins and searching and installing Lombok Plugin and restarting the IDE

 

After you have restarted the IDE, navigate to your model and add @Data annotation at the top of your class and remove all getters/setters, toString, equals, hashcode and if the plugin was installed correctly and lombok was installed from the gradle dependencies, these will be automatically generated for you at build time without any problem. A way for you to see the generated methods is to the structure perspective in the Project Window.

 

There are many more fun tools in lombok and more fine grained control options are provided. Our class looks like this now

@Data
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class CallForPapers {
    private String announcement;
    private String startsAt;
    private String privacy;
    private String endsAt;
}

 

Reduced to 16 lines (including imports and package). Now, there are some corner cases that you want to iron out for the integration between lombok and Jackson to work correctly.

Lombok uses property names for generating its getters and setters. But there’s a different convention for handling booleans. For the sake of simplicity, we’ll only talk about primitive boolean. You can check out the links below to learn more about class type. The primitive boolean property of the standard Java format, for example hasSessions will generate hasSessions and getter and setHasSessions. Jackson is smart but it expects a getter named getHasSessions creating problems in serialization. Similarly, for a property name isComplete, generate getter and setter will be isComplete and setComplete, creating a problem in deserialization too. Actually, there are ways how Jackson can get boolean values mapped correctly with these getters/setters, but that method needs to rename property itself, changing the getters and setters generated by Lombok. There is actually a way to tell Lombok to not generate this format of getter/setter. You’d need to create a file named lombok.config in your project directory app/ and write this in it

lombok.anyConstructor.suppressConstructorProperties = true
lombok.addGeneratedAnnotation = false
lombok.getter.noIsPrefix = true

 

There are some other settings in it that make it configured for Android specific project

There are some known issues in Android related to Lombok. As lombok itself is an annotation processor, and there is no order for annotation processors to run, it may create problems with other annotation processors. Dagger had issues with it until they fixed it in their later versions. So you might need to check out if any of your libraries depend upon the lombok generated code like getters and setters. Certain database libraries use that and Android Data Binding does too. Currently, there is no solution to the problem as they will throw an error about not finding a getter/setter because they ran before lombok. A possible workaround is to make properties public so that instead of using getters and setters, these libraries use them instead. This is not a good practice, but as this is a data class and you are already creating getters and setters for all fields, this is not a security vulnerability.

There are tons of options for both Jackson and Lombok with a lot of features to help the development process, so be sure to check out these links:

Continue ReadingShrinking Model Classes Boilerplate in Open Event Android Projects Using Jackson and Lombok

Smart Data Loading in Open Event Android Orga App

In any API centric native application like the Open Event organizer app (Github Repo), there is a need to access data through network, cache it for later use in a database, and retrieve data selectively from both sources, network and disk. Most of Android Applications use SQLite (including countless wrapper libraries on top of it) or Realm to manage their database, and Retrofit has become a de facto standard for consuming a REST API. But there is no standard way to manage the bridge between these two for smart data loading. Most applications directly make calls to DB or API service to selectively load their data and display it on the UI, but this breaks fluidity and cohesion between these two data sources. After all, both of these sources manage the same kind of data.

Suppose you wanted to load your data from a single source without having to worry from where it is coming, it’d require a smart model or repository which checks which kind of data you want to load, check if it is available in the DB, load it from there if it is. And if it not, call the API service to load the data and also save it in the DB there itself. These smart models are self contained, meaning they handle the loading logic and also handle edge cases and errors and take actions for themselves about storing and retrieving the data. This makes presentation or UI layer free of data aspects of the application and also remove unnecessary handling code unrelated to UI.

From the starting of Open Event Android Orga Application planning, we proposed to create an efficient MVP based design with clear separation of concerns. With use of RxJava, we have created a single source repository pattern, which automatically handles connection, reload and database and network management. This blog post will discuss the implementation of the AbstractObservableBuilder class which manages from which source to load the data intelligently

Feature Set

So, first, let’s discuss what features should our AbstractObservableBuilder class should have:

  • Should take two inputs – disk source and network source
  • Should handle force reload from server
  • Should handle network connection logic
  • Should load from disk observable if data is present
  • Should load from network observable if data is not present is disk observable

This constitutes the most basic data operations done on any API based Android application. Now, let’s talk about the implementation

Implementation

Since our class will be of generic type, we will used variable T to denote it. Firstly, we have 4 declarations globally

private IUtilModel utilModel;
private boolean reload;
private Observable<T> diskObservable;
private Observable<T> networkObservable;

 

  • UtilModel tells us if the device is connected to internet or not
  • reload tells if the request should bypass database and fetch from network source only
  • diskObservable is the database source of the item to be fetched
  • networkObservable is the network source of the same item

Next is a very simple implementation of the builder pattern methods which will be used to set these variable fields

@Inject
public AbstractObservableBuilder(IUtilModel utilModel) {
    this.utilModel = utilModel;
}

AbstractObservableBuilder<T> reload(boolean reload) {
    this.reload = reload;

    return this;
}

AbstractObservableBuilder<T> withDiskObservable(Observable<T> diskObservable) {
    this.diskObservable = diskObservable;

    return this;
}

AbstractObservableBuilder<T> withNetworkObservable(Observable<T> networkObservable) {
    this.networkObservable = networkObservable;

    return this;
}

 

UtilModel is the required dependency, and so is added as a constructor parameter.

All right, all variables are set up, now we need to create the build function to actually create the observable:

@NonNull
public Observable<T> build() {
    if (diskObservable == null || networkObservable == null)
        throw new IllegalStateException("Network or Disk observable not provided");

    return Observable
            .defer(getReloadCallable())
            .switchIfEmpty(getConnectionObservable())
            .compose(applySchedulers());
}

Reloading Logic

First of all, we check if the caller forgot to add disk or network source and throw an exception if it is actually so. Next, we use defer operator to defer the call to getReloadCallable() so that this function is not executed until this observable is subscribed. Some articles over the internet directly use combine operators from Rx to make things easy, but this lazy calling is the most efficient way to do things because no actual call will be made to observables.

Secondly, you can easily test the behaviour in unit tests, by verifying that

  • no call to the network observable was made if the data inside disk observable was present; or
  • call to network observable was made even if there was data in disk observable if the reload request was made

These tests would not have been possible if we did not employ the lazy call technique because the calls to the observables and utilModel would have been made before the subscription to this model happen, in order to create this observable eagerly.

Now, let’s see what getReloadCallable does

@NonNull
private Callable<Observable<T>> getReloadCallable() {
    return () -> {
        if (reload)
            return Observable.empty();
        else
            return diskObservable
                .doOnNext(item -> 
                    Timber.d("Loaded %s From Disk on Thread %s",
                    item.getClass(), Thread.currentThread().getName()));
    };
}

 

This function’s role is to return the disk observable if the request is not a reload call, or else return an empty observable, so that force network request happens to reload the data

So it returns a Callable which encapsulates this logic, and besides that, it also adds a log if loading from disk about the type of item loaded and the thread it was loaded on

Connection and Database Switch Logic

In the next chain of operation, we make a switchIfEmpty call to getConnectionObservable(). Because of the above reloading logic, switchIfEmpty serves 2 purpose here, it changes to API call:

  • if db does not contain data
  • If it is a reload call

The observable we switch to is returned by getConnectionObservable() and its purpose is to check if the device is connected to the internet, and if it is, to forward the network request and if it is not, then return an Error Observable.

@NonNull
private Observable<T> getConnectionObservable() {
    if (utilModel.isConnected())
        return networkObservable
            .doOnNext(item -> Timber.d("Loaded %s From Network on Thread %s",
                item.getClass(), Thread.currentThread().getName()));
    else
        return Observable.error(new Throwable(Constants.NO_NETWORK));
}

We use util model to determine if we are connected to internet and take action accordingly. As you can see, here too, we log about the data being loaded and the thread information.

Threading

Lastly, we want to ensure that all processing happens on correct threads, and for that, we call compose with an Observable Transformer to make all requests happen on I/O scheduler and the data is received on Android’s Main Thread

@NonNull
private <V> ObservableTransformer<V, V> applySchedulers() {
    return observable -> observable
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
}

And that’s all it takes to create a reactive, generic and reusable data handler for disk and network based operations. In the repository pattern we have employed in the Open Event Android Orga Application, all our data switching and handling code is delegated to it, with unit tests and integration tests testing the individual and cross component working in all cases.

If you want to learn more about other implementations, you can read these articles

Continue ReadingSmart Data Loading in Open Event Android Orga App

Dynamic Ticket Analysis UI using Data Binding in Open Event Android Orga App

Any event manager application has the responsibility to show the analytics about the event to the organiser and in Open Event Android Orga App (Github Repo), we wanted to achieve a way to display the analytics of total and sold tickets with the data present to us.
To analyse, we have a list of tickets, which are divided into 3 categories:

  • Free
  • Paid
  • Donation

Our goal was to show information about total tickets and the amount of sold tickets per category. This blog will focus on the dynamic UI creation for the ticket analysis component of the Event Details Dashboard using Android Layout Data Binding. By using Data Binding, we not only reduced the amount of Java Boilerplate code we would have to write, but also accomplished UI reuse in just XML which wouldn’t have been possible without it. You’ll see in a moment what I mean.

Properties

So first, we’d need to define some properties which will be bound in the UI. These properties are declared in the Event model and their type is ObservableLong provided by the Android DataBinding package. The reason why we are using these instead of primitives is because these fields being Observable, will update the UI as soon as they are updated, without requiring the programmer to set the View Property at all.

There are six fields, 3 for total tickets of each type and 3 for sold tickets

public final ObservableLong freeTickets = new ObservableLong();
public final ObservableLong paidTickets = new ObservableLong();
public final ObservableLong donationTickets = new ObservableLong();

public final ObservableLong soldFreeTickets = new ObservableLong();
public final ObservableLong soldPaidTickets = new ObservableLong();
public final ObservableLong soldDonationTickets = new ObservableLong();

Some more advantages we get from using these are the batch view update and the use of computed properties in UI. Imagine having a TextView display the amount of free tickets and a progress bar showing the percentage of free tickets sold. Traditionally, you’d have to set the text and compute the percentage and set the progress bar as the data changes, whereas you can just use the fields in layout as is in both TextView and ProgressBar with the computations required and they’ll work in harmony.

We have leveraged this feature to show the analytics component of tickets with a

  • Ticket Type
  • Circular Progress Bar
  • Sold Tickets
  • Total Tickets

All using the XML layout and databinding

Ticket Component

For each ticket component, we have 4 variables, namely

  • Ticket Type Name
  • Total Amount
  • Completed Amount (Sold Tickets)
  • Color

First 3 are fairly self explanatory, the color attribute we used in our component needs a little bit of description. We decided to give each ticket category its own color for circular progress bar for aesthetics. So, we need each component to have its own color attribute too. But this is not a normal android color ID or a hex. We needed 2 variants of the same color to show in the circular progress to discern the total and completed part. As we are using Material Color Palette, which has a color divided by intensities, we used 500 variant for completed portion and 100 (lighter) variant for the background of circular progress.

Let’s look at the layout now:

<data>
    <variable name="color" type="String" />
    <variable name="ticketName" type="String" />
    <variable name="total" type="long" />
    <variable name="completed" type="long" />
</data>

<LinearLayout
     android:orientation="vertical">
    <TextView
        android:text="@{ticketName}" />
    <FrameLayout>
        <com.mikhaellopez.circularprogressbar.CircularProgressBar
            app:circular_progress_color="@{color}"
            app:progress_with_animation="@{total == 0 ? 0 : (int) ((completed*100)/total)}" />

        <LinearLayout
            android:orientation="horizontal">

            <TextView
                android:text="@{completed}" />
            <TextView
                android:text='@{"/" + total}' />

        </LinearLayout>
    </FrameLayout>

    <TextView
        android:text='@{(total == 0 ? 0 : (int) ((completed*100)/total)) + "%"}' />

</LinearLayout>

Note: The layout snippet is not complete. Only attribute names to be discussed in the blog are shown for brevity

As you can see, after the data variable declarations, we have a CardView first showing the ticket name on top, and then we have a FrameLayout wrapping the circular progress and a textview showing the Sold/Total tickets.

Circular Progress Bar

Let’s discuss the circular progress first, we have used this library to create a circular progress bar, the two other attributes circular_progress_color and progress_with_animation are specific to Open Event Orga Application and we have created custom adapters for them:

@BindingAdapter("progress_with_animation")
public static void bindCircularProgress(CircularProgressBar circularProgressBar, int progress) {
    circularProgressBar.setProgressWithAnimation(progress, 500);
}

@BindingAdapter("circular_progress_color")
public static void bindCircularProgressColor(CircularProgressBar circularProgressBar, String colorName) {
    Context context = circularProgressBar.getContext();
    Resources resources = circularProgressBar.getResources();

    int color = ContextCompat.getColor(context, resources.getIdentifier(colorName + "_500", "color", context.getPackageName()));
    int bgColor = ContextCompat.getColor(context, resources.getIdentifier(colorName + "_100", "color", context.getPackageName()));

    circularProgressBar.setColor(color);
    circularProgressBar.setBackgroundColor(bgColor);
}
  • progress_with_animation sets the provided integer value as the progress of the circular progress bar with an animation of 500 ms
  • circular_progress_color finds the 100 and 500 variant of the color name string provided and sets them as background and foreground color of the progress bar

These are the color definitions we have used in the app:

<color name="light_blue_100">#B3E5FC</color>
<color name="light_blue_500">#03A9F4</color>
<color name="purple_100">#E1BEE7</color>
<color name="purple_500">#9C27B0</color>
<color name="red_100">#ffcdd2</color>
<color name="red_500">#f44336</color>

As you can that if we pass purple as the color name, it’ll load purple_100 and purple_500 and set it as corresponding background and foreground color

Other Properties

Now, let’s talk about other properties like the progress value :

  • total == 0 ? 0 : (int) ((completed*100)/total)The conditional is used to prevent divide by zero error.
    The same expression is used to display the circular progress and percentage text in the TextView at the bottom of the layout
  • completed and “/” + total are used to in TextViews of different sizes to create a nice design with completed/total format

This completes our ticket component design and now we’ll see how to reuse this component to display different ticket types.

Composite Layout

To use the ticket component, we just include the layout and bind specific variables from Event model to create a dynamic layout like this:

<data>
    <variable
        name="event"
        type="org.fossasia.openevent.app.data.models.Event" />
</data>

<LinearLayout
    android:orientation="vertical">

    <TextView
        android:text="@string/tickets" />

    <LinearLayout
        android:orientation="horizontal">

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"light_blue"}'
            bind:completed="@{event.soldFreeTickets}"
            bind:ticketName="@{@string/ticket_free}"
            bind:total="@{event.freeTickets}" />

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"purple"}'
            bind:completed="@{event.soldPaidTickets}"
            bind:ticketName="@{@string/ticket_paid}"
            bind:total="@{event.paidTickets}" />

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"red"}'
            bind:completed="@{event.soldDonationTickets}"
            bind:ticketName="@{@string/ticket_donation}"
            bind:total="@{event.donationTickets}" />
    </LinearLayout>

</LinearLayout>

The layout consists of a horizontal with 3 equally divided ticket components,

  • Free Ticket Component -> Light Blue
  • Paid Ticket Component -> Purple
  • Donation Ticket Component -> Red

This is how it looks on a device

 

So this is how data binding made us accomplish easily which would have been a very convoluted solution using traditional ID based view binding. For more info about data binding, refer to these sites:

https://developer.android.com/topic/libraries/data-binding/index.html

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

Continue ReadingDynamic Ticket Analysis UI using Data Binding in Open Event Android Orga App

Serialisation, Deserialisation and gson in Open Event Android

JSON is a format to exchange and inter-change data. It has almost become a universal means for transferring data because of it being lightweight, human understandable, machine readable and most importantly, it’s language independence. It can be used with, and in any programming language. (Reference)

FOSSASIA’s Open Event project makes extensive use of JSON for transferring information about events, their speakers, sessions and other event information. The Open Event Android application itself loads all it’s data in JSON format. This JSON is uploaded by the user in the form of a .zip compressed file or by giving an API link. Now before we use this data in the app, we have to parse the data to get Java objects that can be used in Android. Deserialisation is the process of converting JSON to Java objects. In the same way, Serialisation refers to converting Java objects into JSON.

In most applications and frameworks, JSON is serialized and deserialized on multiple instances. The most common approach is to create objects corresponding to the JSON format and then use functions to convert them to JSON line-by-line, attribute by attribute. While this approach will work, it will mean writing unnecessary code and spending a lot more time on it. In Open-event-Android, we are using Google’s gson library to  serialise and deserialise JSON and corresponding Java objects respectively.

Why are we using gson specifically? And why do we need any library in the first place?

Yes, we can obviously make functions in Java by defining all the JSON parameters and converting the JSON into Java objects manually. It is indeed the obvious approach, but the entire process will be time-consuming, complex and definitely not error-free. Also, as projects become bigger, it is inevitable to take care of the code size and reduce it to only what is necessary.

Let’s take a look at the one of the Open Event JSON file as a sample to try and understand things in a better way. This is an example of event.json in the FBF8’2017 sample.

{

"id": 180,
"name": "F8-Facebook Developer Conference 2017",
"latitude": 37.329008,
"longitude": -121.888794,
"location_name": "San Jose Convention Center, 150 W San Carlos St  San Jose  CA USA 95113 ",
"start_time": "2017-04-18T10:00:00-07:00",
"end_time": "2017-04-19T10:00:00-07:00",
"timezone": "US / Pacific",
"description": "Join us for our annual 2-day event, where developers and businesses come together to explore the future of technology. Learn how Facebook connects the world through new products and innovation. This year's event is bigger than ever – with more than 50 sessions, interactive experiences, and the opportunity to meet one-on-one with members of the Facebook team.",
"background_image": "/images/background.jpg",
"logo": "/images/fbf8.png",
"organizer_name": "Facebook",
"organizer_description": "Join us for our annual 2-day event, where developers and businesses come together to explore the future of technology. Learn how Facebook connects the world through new products and innovation. This year's event is bigger than ever – with more than 50 sessions, interactive experiences, and the opportunity to meet one-on-one with members of the Facebook team.",
"event_url": "https://www.fbf8.com",
"social_links": [
{"id": 1,
"link": "https://www.facebook.com/FacebookForDevelopers",
"name": "Facebook"},
{"id": 2,
"link": "https://twitter.com/fbplatform",
"name": "Twitter"},
{"id": 3,
"link": "https://www.instagram.com/explore/tags/fbf8/",
"name": "Instagram"},
{"id": 4,
"link": "https://github.com/Facebook",
"name": "GitHub"}],
"ticket_url": null,
"privacy": "public",
"type": "",
"topic": "Science & Technology",
"sub_topic": "High Tech",
"code_of_conduct": "",
"copyright": {
"logo": "",
"licence_url": "https://en.wikipedia.org/wiki/All_rights_reserved",
"holder": "Facebook",
"licence": "All rights reserved",
"holder_url": null,
"year": 2017},
"call_for_papers": null,
"email": null,
"has_session_speakers": false,
"identifier": "7d16c124",
"large": null,
"licence_details": {
"logo": "",
"compact_logo": "",
"name": "All rights reserved",
"url": "https://en.wikipedia.org/wiki/All_rights_reserved",
"long_name": "All rights reserved",
"description": "The copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."},
"placeholder_url": null,
"schedule_published_on": null,
"searchable_location_name": null,
"state": "Published",
"version": {
"event_ver": 0,
"microlocations_ver": 0,
"sessions_ver": 0,
"speakers_ver": 0,
"sponsors_ver": 0,
"tracks_ver": 0}}

For a file with so many attributes, it can be very tedious to make a parser class in Java. Instead, we simply use the gson library to do the job.

To install gson in maven central, add the following dependency:

<dependencies>
   <!--  Gson: Java to Json conversion -->
   <dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.8.0</version>
     <scope>compile</scope>
   </dependency>
</dependencies>                                             

[source:https://github.com/google/gson/blob/master/UserGuide.md]

To use gson in Android, we need to add the following dependency in build.gradle of the project.

compile ‘com.google.code.gson:gson:2.6.2’

Using gson in your code is as simple as using any built-in class in java. There are two main functions – toJson()and fromJson() that will be used throughout the processes of serialisation and deserialisation.

First off, let’s declare a basic Java object. I will take an example of a Java object that represents a car to simplify things a bit. This object car contains 3 variables- Name, Model number and color of the car. For example:

class Car(){
String  name;
int model_no;
String colour;
Car(){}
}

Serialisation:

Serialising the car object now, that is converting the object into JSON format:

Car one = new Car();
Gson gson = new GSON();
String serialised_output= gson.toJson(car);//serialised_output=>JSON

Deserialisation:

Deserializing the JSON now, that is converting the JSON into java objects:

Car car = gson.fromJson(serialised_output,Car.class);

Finally, you can find the GitHub link for gson library here in case you want to know more about it or if you are facing some issues with the library, you can report them there. This is the link to the official Google group for discussions related to gson.

Continue ReadingSerialisation, Deserialisation and gson in Open Event Android

Debugging JSON Files of Sample Events for Open Event Android using Stetho

In this blog, I will talk about data validation and debugging in Android using the Stetho-A debug bridge for Android by Facebook. Most Android applications have JSON files to populate the elements like RecyclerViews, ListViews and different types of Layouts. Stetho is a debug tool for Android which uses the well-known Chrome Developer tools as it’s User Interface. With Stetho, we can see all our incoming JSON data in spreadsheets making debugging much easier and fun. What’s more, it’s completely Open source.

Getting started with Stetho

To start with, we need to enable Stetho by adding it as one of the dependencies in the build.gradle file.

compile 'com.facebook.stetho:stetho:1.4.2'

It is already enabled in the Open Event Android app.

Now, you need to add the api endpoint for your sample in the config.json file in the project(if you are using the Android repo for debugging). The config.json file is at app/assets/

In case, you are using an Open Event generated app then there’s no need to do that.

Now connect your phone through the USB cable to your laptop and start your application.

Next, you have to browse to chrome://inspect where you will see your device in the window as shown below.

 

On clicking “Inspect”, you will be shown the Stetho debug tool interface. Something like this:

When you download the details by clicking “Yes” on the starting of the application, be sure to keep it connected to the Stetho tool in the ‘inspect’ mode.

Once the data has loaded, go to Web SQL-> default.realm. You will see tabs like:-

class_speaker, class_sponsor, class_session, class_track among others.

On clicking them, you will see well-organised tables that show all linked attributes of the class that you selected.

Sessions can be seen along with all the related information in a tabular format.

Stetho displays speakers of the event and their information in an easy-to-read tabular format.

Micro locations of the event along with related information in a tabular format.

This is how you can view all the attributes of your sample in a tabular layout and hence, debug them easily. Although, Stetho is about much more than this, this is all I will talk about in this blog.

You can read more about Stetho and it’s functionalities here.

The official repo for Stetho can be found here for the source code.

Continue ReadingDebugging JSON Files of Sample Events for Open Event Android using Stetho

Creating A Sample Event Web App Using the Open Event Framework

I have been creating sample events for the Open Event Framework by creating JSON files for them. In this blog, I will walk you through 4 easy steps to create a sample event for FOSSASIA Open Event. These are the steps that I followed while I contributed in making sample web apps:

Let’s start by taking the example of a sample event. I recently created a sample for FBF8’2017. F8 is conference held by Facebook annually.

I started by visiting the official F8 site. I personally used Google Chrome for it as I was almost positive that the official site will be having an API which will make it easier for me to extract JSON for the event. Also, Google Chrome developer tools make it easier to access the API. I have previously explained how to access JSON through APIs in one of my previous blog posts that can be found here.

So in the F8 site, I looked for the page that will lead me to the full schedule of the conference:

After clicking on the “SEE FULL SCHEDULE” button, this is where it leads us, with the developer tools ON.

That weird named file on that appears in under the XHR tab contains our JSONs!

[{1492502400: [{day: 1, sessionTitle: "Registration", sessionSlug: "day-1-registration",…},…],…}, {,…}]

0:{1492502400: [{day: 1, sessionTitle: "Registration", sessionSlug: "day-1-registration",…},…],…}

1:{,…}

This is how the file looks on clicking it at first. You must be seeing small clickable arrows on the sides which expand the JSON to reveal all the contents. But okay, we have crossed our first step: Obtaining JSON from the website’s API!

Step 2: Tailoring the JSON according to the Open Event format needs to be done now. To view the latest API format, visit here. Now this step is obviously most time-consuming and tedious. We need to modify the API we obtained from the F8 site to meet the Open Event requirements.

I mainly used Sublime text editor to do this. It has this great feature to find and replace text in text files.

Taking an example of a session object. This is what we will get from the API:-

allDay:false

createdAt:"2017–01–28T01:24:12.770Z"

day:1

displayTime:"3:00pm"

endTime:{__type: "Date", iso: "2017–04–18T15:50:00.000Z"}

iso:"2017–04–18T15:50:00.000Z"

__type:"Date"

hasDetails:true

objectId:"62yCeJMvAf"

ogImage:{__type: "File", name: "296643cca9aad196bbd3534324c52049_Making Great Camera Effects.png",…}

name:"296643cca9aad196bbd3534324c52049_Making Great Camera Effects.png"

url:"https://f82017-parse.s3.amazonaws.com/296643cca9aad196bbd3534324c52049_Making%20Great%20Camera%20Effects.png"

__type:"File"

onMySchedule:false

sessionDescription:"With AI that transforms images, Facebook's Camera Effects Platform lets you create animated masks, interactive overlays, frames, and other effects that people can apply to their photos and videos in the Facebook camera. Learn best technical and design practices for creating effects using this innovative technology, and see examples of successful effects."

sessionLocation:"G"

sessionSlug:"making-great-camera-effects"

sessionTitle:"Making Great Camera Effects"

sortTime:"1492527600"

speakers:[{speakerName: "Volodymyr Giginiak", createdAt: "2017–01–28T01:27:49.292Z",…},…]

0:{speakerName: "Volodymyr Giginiak", createdAt: "2017–01–28T01:27:49.292Z",…}

1:{speakerName: "Stef Smet", createdAt: "2017–01–28T01:30:16.022Z",…}

startTime:{__type: "Date", iso: "2017–04–18T15:00:00.000Z"}

iso:"2017–04–18T15:00:00.000Z"

__type:"Date"

tags:["Camera"]

0:"Camera"

updatedAt:"2017–04–18T19:19:16.501Z"

Now, this is where I used the find & replace feature in Sublime text to make it look like the open-event standard JSON. For instance, replacing “sessionLocation” with “microlocation”, “sessionDescription” with “long_abstract” and so on. Yes, there were a few manual changes as well that I had to make.

This is how the final JSON session object looked like after completing step 2:

{
     "short_abstract": "",
     "id": 26,
     "subtitle": "making-great-camera-effects",
     "title": "Making Great Camera Effects",
     "long_abstract": "With AI that transforms images, Facebook's Camera Effects Platform lets you create animated masks, interactive overlays, frames, and other effects that people can apply to their photos and videos in the Facebook camera. Learn best technical and design practices for creating effects using this innovative technology, and see examples of successful effects.",
     "end_time": "2017-04-18T15:50:00-07:00",
     "microlocation": {
        "id": 5,
        "name": "Hall G"
     },
     "start_time": "2017-04-18T15:00:00-07:00",
     "comments": "",
     "language": "",
     "slides": "",
     "audio": "",
     "video": "",
     "signup_url": "",
     "state": "accepted",
     "session_type": {
        "id": 6,
        "name": "50 minutes session",
        "length": "00.50"
     },
     "track": {
        "id": 2,
        "name": "Camera",
        "color": "#FF3046"
     },
     "speakers": [{
           "city": "",
           "heard_from": "",
           "icon": "",
           "long_biography": "",
           "small": "",
           "photo": "",
           "thumbnail": "",
           "short_biography": "",
           "speaking_experience": "",
           "sponsorship_required": "",
           "organisation": "Facebook",
           "id": 40,
           "name": "Volodymyr Giginiak"
        }
     ]
  }

Apart from this, there were places where I had to dig for information about speakers, their social website links, their images and so on because the open-event JSON format expects all this.

Step 3: JSON Validation

It is very likely that one of your JSON files will contain invalid JSON strings. So it is important to validate them. I used this online tool and I liked it very much. Once the JSON is validated, everything is set. All you need to now do is-move to step 4!

Step 4: Web app generation

Compress your JSON archive in .zip format and then go to the webapp generator. Enter your email ID and upload the zip file. Now, you can deploy, download and preview your personalised web-app, generated by FOSSASIA open-event web app generator.

The official web-app generator repo is here.

If you want to contribute to FOSSASIA by creating a sample, you can create an issue in the official open-event repo, along with a PR which contains the JSON sample files, the zipped file and a link to the web-app deployment on GitHub pages.

Continue ReadingCreating A Sample Event Web App Using the Open Event Framework