Deploying a Postgres-Based Open Event Server to Kubernetes

In this post, I will walk you through deploying the Open Event Server on Kubernetes, hosted on Google Cloud Platform’s Compute Engine. You’ll be needing a Google account for this, so create one if you don’t have one.

First, I cd into the root of our project’s Git repository. Now I need to create a Dockerfile. I will use Docker to package our project into a nice image which can be then be “pushed” to Google Cloud Platform. A Dockerfile is essentially a text doc which simply contains the commands required to assemble an image. For more details on how to write one for your project specifically, check out Docker docs. For Open Event Server, the Dockerfile looks like the following:

FROM python:3-slim

ENV INSTALL_PATH /open_event
RUN mkdir -p $INSTALL_PATH

WORKDIR $INSTALL_PATH

# apt-get update and update some packages
RUN apt-get update && apt-get install -y wget git ca-certificates curl && update-ca-certificates && apt-get clean -y


# install deps
RUN apt-get install -y --no-install-recommends build-essential python-dev libpq-dev libevent-dev libmagic-dev && apt-get clean -y

# copy just requirements
COPY requirements.txt requirements.txt
COPY requirements requirements

# install requirements
RUN pip install --no-cache-dir -r requirements.txt 
RUN pip install eventlet

# copy remaining files
COPY . .

CMD bash scripts/docker_run.sh

These commands simply install the dependencies and set up the environment for our project. The final CMD command is for running our project, which, in our case, is a server.

After our Dockerfile is configured, I go to Google Cloud Platform’s console and create a new project:

Screen Shot 2018-08-09 at 7.37.34 PM

Once I enter the product name and other details, I enable billing in order to use Google’s cloud resources. A credit card is required to set up a billing account, but Google doesn’t charge any money for that. Also, one of the perks of being a part of FOSSASIA was that I had about $3000 in Google Cloud credits! Once billing is enabled, I then enable the Container Engine API. It is required to support Kubernetes on Google Compute Engine. Next step is to install Google Cloud SDK. Once that is done, I run the following command to install Kubernetes CLI tool:

gcloud components install kubectl

Then I configure the Google Cloud Project Zone via the following command:

gcloud config set compute/zone us-west1-a

Now I will create a disk (for storing our code and data) as well as a temporary instance for formatting that disk:

gcloud compute disks create pg-data-disk --size 1GB
gcloud compute instances create pg-disk-formatter
gcloud compute instances attach-disk pg-disk-formatter --disk pg-data-disk

Once the disk is attached to our instance, I SSH into it and list the available disks:

gcloud compute ssh "pg-disk-formatter"

Now, ls the available disks:

ls /dev/disk/by-id

This will list multiple disks (as shown in the Terminal window below), but the one I want to format is “google-persistent-disk-1”.

Screen Shot 2018-08-09 at 7.48.39 PM.png

Now I format that disk via the following command:

sudo mkfs.ext4 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/disk/by-id/google-persistent-disk-1

Finally, after the formatting is done, I exit the SSH session and detach the disk from the instance:

gcloud compute instances detach-disk pg-disk-formatter --disk pg-data-disk

Now I create a Kubernetes cluster and get its credentials (for later use) via gcloud CLI:

gcloud container clusters create opev-cluster
gcloud container clusters get-credentials opev-cluster

I can additionally bind our deployment to a domain name! If you already have a domain name, you can use the IP reserved below as an A record of your domain’s DNS Zone. Otherwise, get a free domain at freenom.com and do the same with that domain. This static external IP address is reserved in the following manner:

gcloud compute addresses create testip --region us-west1

It’ll be useful if I note down this IP for further reference. I now add this IP (and our domain name if I used one) in Kubernetes configuration files (for Open Event Server, these were specific files for services like nginx and PostgreSQL):

#
# Deployment of the API Server and celery worker
#
kind: Deployment
apiVersion: apps/v1beta1
metadata:
name: api-server
namespace: web
spec:
replicas: 1
template:
metadata:
labels:
app: api-server
spec:
volumes:
- name: data-store
emptyDir: {}
containers:
- name: api-server
image: eventyay/nextgen-api-server:latest
command: ["/bin/sh","-c"]
args: ["./kubernetes/run.sh"]
livenessProbe:
httpGet:
path: /health-check/
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 3
ports:
- containerPort: 8080
protocol: TCP
envFrom:
- configMapRef:
name: api-server
env:
- name: C_FORCE_ROOT
value: 'true'
- name: PYTHONUNBUFFERED
value: 'TRUE'
- name: DEPLOYMENT
value: 'api'
- name: FORCE_SSL
value: 'yes'
volumeMounts:
- mountPath: /opev/open_event/static/uploads/
name: data-store
- name: celery
image: eventyay/nextgen-api-server:latest
command: ["/bin/sh","-c"]
args: ["./kubernetes/run.sh"]
envFrom:
- configMapRef:
name: api-server
env:
- name: C_FORCE_ROOT
value: 'true'
- name: DEPLOYMENT
value: 'celery'
volumeMounts:
- mountPath: /opev/open_event/static/uploads/
name: data-store
restartPolicy: Always

These files will look similar for your specific project as well. Finally, I run the deployment script provided for Open Event Server to deploy with defined configuration:

./kubernetes/deploy.sh create all

This is the specific deployment script for this project; for your app, this will be a bunch of simple kubectl create * commands specific to your app’s context. Once this script completes, our app is deployed! After a few minutes, I can see it live at the domain I provided above!

I can also see our project’s statistics and set many other attributes at Google Cloud Platform’s console:

Screen Shot 2018-08-09 at 7.49.42 PM.png

And that’s it for this post! For more details on deploying Docker-based containers on Kubernetes clusters hosted by Google, visit Google Cloud Platform’s documentation.

Resources

Continue ReadingDeploying a Postgres-Based Open Event Server to Kubernetes

Implementing Access Codes in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create access codes for his or her event.  Access codes can be used to password protect hidden tickets reserved for sponsors, members of the press and media. This blog post explains how we have integrated access codes creation in the frontend utilising the various features of Ember JS and Semantic UI.

Create Access code component

We will be creating a separate component for creating access code. To create it we will use the following command:

ember g component forms/events/view/create-access-code

This will create the following files:

  1. components/forms/events/view/create-access-code.js
  2. templates/components/forms/events/view/create-access-code.hbs
  3. tests/integration/components/forms/events/view/create-access-code-test.js

Create-access-code.hbs

This file includes the handlebar syntax to design the front end of the access code component. The whole template is nested inside the Semantic UI’s form class. Some of the helpers used are as follows:

  • Ember Input Helper: It has been used extensively throughout the template in order to take input from the event organizer. For e.g.:
{{input type=‘text’ name=‘access_code’ value=data.code}}
  • Semantic Radio Button: The semantic radio button has been used in order to allow the organizer to select the state of the access-code. He/She can choose if the access-code is active or inactive.

<div class="grouped inline fields">
    <label class="required">{{t 'Status'}}</label>
    <div class="field">
      {{ui-radio current=data.isActive name='status' label='Active' value='true' onChange=(action (mut data.isActive))}}
    </div>
    <div class="field">
      {{ui-radio name='status' label='Inactive' value='false' current=data.isActive onChange=(action (mut data.isActive))}}
    </div>
  </div>
  • Date Time Picker: The organizer can set the validity of the access code as well. We have used date-picker and time-picker components which were already created in the project. They have been used in the following way:

<div class="fields">
        <div class="wide field {{if device.isMobile 'sixteen' 'five'}}">
          <label>{{t 'Valid from'}}</label>
          {{widgets/forms/date-picker id='start_date' value=data.validFromDate rangePosition='start'}}
          <div class="ui hidden divider"></div>
          {{widgets/forms/time-picker id='start_time' value=data.validFromTime rangePosition='start'}}
        </div>
        <div class="wide field {{if device.isMobile 'sixteen' 'five'}}">
          <label>{{t 'Expires on'}}</label>
          {{widgets/forms/date-picker id='end_date' value=data.validTillDate rangePosition='end'}}
          <div class="ui hidden divider"></div>
          {{widgets/forms/time-picker id='end_time' value=data.validTillTime rangePosition='end'}}
        </div>
      </div>

Create-access-code.js

We use this file as the core of the component and handle the following use cases:

  1. Validation of the input. We show warning if something is wrong.
  2. Actions used by the various elements of the templates.
  3. Providing the link for the access-code.
  4. Saving the access-code.

accessCode : '',
  accessUrl  : computed('data.code', function() {
    const params = this.get('router._router.currentState.routerJsState.params');
    this.set('data.accessUrl', location.origin + this.get('router').urlFor('public', params['events.view'].event_id, { queryParams: { access_code: this.get('data.code') } })) ;
    return this.get('data.accessUrl');
  }),
  actions: {
    toggleAllSelection(allTicketTypesChecked) {
      this.set('allTicketTypesChecked', allTicketTypesChecked);
      if (allTicketTypesChecked) {
        this.set('data.tickets', this.get('data.event.tickets').slice());
      }
    },
    updateTicketSelections(newSelection) {
      if (newSelection.length === this.get('data.event.tickets').length) {
        this.set('allTicketTypesChecked', true);
      } else {
        this.set('allTicketTypesChecked', false);
      }
    },
    submit(data) {
      this.onValid(() => {
        data.save()
          .then(() => {
            this.get('notify').success(this.get('l10n').t('Access code has been successfully created.'));
            this.get('router').transitionTo('events.view.tickets.access-codes');
          })
          .catch(() => {
            this.get('notify').error(this.get('l10n').t('An unexpected error has occurred. Access code cannot be created.'));
          });
      });
    }
  }

Create-access-code-test.js

We can specify the tests in order to test the compatibility of the component here. For now, we will just write a simple test which checks if the component is rendered or not.

import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';

module('Unit | Controller | events/view/tickets/access codes/create', function(hooks) {
  setupTest(hooks);


  test('it exists', function(assert) {
    let controller = this.owner.lookup('controller:events/view/tickets/access-codes/create');
    assert.ok(controller);
  });
});

Now that we are done, setting up our component, we just need to add it in our application. We can achieve that using the following:

{{forms/events/view/create-access-code data=model}}

The model passed to the component is fetched from the create-access-code.js file.

References

Continue ReadingImplementing Access Codes in Open Event Frontend

Implementing Custom Date and Time Picker with 2-way Data Binding Support

The Data binding library is one of the most popular libraries among the android developers. We have been using it in the Open Event Organiser Android app for building interactive UI’s for some time now. The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. This blog explains how we implemented our own custom Date and Time picker with 2-way data binding support using the Data binding framework.

Why custom picker ?

One specific requirement in the app is to have a button, clicking on that button should open a DatePicker which would allow the user to select the date. A similar behaviour was required to allow the user to select the time as well. In order to handle this requirement we were using Binding Adapters on Button. For eg. the following Binding Adapter allowed us to define a property date on a button and set an Observable String as it’s value. We implemented a similar Binding Adapter for selecting time as well.

@BindingAdapter("date")
public static void bindDate(Button button, ObservableField<String> date) {
    String format = DateUtils.FORMAT_DATE_COMPLETE;

    bindTemporal(button, date, format, zonedDateTime ->
        new DatePickerDialog(button.getContext(), (picker, year, month, dayOfMonth) ->
                setPickedDate(
                    LocalDateTime.of(LocalDate.of(year, month + 1, dayOfMonth), zonedDateTime.toLocalTime()),
                    button, format, date),
                zonedDateTime.getYear(), zonedDateTime.getMonthValue() - 1, zonedDateTime.getDayOfMonth()));
  }

It calls the bindTemporal method which takes in a function along with the button, date and the format and does two things. First, it sets the value of the date as the text of the button. Secondly, it attaches a click listener to the button and applies the function passed in as the argument when clicked. Below is the bindTemporal method for reference:

private static void bindTemporal(Button button, ObservableField<String> date, String format, Function<ZonedDateTime, AlertDialog> dialogProvider) {
        if (date == null)
            return;

        String isoDate = date.get();
        button.setText(DateUtils.formatDateWithDefault(format, isoDate));

        button.setOnClickListener(view -> {
            ZonedDateTime zonedDateTime = ZonedDateTime.now();
            try {
                zonedDateTime = DateUtils.getDate(isoDate);
            } catch (DateTimeParseException pe) {
                Timber.e(pe);
            }
            dialogProvider.apply(zonedDateTime).show();
        });
    }

It was working pretty well until recently when we started getting deprecation warnings about using Observable fields as a parameter of Binding Adapter. Below is the full warning:

Warning:Use of ObservableField and primitive cousins directly as method parameters is deprecated and support will be removed soon. Use the contents as parameters instead in method org.fossasia.openevent.app.common.app.binding.DateBindings.bindDate

The only possible way that we could think of was to pass in regular String in place of Observable String. Now if we pass in a regular String object then the application won’t be reactive. Hence we decided to implement our own custom view to resolve this problem.

Custom Date and Time Picker

We decided to create an Abstract DateTimePicker class which will hold all the common code of our custom Date and Time pickers. It is highly recommended that you go through this awesome blog post first before reading any further. We won’t be going through the details already explained in the post.

Following are the important features of this Abstract class:

  1. It extends the AppCompatButton class.
  2. It stores an ObservableString named value and an OnDateTimeChangedListener as it’s field. We will discuss the change listener later in the article.
  3. It implements the three mandatory constructors and calls it’s super method. It also calls the init method which sets the current date and time as the default.
  4. It has a bindTemporal method which is the same as we discussed earlier.
  5. It has a setPickedDate method which sets the selected date/time as the text for the button so that users can see the selected date/time on the button itself. Moreover it notifies the change listener about the change in date if attached.
  6. It has an abstract method called setValue. It will be implemented in the sub classes and used to set the date or time value for the field named value.

You can check the full implementation here.

The OnDateTimeChangedListener which we mentioned above is an extremely simple interface. It defines a simple method onDateChanged which takes in the selected date as the argument.

public interface OnDateTimeChangedListener {
    void onDateChanged(ObservableString newDate);
}

Let’s have a look at the implementation of the DatePicker class. The key features of this class are:

  1. It extends the AbstractDateTimePicker class and implements the necessary constructors calling the corresponding super constructor.
  2. It implements the method setValue which sets the date or time passed in to the field value. It also calls the bindTemporal method of the super class.

public class DatePicker extends AbstractDateTimePicker {
    public DatePicker(Context context) {
        super(context);
    }

    public DatePicker(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DatePicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

  public void setValue(String value) {
        ObservableString observableValue = getValue();
        if (observableValue.get() == null || !TextUtils.equals(observableValue.get(), value)) {
            observableValue.set(value);
            String format = DateUtils.FORMAT_DATE_COMPLETE;

            bindTemporal(value, format, zonedDateTime ->
                new DatePickerDialog(this.getContext(), (picker, year, month, dayOfMonth) ->
                    setPickedDate(
                        LocalDateTime.of(LocalDate.of(year, month + 1, dayOfMonth), zonedDateTime.toLocalTime()), format),
                    zonedDateTime.getYear(), zonedDateTime.getMonthValue() - 1, zonedDateTime.getDayOfMonth()));
        }
    }
}

Next we discuss the BindingAdapter and the InverseBindingAdapter for the custom DatePicker which allows the data binding framework to set the action to be performed when date changes and get the date from the view respectively.

@BindingAdapter(value = "valueAttrChanged")
public static void setDateChangeListener(DatePicker datePicker, final InverseBindingListener listener) {
        if (listener != null) {
            datePicker.setOnDateChangedListener(newDate -> listener.onChange());
        }
    }

@InverseBindingAdapter(attribute = "value")
public static String getRealValue(DatePicker datePicker) {
    return datePicker.getValue().get();
}

Now in order to use our view, we can simply define it in the layout file as shown below:

<org.fossasia.openevent.app.ui.views.DatePicker
                    style="?attr/borderlessButtonStyle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/purple_500"
                    app:value="@={ date }" />

The key thing to notice is the use of @= instead of @ which denotes two way data binding.    

Conclusion

The Android Data binding framework is extremely powerful and flexible at the same time. We can use it for our custom requirements as shown in this article.

References

 

 

 

 

 

Continue ReadingImplementing Custom Date and Time Picker with 2-way Data Binding Support

Attendee details in the Open Event Android App

To be able to create an order we first need to create an attendee with whom we can associate an order. Let’s see how in Open Event Android App we are creating an attendee.

We are loading the event details from our local database using the id variable. Since only logged in users can create an attendee, if the user is not logged in then the user is redirected to the login screen. If any errors are encountered while creating an attendee then they are shown in a toast message to the user. When the user clicks on the register button a POST request is sent to the server with the necessary details of the attendee. In the POST request we are passing an attendee object which has the id, first name, last name and email of the attendee. The ticket id and event id is also sent.

attendeeFragmentViewModel.loadEvent(id)

if (!attendeeFragmentViewModel.isLoggedIn()) {
redirectToLogin()
Toast.makeText(context, "You need to log in first!", Toast.LENGTH_LONG).show()
}

 

attendeeFragmentViewModel.message.observe(this, Observer {
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
})

attendeeFragmentViewModel.progress.observe(this, Observer {
it?.let { Utils.showProgressBar(rootView.progressBarAttendee, it) }
})

 

attendeeFragmentViewModel.event.observe(this, Observer {
it?.let { loadEventDetails(it) }
})

rootView.register.setOnClickListener {
val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
firstname = firstName.text.toString(),
lastname = lastName.text.toString(),
email = email.text.toString(),
ticket = ticketId,
event = eventId)

attendeeFragmentViewModel.createAttendee(attendee)

 

We are using a method called loadEvent in the above code which is defined in the View Model let’s have a look. We are throwing an IllegalStateException if the id is equal to -1 because this should never happen. Then we are fetching the event from the database in a background thread. If we face any errors while fetching the event we report it to the user

 

fun loadEvent(id: Long) {
if (id.equals(-1)) {
throw IllegalStateException("ID should never be -1")
}
compositeDisposable.add(eventService.getEvent(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
event.value = it
}, {
Timber.e(it, "Error fetching event %d", id)
message.value = "Error fetching event"
}))
}

 

This method is used to create an attendee. We are checking if the user has filled all the fields if any of the fields is empty a toast message is shown. Then we send a POST request to the server in a background thread. The progress bar starts loading as soon as the request is made and then finally when the attendee has been created successfully, the progress bar stops loading and a success message is shown to the user. If we face any errors while creating an attendee, an error message is shown to the user.

fun createAttendee(attendee: Attendee) {
if (attendee.email.isNullOrEmpty() || attendee.firstname.isNullOrEmpty() || attendee.lastname.isNullOrEmpty()) {
message.value = "Please fill all the fields"
return
}

compositeDisposable.add(attendeeService.postAttendee(attendee)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
progress.value = true
}.doFinally {
progress.value = false
}.subscribe({
message.value = "Attendee created successfully!"
Timber.d("Success!")
}, {
message.value = "Unable to create Attendee!"
Timber.d(it, "Failed")
}))
}

 

This function sends a POST request to the server and stores the attendee details in the local database.

fun postAttendee(attendee: Attendee): Single<Attendee> {
return attendeeApi.postAttendee(attendee)
.map {
attendeeDao.insertAttendee(it)
it
}

 

This is how the attendee details are inserted into the local database. In case of a conflict the attendee object gets replaced.

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAttendee(attendee: Attendee)

 

Resources

  1. ReactiveX official documentation : http://reactivex.io/
  2. Vogella RxJava 2 – Tutorial : http://www.vogella.com/tutorials/RxJava/article.html
  3. Androidhive RxJava Tutorial : https://www.androidhive.info/RxJava/
Continue ReadingAttendee details in the Open Event Android App

Implementation of Sponsors API in Open Event Organizer Android App

New contributors to this project are sometimes not experienced with the set of libraries and MVP pattern which this app uses. This blog post is an attempt to walk a new contributor through some parts of the code of the app by implementing an operation for an endpoint of the API. We’ll be implementing the sponsor endpoint.

Open Event Organizer Android app uses a robust architecture. It is presently using the MVP (Model-View-Presenter) architecture. Therefore, this blog post aims at giving some brief insights to the app architecture through the implementation Sponsor endpoint. This blog post will focus only on one operation of the endpoint – the list operation – so as to make the post short enough.

This blog post relates to Pull Request #901 of Open Event Organizer App.

Project structure:

These are the parts of the project structure where major updates will be made for the implementation of Sponsor endpoint:

core

data

Setting up elements in the data module for the respective endpoint

Sponsor.java

@Data
@Builder
@Type(“sponsor”)
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@EqualsAndHashCode(callSuper = false, exclude = { “sponsorDelegate”, “checking” })
@Table(database = OrgaDatabase.class)
public class Sponsor extends AbstractItem<Sponsor, SponsorsViewHolder> implements Comparable<Sponsor>, HeaderProvider {

   @Delegate(types = SponsorDelegate.class)
   private final SponsorDelegateImpl sponsorDelegate = new         SponsorDelegateImpl(this);

This class uses Lombok, Jackson, RaizLabs-DbFlow, extends AbstractItem class (from Fast Adapter) and implements Comparable and HeaderProvider.

All the annotations processor help us reduce boilerplate code.

From the Lombok plugin, we are using:

Lombok has annotations to generate Getters, Setters, Constructors, toString(), Equal() and hashCode() methods. Thus, it is very efficient in reducing boilerplate code

@Getter,  @Setter, @ToString, @EqualsAndHashCode

@Data is a shortcut annotation that bundles the features of @Getter, @Setter, @ToString and @EqualsAndHashCode

The @Delegate is used for direct calls to the methods that are annotated with it, to the specified delegate. It basically separates the model class from other methods which do not pertain to data.

Jackson

@JsonNaming – used to choose the naming strategies for properties in serialization, overriding the default. For eg:  KEBAB_CASE, LOWER_CASE, SNAKE_CASE, UPPER_CAMEL_CASE

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)

@JsonProperty – used to store the variable from JSON schema as the given variable name. So, “type” from JSON will be stored as sponsorType.

@JsonProperty(“type”)
public String sponsorType;

RaizLabs-DbFlow

DbFlow uses model classes which must be annotated using the annotations provided by the library. The basic annotations are – @Table, @PrimaryKey, @Column, @ForeignKey etc.

These will create a table named attendee with the columns and the relationships annotated.

SponsorDelegate.java and SponsorDelegateImpl.java

The above are required only for method declarations of the classes and interfaces that Sponsor.java extends or implements. These basically separate the required method overrides from the base item class.

public class SponsorDelegateImpl extends AbstractItem<Sponsor, SponsorsViewHolder> implements SponsorDelegate {

SponsorRepository.java and SponsorRepositoryImpl.java

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.

public interface SponsorRepository {
  @NonNull
  Observable<Sponsor> getSponsors(long eventId, boolean reload);
}

Presently the app uses MVP architecture and the core package contains respective Views and their Presenters, whereas the data package contains the Model implementation.

To understand Observable, one will need to dive in RxJava. This video of a presentation by Jake Wharton can be a great start.

So, basically Observables represent sources of data, and whenever there is a change in our observable, i.e. our data, it fires an event and we get to know about it only if we have subscribed to it.

Setting up elements in core module (views and presenters)

Presently the app uses MVP architecture and the core package contains respective Views and Presenters for different properties of an event.

SponsorView.java

public interface SponsorsView extends Progressive, Erroneous, Refreshable, Emptiable<Sponsor> {
}

SponsorsFragment.java

This is a simple Fragment that extends BaseFragment (a generic fragment class predefined in the project) and implements SponsorView interface.

SponsorsListAdapter.java

A simple adapter for a RecyclerView.

SponsorsPresenter.java

The presenter is the middle-man between model and view. All your presentation logic belongs to it. A Presenter in a MVP architecture is responsible for querying the model and updating the view, reacting to user interactions and updating the model.

In this Presenter we have used the @Inject annotation and some RxJava code:

@Inject
public SponsorsPresenter(SponsorRepository sponsorRepository, DatabaseChangeListener<Sponsor> sponsorChangeListener) {
  this.sponsorRepository = sponsorRepository;
  this.sponsorChangeListener = sponsorChangeListener;
}

The @Inject annotation is used to request dependencies. It can be used on a constructor, a field, or a method. If you annotate a constructor with @Inject, Dagger 2 can also use an instance of this object to fulfill dependencies. Note that we are not instantiating any object here ourselves.

Some RxJava part, which is not in the scope of this blog.

private void listenChanges() {
  sponsorChangeListener.startListening();
  sponsorChangeListener.getNotifier()
      .compose(dispose(getDisposable()))
      .map(DbFlowDatabaseChangeListener.ModelChange::getAction)
      .filter(action -> action.equals(BaseModel.Action.INSERT))
      .subscribeOn(Schedulers.io())
      .subscribe(sponsorModelChange -> loadSponsors(false), Logger::logError);
}

 

public void loadSponsors(boolean forceReload) {
  getSponsorSource(forceReload)
      .compose(dispose(getDisposable()))
      .compose(progressiveErroneousRefresh(getView(), forceReload))
      .toList()
      .compose(emptiable(getView(), sponsors))
      .subscribe(Logger::logSuccess, Logger::logError);
}

Make changes to Dependency Injection modules

We are using Dagger for Dependency Injection, and therefore we need to update the modules that provide endpoint specific objects.

This video and this blog are a great start to learn about dependency injection using dagger.

Dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.(source)

Dependency Injection in built upon the concept of Inversion of Control. Which says that a class should get its dependencies from outside. In simple words, no class should instantiate another class but should get the instances from a configuration class.

Dagger 2 analyzes the dependencies for you and generates code to help wire them together.  It relies purely on using Java annotation processors and compile-time checks to analyze and verify dependencies. It is considered to be one of the most efficient dependency injection frameworks built to date.

@Provides is basically required to specify that the annotated method returns an object that should be available for injection to dependencies using @Inject

@Singleton annotation signals to the Dagger compiler that the instance should be created only once in the application.

@Binds annotation is a replacement for @Provides methods that simply returns an injected parameter. Its generated implementation is likely to be more efficient. A method annotated with @Binds must be: abstract.

ApiModule.java

@Provides
@Singleton
SponsorApi providesSponsorApi(Retrofit retrofit) {    return retrofit.create(SponsorApi.class);
}    

ChangeListenerModule.java

@Provides
DatabaseChangeListener<Sponsor> providesSponsorChangeListener() {
   return new DbFlowDatabaseChangeListener<>(Sponsor.class);
}

NetworkModule.java

@Provides
Class[] providesMappedClasses() {
   return new Class[]{Event.class, Attendee.class, Ticket.class, User.class,
   EventStatistics.class, Faq.class, Copyright.class, Feedback.class,           Track.class, Session.class, Sponsor.class};
   }

RepoModule.java

@Binds
@Singleton
abstract SponsorRepository bindsSponsorRepository(SponsorRepositoryImpl sponsorRepositoryImpl);

MainFragmentBuilderModule.java

@ContributesAndroidInjector
abstract SponsorsFragment contributeSponsorsFragment();

Also remember to make this change to FragmentNavigator.java

case R.id.nav_sponsor:
fragment = SponsorsFragment.newInstance(eventId);
break;

XML resources to be added:

  1. sponsors_item.xml
  2. sponsors_fragment.xml
  3. Make changes to activity_main_drawer.xml to include “sponsors” option.

Here’s what the result looks like:

References:

Lombok Plugin: https://blog.fossasia.org/using-lombok-to-reduce-boilerplate-code-in-open-event-android-app/

Jackson: https://blog.fossasia.org/shrinking-model-classes-boilerplate-in-open-event-android-projects/

RaizLabs DbFlow: https://blog.fossasia.org/persistence-layer-in-open-event-organizer-android-app/

Dependency Injection:
https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-di-part-i-f5cc4e5ad878

Continue ReadingImplementation of Sponsors API in Open Event Organizer Android App

Use Of DBFlowChangeListener for Automatic Updation of FAQ List in the Open Event Orga App

In the Open Event Orga App, whenever a faq is created in via the CreatefaqFragment, the new faqs aren’t updated in the FAQ’s list. The user has to swipe down and refresh to see the newly created FAQ. This seems to be a major problem and hence to solve this issue a DBFlowChangeListener class has been created.

Following blog post shows the implementation of the ChangeListener class.

Steps of Implementation

In the FaqListPresenter constructor pass an argument of the type DatabaseChangeListener<Faq> and assign it to local variable faqChangeListener.

@Inject
public FaqListPresenter(FaqRepository faqRepository, DatabaseChangeListener<Faq> faqChangeListener) {
  this.faqRepository = faqRepository;
  this.faqChangeListener = faqChangeListener;
}

A method listenChanges( ) is made in the Presenter that will initiate the listening of database. Following code shows the listenChanges( ) . startListening( ) is a method in the DBFlowDatabaseChangeListener which is accessed with the help of DatabaseChangeListener interface.

private void listenChanges() {
  
  faqChangeListener.startListening();
  
  faqChangeListener.getNotifier()
      .compose(dispose(getDisposable()))
      .map(DbFlowDatabaseChangeListener.ModelChange::getAction)
      .filter(action -> action.equals(BaseModel.Action.INSERT) || action.equals(BaseModel.Action.DELETE))
      .subscribeOn(Schedulers.io())
      .subscribe(faqModelChange -> loadFaqs(false), Logger::logError);
}

In the startListening( )  in DatabaseChangeListener, DirectModelNotifier is used to get notified of database changes. It is ultimately registered for any kind of model change as shown in the following code snippet. ReplaySubject is one of the 4 kinds of subjects ( Publish Subject, Replay Subject, Behavior Subject, Async Subject) that RxJava provides. It emits all the items of the source Observable, regardless of when the subscriber subscribes.

public void startListening() {
  if (disposable == null || disposable.isDisposed())
      replaySubject = ReplaySubject.create();

  modelModelChangedListener = new DirectModelNotifier.ModelChangedListener<T>() {

      @Override
      public void onTableChanged(@Nullable Class<?> aClass, @NonNull BaseModel.Action action) {
          replaySubject.onNext(new ModelChange<>(null, action));
      }

      @Override
      public void onModelChanged(@NonNull T model, @NonNull BaseModel.Action action) {
          replaySubject.onNext(new ModelChange<>(model, action));
      }
  };

  DirectModelNotifier.get().registerForModelChanges(classType, modelModelChangedListener);
}

When the faqChangeListener is subscribed it calls the loadFaqs( ) , it ultimately loads the updated values from the database. Hence all the updated values are now displayed in the FaqListFragment.

public void loadFaqs(boolean forceReload) {
  getFaqSource(forceReload)
      .compose(dispose(getDisposable()))
      .compose(progressiveErroneousRefresh(getView(), forceReload))
      .toList()
      .compose(emptiable(getView(), faqs))
      .subscribe(Logger::logSuccess, Logger::logError);
}

Hence the use of DBFlowListener makes it possible to communicate between 2 fragments. Its usability has also been exploited in Tracks, Tickets, Sponsors fragments as well.

References:

  1. RaizLab’s DBFLow:      https://github.com/Raizlabs/DBFlow
  2. Docs related to DBFlow:             https://agrosner.gitbooks.io/dbflow/content/Observability.html
  3. Subjects used in RxJavahttps://blog.mindorks.com/understanding-rxjava-subject-publish-replay-behavior-and-async-subject-224d663d452f
Continue ReadingUse Of DBFlowChangeListener for Automatic Updation of FAQ List in the Open Event Orga App

Open Event Server – Export Attendees as CSV File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested.

The organizer can see all the attendees in a very detailed view in the event management dashboard. He can see the statuses of all the attendees. The possible statuses are completed, placed, pending, expired and canceled, checked in and not checked in. He/she can take actions such as checking in the attendee.

If the organizer wants to download the list of all the attendees as a CSV file, he or she can do it very easily by simply clicking on the Export As and then on CSV.

Let us see how this is done on the server.

Server side – generating the Attendees CSV file

Here we will be using the csv package provided by python for writing the csv file.

import csv
  • We define a method export_attendees_csv which takes the attendees to be exported as a CSV file as the argument.
  • Next, we define the headers of the CSV file. It is the first row of the CSV file.
def export_attendees_csv(attendees):
   headers = ['Order#', 'Order Date', 'Status', 'First Name', 'Last Name', 'Email',
              'Country', 'Payment Type', 'Ticket Name', 'Ticket Price', 'Ticket Type']
  • A list is defined called rows. This contains the rows of the CSV file. As mentioned earlier, headers is the first row.
rows = [headers]
  • We iterate over each attendee in attendees and form a row for that attendee by separating the values of each of the columns by a comma. Here, every row is one attendee.
  • The newly formed row is added to the rows list.
for attendee in attendees:
   column = [str(attendee.order.get_invoice_number()) if attendee.order else '-',
             str(attendee.order.created_at) if attendee.order and attendee.order.created_at else '-',
             str(attendee.order.status) if attendee.order and attendee.order.status else '-',
             str(attendee.firstname) if attendee.firstname else '',
             str(attendee.lastname) if attendee.lastname else '',
             str(attendee.email) if attendee.email else '',
             str(attendee.country) if attendee.country else '',
             str(attendee.order.payment_mode) if attendee.order and attendee.order.payment_mode else '',
             str(attendee.ticket.name) if attendee.ticket and attendee.ticket.name else '',
             str(attendee.ticket.price) if attendee.ticket and attendee.ticket.price else '0',
             str(attendee.ticket.type) if attendee.ticket and attendee.ticket.type else '']

   rows.append(column)
  • rows contains the contents of the CSV file and hence it is returned.
return rows
  • We iterate over each item of rows and write it to the CSV file using the methods provided by the csv package.
writer = csv.writer(temp_file)
from app.api.helpers.csv_jobs_util import export_attendees_csv
content = export_attendees_csv(attendees)
for row in content:
   writer.writerow(row)

Obtaining the Attendees CSV file:

Firstly, we have an API endpoint which starts the task on the server.

GET - /v1/events/{event_identifier}/export/attendees/csv

Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server to export the attendees of the event as a CSV file. It returns the URL of the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the above-mentioned URL.

References

Continue ReadingOpen Event Server – Export Attendees as CSV File

Open Event Server – Export Orders as CSV File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested.

The organizer can see all the orders in a very detailed view in the event management dashboard. He can see the statuses of all the orders. The possible statuses are completed, placed, pending, expired and canceled.

If the organizer wants to download the list of all the orders as a CSV file, he or she can do it very easily by simply clicking on the Export As and then on CSV.

Let us see how this is done on the server.

Server side – generating the Orders CSV file

Here we will be using the csv package provided by python for writing the csv file.

import csv
  • We define a method export_orders_csv which takes the orders to be exported as a CSV file as the argument.
  • Next, we define the headers of the CSV file. It is the first row of the CSV file.
def export_orders_csv(orders):
   headers = ['Order#', 'Order Date', 'Status', 'Payment Type', 'Total Amount', 'Quantity',
              'Discount Code', 'First Name', 'Last Name', 'Email']
  • A list is defined called rows. This contains the rows of the CSV file. As mentioned earlier, headers is the first row.
rows = [headers]
  • We iterate over each order in orders and form a row for that order by separating the values of each of the columns by a comma. Here, every row is one order.
  • The newly formed row is added to the rows list.
for order in orders:
   if order.status != "deleted":
       column = [str(order.get_invoice_number()), str(order.created_at) if order.created_at else '',
                 str(order.status) if order.status else '', str(order.paid_via) if order.paid_via else '',
                 str(order.amount) if order.amount else '', str(order.get_tickets_count()),
                 str(order.discount_code.code) if order.discount_code else '',
                 str(order.user.first_name)
                 if order.user and order.user.first_name else '',
                 str(order.user.last_name)
                 if order.user and order.user.last_name else '',
                 str(order.user.email) if order.user and order.user.email else '']
       rows.append(column)
  • rows contains the contents of the CSV file and hence it is returned.
return rows
  • We iterate over each item of rows and write it to the CSV file using the methods provided by the csv package.
writer = csv.writer(temp_file)
from app.api.helpers.csv_jobs_util import export_orders_csv
content = export_orders_csv(orders)
for row in content:
   writer.writerow(row)

Obtaining the Orders CSV file:

Firstly, we have an API endpoint which starts the task on the server.

GET - /v1/events/{event_identifier}/export/orders/csv

Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server to export the orders of the event as a CSV file. It returns the URL of the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}</span

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the aabove-mentionedURL.

References

Continue ReadingOpen Event Server – Export Orders as CSV File

Handling No internet cases in Open Event Android

It’s pretty common to face connectivity issues and when the user has no Internet connection he should be shown an appropriate response rather than allowing him to send requests to the server. Let’s have a look how we are handling such cases in Open Event Android

Firstly we need to add the required permission in the manifest. We need the permission to access the user’s WiFi state and network state.

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

We use this function to check if the user is connected to the Internet. This function return a Boolean which is true if the user is connected to the Internet otherwise it is false

private fun isNetworkConnected(): Boolean {
val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager

return connectivityManager?.activeNetworkInfo != null
}

 

This function is used to decide which screen should be shown to the user. If the user has an active Internet connection he will see events fragment but if there is no Internet he will see the no Internet card.

private fun showNoInternetScreen(show: Boolean) {
rootView.homeScreenLL.visibility = if (show) View.VISIBLE else View.GONE
rootView.noInternetCard.visibility = if (!show) View.VISIBLE else View.GONE
}

 

Let’s see how the above two functions are used in the events fragment. When the app starts we check if there is a need to show the no Internet screen. If the user is not connected to the Internet, the no Internet card will be shown. Then when the user clicks on retry, the events fragment is shown again if the user is connected to the Internet.

showNoInternetScreen(isNetworkConnected())

rootView.retry.setOnClickListener {
showNoInternetScreen(isNetworkConnected())
}

 

Let’s have a look a how the XML code looks, here we are only seeing a part of the code as the rest is pretty obvious. We have cardView and inside it all the views ie ImageView,TextView are inside a LinearLayout which has a vertical orientation so that all these views appear below each other.

<android.support.v7.widget.CardView
android:id="@+id/noInternetCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_margin_medium"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_margin_extra_large"
android:orientation="vertical">

<ImageView
android:id="@+id/noInternetImageView"
android:layout_width="@dimen/item_image_view_large"
android:layout_height="@dimen/item_image_view_large"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/layout_margin_large"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_no_internet" />

<TextView
android:id="@+id/noInternetTextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/layout_margin_large"
android:text="@string/no_internet_message"
android:textSize="@dimen/text_size_medium"
tools:text="No Internet" />

 

References

  1. AndroidHive tutorial – https://www.androidhive.info/2012/07/android-detect-internet-connection-status/
  2. Official Android Documentation – https://developer.android.com/training/monitoring-device-state/connectivity-monitoring
  3. StackOverflow – https://stackoverflow.com/questions/9570237/android-check-internet-connection
Continue ReadingHandling No internet cases in Open Event Android

Opening Orga App through Intent in Open Event Android App

In the Open Event Android App there is a section called “Manage events”, we direct the users to the Organizer’s app if the users have the app already installed otherwise we open the organizer’s app in the playstore. This way users are motivated to create events using the organizer’s app. Let’s see how this feature was implemented.

So when the user clicks on the menu item “Manage Events” startOrgaApp function is called with the package name of the organizer’s app as the argument. Android apps are uniquely identified by their package names in the playstore.

override fun onOptionsItemSelected(item: MenuItem?): Boolean {

when (item?.getItemId()) {
R.id.orgaApp -> {
startOrgaApp("org.fossasia.eventyay")
return true
}
}

 

Let’s have a look at the startOrgaApp function that we are calling above. We are using a try/catch block here. We are opening the app in the try block if it is installed otherwise we throw ActivityNotFoundException and if this exception happens we will catch it and use the showInMarket function to show the Organizer’s app in the market.

private fun startOrgaApp(packageName: String) {
val manager = activity?.packageManager
try {
val intent = manager?.getLaunchIntentForPackage(packageName)
?: throw ActivityNotFoundException()
intent.addCategory(Intent.CATEGORY_LAUNCHER)
startActivity(intent)
} catch (e: ActivityNotFoundException) {
showInMarket(packageName)
}
}

 

Now since we know this will never raise an exception there is no try catch block as the app with this package name will always be there. We just need to create an intent with the required parameters and then just pass the intent to startActivity

private fun showInMarket(packageName: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName"))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}

 

Lastly we just need to add the XML code to create a menu item. It should contain the id so that we can reference it and the title that will be visible.

<group android:id="@+id/profileMenu">
<item
android:id="@+id/orgaApp"
android:title="@string/manage_events" />
</group>

 

That’s it now you can open an app in the playstore if it is not installed or just open the app if the user has already installed the app.  

Resources

  1. Vogella Intent tutorial – http://www.vogella.com/tutorials/AndroidIntent/article.html
  2. Official Android Documentation Intent – https://developer.android.com/guide/components/intents-filters
  3. Javatpoint intent tutorial – https://www.javatpoint.com/android-explicit-intent-example
Continue ReadingOpening Orga App through Intent in Open Event Android App