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

Using SharedViewModel for data persistence in multiple Fragments in Open Event Organizer Android App

Google announced the new architectural pattern Model-View-View-Model (or MVVM), which has gained a lot of popularity for increased simplicity. The Open Event Organizer Android App is currently (at the time of writing this blog) using Model-View-Presenter (MVP) architecture for the most part. Only the Login module had been converted to MVVM, but it wasn’t using any Shared View Model.

Let’s look at the problem at hand:

For the Login fragment, there’s an email field for user’s email in every fragment of the auth module. But there’s the use case where users can bounce around different fragment screens in order to perform different operations like login, signup, forgot password and enter new password.

A good User Experience is at the core of every successful product. Users don’t like to spend time doing trivial things like registration and they hate to retype emails again and again.

To solve the problem, the approach that I first used was, using interfaces to tell each and every component when the fragment opens and send the email text through the interface. I spent quite some time implementing the interfaces right and then opened a PR. It was around 200 lines addition at first, and I also received 2-3 requested changes from my project mentor.

Defining the interface:

The interface  EmailIdTransfer  was defined in the  LoginFragment  as follows:

public class LoginFragment extends BaseFragment implements LoginView, EmailIdTransfer {

Using the interface:

getPresenter().getEmailId().setEmail(
((LoginFragment.EmailIdTransfer) getContext()).getEmail()

Imagine setting up the   EmailIdTransfer  interface for each Fragment, and handling their updation and reception.

As you can see, this is a bad solution for something as trivial as the problem at hand! The better solution which stuck me (from Google I/O) afterwards was to use ViewModels. This obviously wasn’t MVP, but since we had to eventually move to MVVM, I dug a little more about it and found out that there’s also a SharedViewModel.

SharedViewModel is basically a View Model that every view of a module shares. It is not very different from ViewModel.

Here’s how to set it up:

public class SharedViewModel extends ViewModel {

   private final MutableLiveData<String> email = new MutableLiveData<>();

   public void setEmail(String email) {
       this.email.setValue(email);
   }

   public LiveData<String> getEmail() {
       return email;
   }
}

The MVVM architecture uses LiveData instead of Observables.

Android LiveData is a variant of the original observer pattern, with the addition of active/inactive transitions. As such, it is very restrictive in its scope. RxJava provides operators that are much more generalized.

Now to use this SharedViewModel in each fragment, we need:

sharedViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

sharedViewModel.getEmail().observe(this, email -> binding.getForgotEmail().setEmail(email));

This is the initial setup for the the fragment.

private void openLoginPage() {

   …
   sharedViewModel.setEmail(binding.getUser().getEmail());
   …
}

This is needed to be done for every fragment which needs to set and use the common email.

This was a dive into using LiveData<>, ViewModel and how to use a SharedViewModel.

This is how the result looks like:

References:

Continue ReadingUsing SharedViewModel for data persistence in multiple Fragments in Open Event Organizer Android App

Generating a Stripe token in the Open Event Android App

To implement the payment functionality in the Open Event Android App using credit cards we are using Stripe. Let’s see how this is being done.

We are taking the sensitive information about the user like the card details and sending it to Stripe’s servers which will return a token encrypting users information which we will use to send it to the Open Event Server to complete the payment.

We need to add the library in the build.gradle file in the dependency block.

//Stripe
implementation 'com.stripe:stripe-android:6.1.2'

 

Next we add Stripe’s Card Input Widget in our layout file. This widget is used to input card details like the card number, expiry date and CVC. It automatically validates the card details. It has a minimum width of 320 px. By default we are setting it’s visibility to gone that is it won’t be visible unless the user selects the payment option.

<com.stripe.android.view.CardInputWidget
android:id="@+id/cardInputWidget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />

 

The visibility of the input field for card details is determined here. We want to show the Input widget only when the user selects Stripe as the mode of payment.

override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
selectedPaymentOption = paymentOptions[p2]
if (selectedPaymentOption == "Stripe")
rootView.cardInputWidget.visibility = View.VISIBLE
else
rootView.cardInputWidget.visibility = View.GONE
}

 

Next we store the user’s card details in a variable. If any of the details be it card number, expiry date or CVC isn’t correct, the value of the variable becomes null and then we show a message to the user.

val cardDetails: Card? = cardInputWidget.card

if (cardDetails == null)
Toast.makeText(context, "Invalid card data", Toast.LENGTH_LONG).show()

 

This is the most important part where we receive the Stripe token. We are sending a request in a background thread to the Stripe servers using the Stripe API_KEY. In the onSuccess method we receive the token if everything went successfully while in the onError method we display the errors to the user.

cardDetails?.let {
context?.let { contextIt ->
Stripe(contextIt).createToken(
it,
API_KEY,
object : TokenCallback {
override fun onSuccess(token: Token) {
//Send this token to server
Toast.makeText(context, "Token received from Stripe", Toast.LENGTH_LONG).show()
}

override fun onError(error: Exception) {
Toast.makeText(context, error.localizedMessage.toString(), Toast.LENGTH_LONG).show()
}
})
}
}

 

Resources

