Speaker details in the Open Event Orga App

The Open Event Organiser Android App is currently released in the Alpha phase on the Google Play Store here. This blog post explains how the speaker details feature has been implemented in the app.

Model

The model for Speaker is pretty straightforward. It includes the personal details of the speaker such as name, biography, country, social media profiles, designation etc. Apart from these details, every instance of speaker is associated with a single event. A speaker will also have multiple instances of sessions. Full implementation of the speaker’s model can be found here.

Network Call

We use Retrofit in order to make the network call and Jackson Factory to deserialize the data received from the call into an instance of the speaker model. The following endpoint provides us with the required information:

https://open-event-api-dev.herokuapp.com/speakers/{speaker_id}

Repository

In any typical android application using both network calls and data persistence, there is a need of a repository class to handle them. Speaker Repository handles the network call to the API in order to fetch the speaker details. It then saves the data returned by the api into the database asynchronously. It also ensures that we send the latest data that we have stored in the database to the view model. Given below is the full implementation for reference:

@Override
    public Observable<Speaker> getSpeaker(long speakerId, boolean reload) {
        Observable<Speaker> diskObservable = Observable.defer(() ->
            repository
                .getItems(Speaker.class, Speaker_Table.id.eq(speakerId)).take(1)
        );

        Observable<Speaker> networkObservable = Observable.defer(() ->
            speakerApi.getSpeaker(speakerId)
                .doOnNext(speaker -> repository
                    .save(Speaker.class, speaker)
                    .subscribe()));

        return repository
            .observableOf(Speaker.class)
            .reload(reload)
            .withDiskObservable(diskObservable)
            .withNetworkObservable(networkObservable)
            .build();
    }

ViewModel

The View Model is responsible for fetching the necessary details from the repository and displaying it in the view. It handles all the view binding logic. The most important method in the SpeakerDetailsViewModel is the getSpeakers method. It accepts a speaker id from the fragment, queries the repository for the details of the speaker and returns it back to the fragment in the form of a LiveData. Below is the full implementation of the getSpeakers method:

protected LiveData<Speaker> getSpeaker(long speakerId, boolean reload) {
        if (speakerLiveData.getValue() != null && !reload)
            return speakerLiveData;

        compositeDisposable.add(speakerRepository.getSpeaker(speakerId, reload)
            .compose(dispose(compositeDisposable))
            .doOnSubscribe(disposable -> progress.setValue(true))
            .doFinally(() -> progress.setValue(false))
            .doOnNext(speaker -> speakerLiveData.setValue(speaker))
            .flatMap(speaker -> sessionRepository.getSessionsUnderSpeaker(speakerId, reload))
            .toList()
            .subscribe(sessionList -> sessionLiveData.setValue(sessionList),
                throwable -> error.setValue(ErrorUtils.getMessage(throwable))));

        return speakerLiveData;
    }

We add the disposable to a composite disposable and dispose it in the onCleared method of the View Model. The full implementation of the View Model can be found here.

Fragment

The SpeakerDetailsFragment acts as the view and is responsible for everything the user sees on the screen. It accepts the id of the speaker whose details are to be displayed in the constructor. When an instance of the fragment is created it sets up it’s view model and inflates it’s layout using the Data binding framework.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
   context = getContext();
   binding = DataBindingUtil.inflate(inflater, R.layout.speaker_details_fragment, container, false);
   speakerDetailsViewModel = ViewModelProviders.of(this, viewModelFactory).get(SpeakerDetailsViewModel.class);
   speakerId = getArguments().getLong(SPEAKER_ID);

   AppCompatActivity activity = ((AppCompatActivity) getActivity());
   activity.setSupportActionBar(binding.toolbar);

   ActionBar actionBar = activity.getSupportActionBar();
   if (actionBar != null) {
       actionBar.setHomeButtonEnabled(true);
       actionBar.setDisplayHomeAsUpEnabled(true);
   }

   return binding.getRoot();
}

In the onStart method of the fragment we load the data by calling the getSpeaker method in the view model. Then we set up the RecyclerView for the sessions associated with the speaker. Lastly we also set up the refresh listener which can be used by the user to refresh the data.

@Override
public void onStart() {
   super.onStart();

   speakerDetailsViewModel.getProgress().observe(this,this::showProgress);
   speakerDetailsViewModel.getError().observe(this, this::showError);
   loadSpeaker(false);

   setupRecyclerView();
   setupRefreshListener();
}

Once the data is returned we simply set it on the layout by calling setSpeaker on the binding.

@Override
public void showResult(Speaker item) {
   binding.setSpeaker(item);
}

The full implementation of the SpeakerDetailsFragment can be found here.

Sessions Adapter

The SessionsAdapter is responsible for handling the RecyclerView of sessions associated with the speaker. The most important method in the adapter is the setSessions method. It accepts a list of sessions and shows it as the contents of the recycler view. It uses the DiffUtil.calculateDiff method to create a DiffResult which will be used by the adapter to figure out where to insert items.

protected void setSessions(final List<Session> newSessions) {
        if (sessions == null) {
            sessions = newSessions;
            notifyItemRangeInserted(0, newSessions.size());
        } else {
            DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                @Override
                public int getOldListSize() {
                    return sessions.size();
                }

                @Override
                public int getNewListSize() {
                    return newSessions.size();
                }

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                    return sessions.get(oldItemPosition).getId()
                        .equals(newSessions.get(newItemPosition).getId());
                }

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                    return sessions.get(oldItemPosition).equals(newSessions.get(newItemPosition));
                }
            });
            sessions = newSessions;
            result.dispatchUpdatesTo(this);
        }
    }

The full implementation of the Adapter can be found here.

References

Continue ReadingSpeaker details in the Open Event Orga App

Creating multiple Attendee Details sections in Open Event Android

Whenever a User picks the quantities of tickets he/she wants to buy for an event, that no of ticket details are required to be provided by the user. For this multiple attendee details section is displayed so that user can type in the information of all the Attendee. This blog post will help you understand how this is implemented in Open Event Android

The idea is to create a Recycler View for attendee objects and add empty attendee objects to the recycler based on what total quantity of tickets are selected by the User. For this first, we will have to create a Recycler View for attendee objects.

RecyclerView for Attendee Objects

Just like any other recycler view, we have to follow the following steps to build the Recycler View

  • Create the layout for single Attendee item.
  • Create a RecyclerAdapter recycler view
  • Create ViewHolder for recycler view

The layout for single attendee item contains editable fields such as First name, Last name, Email, Country and a view which can hold custom forms (if required for that Event Ticket). The Recycler Adapter for attendee has an ArrayList of Attendee which is what is rendered on the fragment, it also has a list of tickets such that attendee at a certain position in ArrayList is for the ticket at the corresponding position in ticket list. We also have some regular recycler adapter functions to insert values into these class variables. We pass a link of the recycler adapter object to the view holder so that these variables can be accessed from the view holder too.

override fun onBindViewHolder(holder: AttendeeViewHolder, position: Int) {
       holder.bind(this, position)
   }

We send the position to the view holder as to know which attendee and ticket from the list of attendees and tickets should the view be created for.

The view holder is responsible for creating views for a single recycler view item and how it should behave when any action is performed on that view.

Since we are dealing with editable text view for getting attendee details from the user, we have set up a Text watcher to listen for text change in any of these view objects. Whenever text is changed in any of these views the corresponding attendee in attendee recycler is automatically updated.  The following is the implementation of text watcher.

 textWatcher = object : TextWatcher {
           override fun afterTextChanged(p0: Editable?) {
               val id = attendeeRecyclerAdapter.attendeeList[position].id
               attendeeRecyclerAdapter.attendeeList.removeAt(position)
               val attendee = Attendee(id, firstname = itemView.attendeeItemFirstName.text.toString(),
                       lastname = itemView.attendeeItemLastName.text.toString(),
                       email = itemView.attendeeItemEmail.text.toString(),
                       city = getAttendeeField(“city”),
                       address = getAttendeeField(“address”),
                       state = getAttendeeField(“state”),
                       country = itemView.attendeeItemCountry.text.toString(),
                       ticket = TicketId(attendeeRecyclerAdapter.ticketList[position].id.toLong()),
                       event = attendeeRecyclerAdapter.eventId)
               attendeeRecyclerAdapter.attendeeList.add(position, attendee)
           }

             }

Now that our recycler adapter is ready we just have to wire the adapter with the recycler view in the Attendee Fragment.

<android.support.v7.widget.RecyclerView
               android:id=“@+id/attendeeRecycler”
               android:layout_width=“match_parent”
               android:layout_height=“wrap_content” />

Adding the above in attendee fragment will create a recycler view. Next step is too wire everything and insert attendee and ticket according to the user.

Inserting Attendees and Tickets in the Recycler

We have set up an observer on the tickets variable of Attendee View model which means that any update in the tickets will automatically call it with latest values. We are inserting all the tickets into the ticket list of the recycler adapter and also for every ticket we are generating a no of attendees according to the value of ticket’s quantity.

attendeeFragmentViewModel.tickets.observe(this, Observer {
               it?.let {
                   ticketsRecyclerAdapter.addAll(it)
                   ticketsRecyclerAdapter.notifyDataSetChanged()
                   it.forEach {
                    val pos = ticketIdAndQty?.map { it.first }?.indexOf(it.id)
                   val iterations = pos?.let { it1 ->       ticketIdAndQty?.get(it1)?.second } ?: 0
                  for (i in 0 until iterations)
                               attendeeRecyclerAdapter.add(Attendee(attendeeFragmentViewModel.getId()), it)
                  attendeeRecyclerAdapter.notifyDataSetChanged()
                     }
    }

In the code snippet shown above, pos = ticketIdAndQty?.map { it.first }?.indexOf(it.id) helps in getting the position of ticket id and its quantity pair from the list of pairs whenever we find a ticket. Using the position value we take can get the second variable in a pair which is the quantity of that ticket. Now that we know ticket details and the quantity of same to be inserted into the recycler we can create empty attendee objects and add them to the adapter with there ticket id as current ticket id and this goes till the quantity limit for the current ticket is reached. These attendees can now be edited by the user and when the user is done providing details the order will take the details of different attendees and create the order.

References

Continue ReadingCreating multiple Attendee Details sections in Open Event Android

Using DialogFragment to show sales data in Open Event Organizer App’s Events List

In the Open Event Organizer Android App The events list shows the list of events, but organizers often need to compare sales of different events fast. Until now they had to select each and every item and go to the dashboard to see the sales. This is about to change in the Pull Request #1063. It provides an intuitive way to show ticket sales by showing a dialog box upon long pressing event list items.

To implement it, we needed a BaseDialogFragment class which would implement Injectable and handle the presenter life cycle for us.

public class BaseDialogFragment<P extends BasePresenter> extends DialogFragment implements Injectable {

BaseDialogFragment class is very similar to the BaseFragment class, except that it extends DialogFragment instead of Fragment, And the new class SalesSummaryFragment is also similar to any other fragment class we are using.

When an item in the events list is clicked, the long click listener for the events’ list adapter opens the SalesSummaryFragment for the corresponding event, and when the data completes loading it calls ItemResult#showResult and binds the event with the data.

@Override
public void showResult(Event event) {
    binding.setEvent(event);
    binding.executePendingBindings();
}

The  SalesSummaryPresenter  is the almost the same as the TicketsPresenter, since it is loading ticket sales. In its onStart() method, it calls the method  loadDetails()  which on completion calls  analyseSoldTickets()  on  ticketAnalyser, which basically updates an event’s analytics after calculations.

public class SalesSummaryPresenter extends AbstractDetailPresenter<Long, SalesSummaryView> {
    …

    public void loadDetails(boolean forceReload) {
        …
        getEventSource(forceReload)
            …
            .subscribe(attendees -> {
                this.attendees = attendees;
                ticketAnalyser.analyseSoldTickets(event, attendees);
            }, Logger::logError);
    }

   …

    @Override
    public void showResult(Event event) {
        binding.setEvent(event);
        binding.executePendingBindings();
    }
}

For  SalesSummaryFragment , we are using the layout  fragment_sales_summary.xml. And this layout mainly makes use of the layout ticket_analytics_item.xml which is a layout designed to produce the three circular views to show the data.

<include
    layout="@layout/ticket_analytics_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    bind:color='@{"red"}'
    bind:completed="@{event.analytics.soldDonationTickets}"
    bind:ticketName="@{@string/ticket_donation}"
    bind:total="@{event.analytics.donationTickets}" />

This is how the result looks like:

References:
Open Event Orga Android App: Pull Request #1063:
https://github.com/fossasia/open-event-orga-app/pull/1063

Codepath guides: Using Dialog Fragment
https://github.com/codepath/android_guides/wiki/Using-DialogFragment

Continue ReadingUsing DialogFragment to show sales data in Open Event Organizer App’s Events List

Paypal Integration in Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. This blog post explains how Paypal has been integrated in the Open Event Server in order to accept payments for tickets.

The integration of Paypal in the server involved the following steps:

  1. An endpoint to accept the Paypal token from the client applications.
  2. Using the token to get the approved payment details.
  3. Capturing the payment using the fetched payment details.

Endpoint for Paypal token

The server exposes an endpoint to get the Paypal token in order to accept payments.

api.route(ChargeList, ‘charge_list’, ‘/orders/<identifier>/charge’, ‘/orders/<order_identifier>/charge’)

The above endpoint accepts the Paypal token and uses that to get the payment details from Paypal and then capture the payments.

Getting Approved Payment Details

We use the Paypal Name-Value pair API in the project. First we get the credentials of the event organizer who will be accepting the payments using a call to the get_credentials helper method. It returns the data as the following dictionary:

credentials = {
  'USER': settings['paypal_live_username'],
  'PWD': settings['paypal_live_password'],
  'SIGNATURE': settings['paypal_live_signature'],
  'SERVER': 'https://api-3t.paypal.com/nvp',
  'CHECKOUT_URL': 'https://www.paypal.com/cgi-bin/webscr',
  'EMAIL': '' if not event or not event.paypal_email or event.paypal_email == "" else event.paypal_email
}

Next, we use the credentials to get the approved payment details from paypal using the following code snippet.

@staticmethod
def get_approved_payment_details(order, credentials=None):
   if not credentials:
     credentials = PayPalPaymentsManager.get_credentials(order.event)

   if not credentials:
     raise Exception('PayPal credentials have not been set correctly')

   data = {
            'USER': credentials['USER'],
            'PWD': credentials['PWD'],
            'SIGNATURE': credentials['SIGNATURE'],
            'SUBJECT': credentials['EMAIL'],
            'METHOD': 'GetExpressCheckoutDetails',
            'VERSION': PayPalPaymentsManager.api_version,
            'TOKEN': order.paypal_token
   }

   if current_app.config['TESTING']:
      return data

   response = requests.post(credentials['SERVER'], data=data)
   return json.loads(response.text)

Capturing the payments

After successfully fetching the payment details, the final step is to capture the payment. We set the amount to be charged to the amount of the order and the payer_id to be the payer id received from step 2. Then we simply make a POST request to the Paypal nvp server and capture the payments. The below method is responsible for executing this task:

@staticmethod
def capture_payment(order, payer_id, currency=None, credentials=None):
  if not credentials:
    credentials = PayPalPaymentsManager.get_credentials(order.event)

  if not credentials:
    raise Exception('PayPal credentials have not be set correctly')

  if not currency:
    currency = order.event.payment_currency

  if not currency or currency == "":
    currency = "USD"

   data = {
            'USER': credentials['USER'],
            'PWD': credentials['PWD'],
            'SIGNATURE': credentials['SIGNATURE'],
            'SUBJECT': credentials['EMAIL'],
            'METHOD': 'DoExpressCheckoutPayment',
            'VERSION': PayPalPaymentsManager.api_version,
            'TOKEN': order.paypal_token,
            'PAYERID': payer_id,
            'PAYMENTREQUEST_0_PAYMENTACTION': 'SALE',
            'PAYMENTREQUEST_0_AMT': order.amount,
            'PAYMENTREQUEST_0_CURRENCYCODE': currency,
   }

   response = requests.post(credentials['SERVER'], data=data)
   return json.loads(response.text)

References

Continue ReadingPaypal Integration in Open Event Server

Charges Layer in Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. This blog post explains how the charge layer has been implemented in the Open Event Server in order to charge the user for tickets of an event.

Schema

We currently support payments via Stripe and Paypal. As a result the schema for Charges layer consists of fields for providing the token for stripe or paypal. It also contains a read only id field.

class ChargeSchema(Schema):
    """
    ChargeSchema
    """

    class Meta:
        """
        Meta class for ChargeSchema
        """
        type_ = 'charge'
        inflect = dasherize
        self_view = 'v1.charge_list'
        self_view_kwargs = {'id': '<id>'}

    id = fields.Str(dump_only=True)
    stripe = fields.Str(allow_none=True)
    paypal = fields.Str(allow_none=True)

Resource

The ChargeList resource only supports POST requests since there is no need for other type of requests. We simply declare the schema, supported methods and the data layer. We also check for required permissions by declaring the decorators.

class ChargeList(ResourceList):
    """
    ChargeList ResourceList for ChargesLayer class
    """
    methods = ['POST', ]
    schema = ChargeSchema

    data_layer = {
        'class': ChargesLayer,
        'session': db.session
    }

    decorators = (jwt_required,)

Layer

The data layer contains a single method create_object which does all the heavy lifting of charging the user according to the payment medium and the related order. It first loads the related order from the database using the identifier.

We first check if the order contains one or more paid tickets or not. If not, then ConflictException is raised since it doesn’t make sense to charge a user without any paid ticket in the order. Next, it checks the payment mode of the order. If the payment mode is Stripe then it checks if the stripe_token is provided with the request or not. If not, an UnprocessableEntity exception is raised otherwise relevant methods are called in order to charge the user accordingly. A similar procedure is followed for payments via Paypal. Below is the full code for reference.

class ChargesLayer(BaseDataLayer):

    def create_object(self, data, view_kwargs):
        """
        create_object method for the Charges layer
        charge the user using paypal or stripe
        :param data:
        :param view_kwargs:
        :return:
        """
        order = Order.query.filter_by(id=view_kwargs['id']).first()
        if not order:
            raise ObjectNotFound({'parameter': 'id'},
                                 "Order with id: {} not found".format(view_kwargs['id']))
        elif order.status == 'cancelled' or order.status == 'expired':
            raise ConflictException({'parameter': 'id'},
                                    "You cannot charge payments on a cancelled or expired order")
        elif (not order.amount) or order.amount == 0:
            raise ConflictException({'parameter': 'id'},
                                    "You cannot charge payments on a free order")

        # charge through stripe
        if order.payment_mode == 'stripe':
            if not data.get('stripe'):
                raise UnprocessableEntity({'source': ''}, "stripe token is missing")
            success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe'])
            if not success:
                raise UnprocessableEntity({'source': 'stripe_token_id'}, response)

        # charge through paypal
        elif order.payment_mode == 'paypal':
            if not data.get('paypal'):
                raise UnprocessableEntity({'source': ''}, "paypal token is missing")
            success, response = TicketingManager.charge_paypal_order_payment(order, data['paypal'])
            if not success:
                raise UnprocessableEntity({'source': 'paypal'}, response)
        return order

The charge_stripe_order_payment and charge_paypal_order_payment are helper methods defined to abstract away the complications of the procedure from the layer.

References

Continue ReadingCharges Layer in Open Event Server

Retrofit2 Rxjava2 Error Response Handling in Open Event Organizer App

In the Open Event Organizer Android app the challenge is to provide user, a readable error description for the input requests. The Organizer App was showing a coded message which was understandable only to a programmer making it unfriendly to the common user. The blog describes how we tackled this problem and implemented a mechanism to provide user friendly error messages for user requests (if any).

Let’s consider the scenario when an organizer want to create an Event or maybe a Ticket so what he has to do is fill out a form containing some user input fields and click on submit button to send the data to the server which in turn sends a response for the request. If the information is valid the server sends a HTTP 201 Response and user gets feedback that the Event/Ticket is created. But if the information is not valid the user gets feedback that there is HTTP 422 error in your request. There is no readable description of error provided to the user.

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Further, we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVP architecture. Let’s move on to the code.

Retrofit to the rescue

Now we will see how we can extract the details about the error response and provide user a better feedback rather than just throwing the error code.

Firstly let’s make the Request to our REST API Server using Retrofit2.

@POST(“faqs”)
Observable<Faq> postFaq(@Body Faq faq);

Here we are making a POST request and expecting a Observable<Faq> Response from the server.The  @Body annotation indicates the Request Body is an Faq object.

Please note that Observable used here is ReactiveX Observable so don’t confuse it with java.util Observable.

public class Faq {
@Id(LongIdHandler.class)
public Long id;  @Relationship(“event”)
@ForeignKey(stubbedRelationship = true,

onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String question;
public String answer;
}

Let’s say the API declares both question and answer as mandatory fields  for creation of an Faq. We supply the following input to the app.

question = “Do I need to buy a Ticket to get In ?”;
answer = null

We used RxJava to make an asynchronous request to server. In case of error we will get a Retrofit throwable and we will pass that on to ErrorUtils.java which will do all the processing and return a readable error message.

faqRepository
.createFaq(faq)
.compose(dispose(getDisposable()))
.compose(progressive(getView()))  .doOnError(throwable ->

getView().showError(ErrorUtils.getMessage(throwable)))
.subscribe(createdFaq -> {
getView().onSuccess(“Faq Created”);
getView().dismiss();
}, Logger::logError);

Now we will extract the ResponseBody from the Retrofit throwable.

ResponseBody responseBody = ((HttpException) throwable)

.response().errorBody();
return getErrorDetails(responseBody);

The ResponseBody is a JSON containing all the information about the error.

{
“errors”: [
{
“status”: “422”,
“source”: {
“pointer”: “/data/attributes/answer”
},
“detail”: “Missing data for required field.”,
“title”: “Validation error”
}
],
“jsonapi”: {
“version”: “1.0”
}
}

In order to provide better feedback to user regarding the error we have to parse the response JSON and extract the pointed field (which in this case is – “answer”) and then combine it with the value corresponding to the “detail” key. Following is the relevant section from ErrorUtils.java (for viewing the complete ErrorUtils.java please refer here).

public static String getErrorDetails(ResponseBody responseBody) {
try {
JSONObject jsonObject = new JSONObject(responseBody.string());
JSONObject jsonArray = new    JSONObject(jsonObject.getJSONArray(“errors”).get(0).toString());
JSONObject errorSource =

new JSONObject(jsonArray.get(“source”).toString());

try {
String pointedField =

getPointedField(errorSource.getString(“pointer”));
if (pointedField == null)
return jsonArray.get(“detail”).toString();
else
return jsonArray.get(“detail”).toString()

.replace(“.”, “”) + “: ” + pointedField;
} catch (Exception e) {
return jsonArray.get(“detail”).toString();
}

} catch (Exception e) {
return null;
}
}

public static String getPointedField(String pointerString) {
if (pointerString == null || Utils.isEmpty(pointerString))
return null;
else {
String[] path = pointerString.split(“/”);
if (path.length > 3)
return path[path.length – 1];
else
return null;
}
}

The final error message returned by ErrorUtils in case of HTTP 422 –

Missing data for required field: answer

Similar approach can be followed for extracting the error “title” or “status” .

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Codebase for Open Event Organizer App on Github https://github.com/fossasia/open-event-orga-app
Continue ReadingRetrofit2 Rxjava2 Error Response Handling in Open Event Organizer App

Implementing Endpoint to Resend Email Verification

Earlier, when a user registered via Open Event Frontend, s/he received a verification link via email to confirm their account. However, this was not enough in the long-term. If the confirmation link expired, or for some reasons the verification mail got deleted on the user side, there was no functionality to resend the verification email, which prevented the user from getting fully registered. Although the front-end already showed the option to resend the verification link, there was no support from the server to do that, yet.

So it was decided that a separate endpoint should be implemented to allow re-sending the verification link to a user. /resend-verification-email was an endpoint that would fit this action. So we decided to go with it and create a route in `auth.py` file, which was the appropriate place for this feature to reside. First step was to do the necessary imports and then definition:

from app.api.helpers.mail import send_email_confirmation
from app.models.mail import USER_REGISTER_WITH_PASSWORD
...
...
@auth_routes.route('/resend-verification-email', methods=['POST'])
def resend_verification_email():
...

Now we safely fetch the email mentioned in the request and then search the database for the user corresponding to that email:

def resend_verification_email():
    try:
        email = request.json['data']['email']
    except TypeError:
        return BadRequestError({'source': ''}, 'Bad Request Error').respond()

    try:
        user = User.query.filter_by(email=email).one()
    except NoResultFound:
        return UnprocessableEntityError(
{'source': ''}, 'User with email: ' + email + ' not found.').respond()
    else:

    ...

Once a user has been identified in the database, we proceed further and create an essentially unique hash for the user verification. This hash is in turn used to generate a verification link that is then ready to be sent via email to the user:

else:
    serializer = get_serializer()
    hash_ = str(base64.b64encode(str(serializer.dumps(
[user.email, str_generator()])).encode()), 'utf-8')
    link = make_frontend_url(
'/email/verify'.format(id=user.id), {'token': hash_})

Finally, the email is sent:

send_email_with_action(
user, USER_REGISTER_WITH_PASSWORD,
app_name=get_settings()['app_name'], email=user.email)
    if not send_email_confirmation(user.email, link):
        return make_response(jsonify(message="Some error occured"), 500)
    return make_response(jsonify(message="Verification email resent"), 200)

But this was not enough. When the endpoint was tested, it was found that actual emails were not being delivered, even after correctly configuring the email settings locally. So, after a bit of debugging, it was found that the settings, which were using Sendgrid to send emails, were using a deprecated Sendgrid API endpoint. A separate email function is used to send emails via Sendgrid and it contained an old endpoint that was no longer recommended by Sendgrid:

@celery.task(name='send.email.post')
def send_email_task(payload, headers):
   requests.post(
       "https://api.sendgrid.com/api/mail.send.json",
       data=payload,
       headers=headers
   )

The new endpoint, as per Sendgrid’s documentation, is:

https://api.sendgrid.com/v3/mail/send

But this was not the only change required. Sendgrid had also modified the structure of requests they accepted, and the new structure was different from the existing one that was used in the server. Following is the new structure:

'{"personalizations": [{"to": [{"email": "example@example.com"}]}],"from": {"email": "example@example.com"},"subject": "Hello, World!","content": [{"type": "text/plain", "value": "Heya!"}]}'

The header structure was also changed, so the structure in the server was also updated to

headers = {
"Authorization": ("Bearer " + key),
"Content-Type": "application/json"
}

The Sendgrid function (which is executed as a Celery task) was modified as follows, to incorporate the changes in the API endpoint and structure:

import json
...
@celery.task(name='send.email.post')
def send_email_task(payload, headers):
    data = {"personalizations": [{"to": []}]}
    data["personalizations"][0]["to"].append({"email": payload["to"]})
    data["from"] = {"email": payload["from"]}
    data["subject"] = payload["subject"]
    data["content"] = [{"type": "text/html", "value": payload["html"]}]
    requests.post(
        "https://api.sendgrid.com/v3/mail/send",
        data=json.dumps(data),
        headers=headers,
        verify=False  # doesn't work with verification in celery context
    )

 

As can be seen, there is a bug that doesn’t allow SSL verification within the celery context. However, the verification is successful when the functionality is executed independent of the celery context. But now email sending via Sendgrid actually works, which makes our verification resend endpoint functional:Screen Shot 2018-08-10 at 10.04.12 PM.pngEmail is received successfully by the recipient:

Screen Shot 2018-08-10 at 10.04.30 PM.png

Thus, a working email verification endpoint is implemented, which can be easily integrated in the frontend.


Resources:

Continue ReadingImplementing Endpoint to Resend Email Verification

Creating an apk in the apk branch using Travis CI

All Android apps in FOSSASIA have an apk branch where after a pull request is merged a new apk gets created so that even people who do not know how to setup the app locally can access it. Let’s see how it is done.

Lets get started

Create a bash file in the scripts folder and name it upload-apk.sh

Here is all the code that you require to create an apk in the apk branch.

First thing that we need to do is setup git. So we’ll set the user name and email just like we do when we setup git for the first time in our own systems.

git config --global user.name "Travis CI"
git config --global user.email "noreply+travis@fossasia.org"

 

Next we will clone the apk branch of the repository and copy all the files that we need ie the apk and the JSON files.

git clone --quiet --branch=apk https://fossasia:$GITHUB_API_KEY@github.com/fossasia/open-event-android apk > /dev/null
cd apk
\cp -r ../app/build/outputs/apk/*/**.apk .
\cp -r ../app/build/outputs/apk/debug/output.json debug-output.json
\cp -r ../app/build/outputs/apk/release/output.json release-output.json

 

Next we will create a new branch that contains only the latest apk. After that we will add the APK and then commit all those changes. You can see that the current date and time are printed out in the commit message.

git checkout --orphan temporary

git add --all .
git commit -am "[Auto] Update Test Apk ($(date +%Y-%m-%d.%H:%M:%S))"

 

We will delete the current apk branch and then rename the current branch to apk. In the end we will force push to origin since histories are unrelated.

git branch -D apk
git branch -m apk

git push origin apk --force --quiet > /dev/null

 

If you have already integrated Travis CI in your repository then you just need to add this line your travis.yml file.

after_success:
- bash scripts/update-apk.sh

 

Now every time a PR gets merged in the repository a new apk file is created in the apk branch of your repository.

Resources

  1. Travis CLI – https://github.com/travis-ci/travis.rb#readme
  2. Travis official documentation – https://travis-ci.org/
Continue ReadingCreating an apk in the apk branch using Travis CI

Using Two-Way Data Binding in Open Event Organizer Android App:

Data Binding is the simple approach which relieves developers of repeated findViewById() calls. It is something that every developer must use if not using ButterKnife.

The Open Event Organizer Android App provides options to fill in an extensive set of details while creating an event, or any other entities. The problem at hand is that many of these options are common to many of these entities. For instance, currently the element date-time-picker and text fields are common to elements of different forms, as each one of them requires date-time checkboxes.

We need to be able to <include> a separate smaller and reusable layout file and bind event data to make the code shorter. This would help decreasing unnecessary code base and improving code readability.

We will see how using 2 way data binding and <include> tags in the small PR #929 reduced the code of 112 lines to just 9 lines:

Step 1: Configuration:

The very first step is to configure your project to enable data bindings in your
build.gradle (Module:app) file.

dataBinding should be included as follows:

android {
   // Rest of gradle file…
   dataBinding {
   enabled true
   }    // Rest of gradle file…
}

Step 2: Import and variable tags:

Data Binding uses the tag <data> to signify the data which will be referred to in lambda expressions inside the XML.

We also need to import any class, whose methods we need to use. This can be done using the <import> tag.

Finally, the <variable> tag is used to define any variables that will be referenced in the XML.

 

<data>
  <import type=”android.view.View” />
  <variable
      name=“date”
      type=”String” />
  <variable
      name=“label”
      type=”String“/>
</data>

Step 3: Binding the declared variables:

Data binding recognizes methods of the type set<variable>, where <variable> is event in our case.

We need to use  executePendingBindings();  so that any pending bindings are done and the UI of our app responds correctly as soon as the view data is updated.

@Override
public void showResult(Event event) {
  binding.setEvent(event);
  binding.executePendingBindings();
}

Step 4: Using the declared variables:

Making use of the declared variables is a very simple task and is as simple as a java statement. You can do almost everything that’s possible in the java file, the only constraint being that the used variables are declared in the xml and binded appropriately.

Most of the data binding expressions use data binding to condense the expression to its smallest possible form.

<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:padding=“@dimen/spacing_extra_small”
  android:orientation=“horizontal”
  android:visibility=“@{ picker.checked ? View.VISIBLE : View.GONE }”>

2 Way Data Binding

In case of the Organizer App, we are using 2 way data binding.

Data Binding allows us to do much more than just set text in TextView or create listener in Button. If we want to use EditText and automatically update text variable in java code, we need to use observable fields and two way binding.

Thus, most variables like date, event that we are binding, are Observable fields.

* Sometimes there’s a use case of using a variable declared in another layout file.

For example, in:

<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 variable date isn’t binded in the java file but the xml files which include the layout time_picker.xml

Using the <include> tag:

The include tag is very simple to use, and we can simply bind the date and label element. The event_create_form.xml binds the variable using the bind attribute like this:

<include
  layout=“@layout/time_picker”
  bind:date=“@={ event.startsAt }”
  bind:label=“@{ @string/starts_at }”/>

The most common error you will face:

Often, when there’s something wrong with the XML, the most common error you will face is:

“Cannot resolve Data Binding class…”

This error is because Android Studio couldn’t generate the Data Binding class for your XML file because of some error. Presently, it doesn’t give much details about what’s wrong, so you’ll have to look for the errors yourselves.

The most common mistake newbie developers make is forgetting to bind the variables appropriately.

References:

Android Developer Guide:

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

Continue ReadingUsing Two-Way Data Binding in Open Event Organizer Android App:

Adding Check-in Attributes to Tickets

Recently, it was decided by the Open Event Orga App team that the event ticket API response from Open Event Server should have two additional attributes for specifying event check-in access. At first sight, it seemed that adding these options will only require changes in the orga app, but it turned out that the entire Ticket API from the server will need this addition.

Implementing these attributes turned out to be quite straightforward. Specifically, the fields to be added were boolean is_checkin_restricted and auto_checkin_enabled. By default, checkin is not automatic and is restricted. Therefore, the default values for these fields were chosen to be True and False respectively. To add them, the ticket model file was changed first – due to the addition of these two columns:

class Ticket(SoftDeletionModel):
    ...
    is_checkin_restricted = db.Column(db.Boolean)  # <--
    auto_checkin_enabled = db.Column(db.Boolean)  # <--
    ...
    def __init__(self,
        name=None,
        event_id=None,
        ...
        is_checkin_restricted=True,
        auto_checkin_enabled=False):

        self.name = name
        ...
        self.is_checkin_restricted = is_checkin_restricted
        self.auto_checkin_enabled = auto_checkin_enabled
        ...

Since the ticket database model was updated, a migration had to be performed. Following shell commands (at the open event server project root) did the migration and database update and a migration file was then generated:

$ python manage.py db migrate
$ python manage.py db upgrade

Here’s the generated migration file:

from alembic import op
import sqlalchemy as sa

revision = '6440077182f0'
down_revision = 'eaa029ebb260'

def upgrade():
    op.add_column('tickets', sa.Column('auto_checkin_enabled', sa.Boolean(), nullable=True))
    op.add_column('tickets', sa.Column('is_checkin_restricted', sa.Boolean(), nullable=True))

def downgrade():
    op.drop_column('tickets', 'is_checkin_restricted')
    op.drop_column('tickets', 'auto_checkin_enabled')

The next code change was required in the ticket API schema. The change was essentially the same as the one added in the model file – just these 2 new fields were added:

class TicketSchemaPublic(SoftDeletionSchema):
    ...
    id = fields.Str(dump_only=True)
    name = fields.Str(required=True)
    ...
    is_checkin_restricted = fields.Boolean(default=True)  # <--
    auto_checkin_enabled = fields.Boolean(default=False)  # <--
    event = Relationship(attribute='event',
    self_view='v1.ticket_event',
    self_view_kwargs={'id': '<id>'},
    related_view='v1.event_detail',
    related_view_kwargs={'ticket_id': '<id>'},
    schema='EventSchemaPublic',
    type_='event')
    ...

Now all that remained were changes in the API documentation, which were made accordingly. This completed the addition of these two checkin attributes in the ticket API, and eventually made way to the orga app. And, these can be requested as usual by the front-end and user app as well.


Resources and Links:

Continue ReadingAdding Check-in Attributes to Tickets