  1. Stripe Android documentation: https://stripe.com/docs/mobile/android  
  2. Stripe Documentation: https://stripe.com/docs/quickstart
  3. Stripe Android Github: https://github.com/stripe/stripe-android
Continue ReadingGenerating a Stripe token in the Open Event Android App

Supporting Toolbar Options – Edit and multiple deletions – in Open Event Organizer App

The Open Event Organizer Android App was supporting deletion for only one item, and didn’t support the edit option. It needed to support multiple deletions for items and the edit operation. This PR and hence the blog covers details on how to handle multiple deletions and implementing the edit option.

Here are the steps I followed to implement these options from scratch:

Step 1: Create toolbar menu

First create a menu for toolbar with the desired toolbar actions (delete and edit in our case)

<?xml version=“1.0” encoding=“utf-8”?>
<menu xmlns:android=“http://schemas.android.com/apk/res/android”
   xmlns:app=“http://schemas.android.com/apk/res-auto”>

   <item android:id=“@+id/del”
       android:title=“Delete”
       android:icon=“@drawable/ic_delete”
       android:visible=“false”
       app:showAsAction=“ifRoom”>
   </item>

   <item android:id=“@+id/edit”
       android:title=“Update”
       android:icon=“@drawable/ic_edit”
       android:visible=“false”
       app:showAsAction=“ifRoom”>
   </item>

</menu>

Step 2: Setup helper options:

We need to keep track of which items have been selected and also be able to get the count of these selected items many times. Therefore, we need to setup the following helper methods  isTrackSelected()  and countSelected()

Both these methods make use of the HashMap:

private final Map<Long, ObservableBoolean> selectedTracks = new HashMap<>();

Every time a method uses  isTrackSelected() , it checks whether or not there is an entry of that track in the HashMap selectedTracks. If not, it adds the entry and returns the corresponding default set ObservableBoolean.

public ObservableBoolean isTrackSelected(Long trackId) {
       if (!selectedTracks.containsKey(trackId))
           selectedTracks.put(trackId, new ObservableBoolean(false));

       return selectedTracks.get(trackId);
   }

   private int countSelected() {
       int count = 0;
       for (Long id : selectedTracks.keySet()) {
           if (selectedTracks.get(id).get())
               Count++;
       }
       return count;
   }

Step 3: Handling clicks

The logic to be followed had to be consistent with existing popular apps, which users are habitual of using. It thus had to be intuitive. And so here’s the logic we follow:

Click -> Opens intent (if toolbar not already active)

Click -> Selects item (if toolbar is active and item is not already selected)

Click -> Deselects item (if item already selected)

Click -> Deselects item and de-activates toolbar (if the already selected item was the only one)

Long Click -> Activates toolbar and selects item (if toolbar not already active)

Long Click -> Selects item (if toolbar already active)

> Handling single clicks:

Following the described logic, we see if a toolbar is not active, we simply open the detailed view of the already selected item.

public void click(Long clickedTrackId) {
       if (isToolbarActive) {

           if (countSelected() == 1 && isTrackSelected(clickedTrackId).get()) {
               selectedTracks.get(clickedTrackId).set(false);
               resetToolbarDefaultState();
           } else if (countSelected() == 2 && isTrackSelected(clickedTrackId).get()) {
               selectedTracks.get(clickedTrackId).set(false);
               getView().changeToolbarMode(true, true);
           } else if (isTrackSelected(clickedTrackId).get())
               selectedTracks.get(clickedTrackId).set(false);
           else
               selectedTracks.get(clickedTrackId).set(true);

           if (countSelected() > EDITABLE_AT_ONCE)
               getView().changeToolbarMode(false, true);

       } else
           getView().openSessionsFragment(clickedTrackId);
   }

> Handling long clicks:

If the toolbar is active, long clicks behave same as simple clicks. Otherwise, we first add them to the selectedTracks, set isToolbarActive to true and change toolbar mode to activate edit and delete options.

public void longClick(Track clickedTrack) {
       if (isToolbarActive)
           click(clickedTrack.getId());
       else {
           selectedTracks.get(clickedTrack.getId()).set(true);
           isToolbarActive = true;
           getView().changeToolbarMode(true, true);
       }
   }

   public void resetToolbarDefaultState() {
       isToolbarActive = false;
       getView().changeToolbarMode(false, false);
  }

Step 4: Add relevant methods in presenter

We need to add the deleteSelectedTracks() method to the presenter so that we can delete all the tracks selected for deletion.

This methods only iterates from iterable selectedTracks.entrySet() and then executes the already existing deleteTrack() method from the presenter

private void deleteTrack(Long trackId) {
        trackRepository
            .deleteTrack(trackId)
            .compose(disposeCompletable(getDisposable()))
           .compose(progressiveErroneousCompletable(getView()))
            .subscribe(() -> {
               getView().showTrackDeleted(“Track Deleted”);
               loadTracks(true);
               selectedTracks.remove(trackId);
               Logger.logSuccess(trackId);
           }, Logger::logError);
   }

   public void deleteSelectedTracks() {
       Observable.fromIterable(selectedTracks.entrySet())
           .compose(dispose(getDisposable()))
           .compose(progressiveErroneous(getView()))
           .doFinally(() -> {
               getView().showMessage(“Tracks Deleted”);
               resetToolbarDefaultState();
           })
           .subscribe(entry -> {
               if (entry.getValue().get()) {
                   deleteTrack(entry.getKey());
               }
            }, Logger::logError);
    }

This is how the result looks like:

Implementation in Open Event Organizer Android App

Resources

Continue ReadingSupporting Toolbar Options – Edit and multiple deletions – 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: