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

Implementing Tax Endpoint 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. The Event organizers may want to charge taxes on the event tickets. The Open Event Server has a Tax endpoint in order to support it. This blog goes over it’s implementation details in the project.

Model

First up, we will discuss what fields have been stored in the database for Tax endpoint. The most important fields are as follows:

  • The tax rate charged in percentage
  • The id for the Tax
  • The registered company
  • The country
  • The address of the event organiser
  • The additional message to be included as the invoice footer

We also store a field to specify whether the tax should be included in the ticket price or not. Each Event can have only one associated Tax information. You can checkout the full model for reference here.

Schema

We have defined two schemas for the Tax endpoint. This is because there are a few fields which contain sensitive information and should only be shown to the event organizer or the admin itself while the others can be shown to the public. Fields like name and rate aren’t sensitive and can be disclosed to the public. They have been defined in the TaxSchemaPublic class. Sensitive information like the tax id, address, registered company have been included in the TaxSchema class which inherits from the TaxSchemaPublic class. You can checkout the full schema for reference here.

Resources

The endpoint supports all the CRUD operations i.e. Create, Read, Update and Delete.

Create and Update

The Tax entry for an Event can be created using a POST request to the /taxes endpoint. We analyze if the posted data contains a related event id or identifier which is necessary as every tax entry is supposed to be related with an event. Moreover we also check whether a tax entry already exists for the event or not since an event should have only one tax entry. An error is raised if that is not the case otherwise the tax entry is created and saved in the database. An existing entry can be updated using the same endpoint by making a PATCH request.  

Read

A Tax entry can be fetched using a GET request to the  /taxes/{tax_id}  endpoint with the id for the tax entry. The entry for an Event can also be fetched from /events/{event_id}/tax  endpoint.

Delete

An existing Tax entry can be deleted by making a DELETE request to the /taxes/{tax_id} endpoint with the id of the entry. We make sure the tax entry exists. An error is raised if that is not the case else we delete it from the database.

References

Continue ReadingImplementing Tax Endpoint in Open Event Server

Stripe Authorization in Open Event Server

Stripe is a popular software platform for online payments. Since Open Event  allows the event organizers to sell tickets, an option to accept payments through Stripe is extremely beneficial to the organizer. Stripe allows accepting payments on other’s behalf using Connect. Connect is the Stripe’s full stack solution for platforms that need to process payments and process to multiple parties. This blog post goes over how Event organizers are able to link their Stripe accounts in order to accept payments later.

Registering the platform

The Admin of the Open Event Server will create an account on Stripe and register the platform. Upon creating the  account he/she will get a secret_key and publishable_key.  Moreover on registering the platform a client_id will be provided. These keys are saved in the application settings and only the Admin is authorized to view or change them.

Connecting the Organiser’s account

The Open Event Frontend has a wizard for creating an Event. It provides the organiser an option to connect his/her Stripe account in order to accept payments.

Upon clicking the following button, the organiser is directed to Stripe where he/she can fill the required details.  

The button directs the organizer to the following URL:

https://connect.stripe.com/oauth/authorize?response_type=code&client_id=client_id&scope=read_write&redirect_uri=redirect_uri 

The above URL has the following parameters:

  • client_id – The client ID acquired when registering your platform.required.
  • response_type – Response type. The value is always code. required.
  • redirect_uri – The URL to redirect the customer to after authorization.
  • scope – We need it to be read_write in order to be able to charge on behalf of the customer later.

After successfully entering the required details, the organizer is redirected to the redirect_url as specified in the above URL with a query parameter named as authorization_code. The Frontend sends this code to the Server using the Stripe Authorization endpoint which will be discussed in detail below.

Fetching Tokens from Stripe

The Server accepts the authorization_code by exposing the Stripe Authorization endpoint. It then uses it to fetch organizer’s details and token from Stripe and stores it for future use.

The schema for Stripe Authorization is extremely simple. We require the client to send an authorization_code which will be used to fetch the details. Stripe_publishable_key of the event organizer is exposed via the endpoint and will be used by the Frontend later.

class StripeAuthorizationSchema(Schema):
    """
        Stripe Authorization Schema
    """

    class Meta:
        """
        Meta class for StripeAuthorization Api Schema
        """
        type_ = 'stripe-authorization'
        self_view = 'v1.stripe_authorization_detail'
        self_view_kwargs = {'id': '<id>'}
        inflect = dasherize

    id = fields.Str(dump_only=True)
    stripe_publishable_key = fields.Str(dump_only=True)
    stripe_auth_code = fields.Str(load_only=True, required=True)

    event = Relationship(attribute='event',
                self_view='v1.stripe_authorization_event',
                self_view_kwargs={'id': '<id>'},
                related_view='v1.event_detail',
                related_view_kwargs={'stripe_authorization_id':                      '<id>'},
                schema="EventSchema",
                type_='event')

We use the Requests library in order to fetch the results. First we fetch the client_id that we had stored in the application settings using a helper method called get_credentials. We then use it along with the authorization_code in order to make a POST request to Stripe Connect API. The full method is given below for reference.

@staticmethod
def get_event_organizer_credentials_from_stripe(stripe_auth_code):
        """
        Uses the stripe_auth_code to get the other credentials for the event organizer's stripe account
        :param stripe_auth_code: stripe authorization code
        :return: response from stripe
        """
        credentials = StripePaymentsManager.get_credentials()

        if not credentials:
            raise Exception('Stripe is incorrectly configured')

        data = {
            'client_secret': credentials['SECRET_KEY'],
            'code': stripe_auth_code,
            'grant_type': 'authorization_code'
        }

        response = requests.post('https://connect.stripe.com/oauth/token', data=data)
        return json.loads(response.text)

We call the above method before creating the object using the before_create_object method of Marshmallow which allows us to do data preprocessing and validations.

If the request was a success, the response from Stripe connect API includes all the details necessary to accept payments on their behalf. We add these fields to the data and save it in the database.

{
  "token_type": "bearer",
  "stripe_publishable_key": PUBLISHABLE_KEY,
  "scope": "read_write",
  "livemode": false,
  "stripe_user_id": USER_ID,
  "refresh_token": REFRESH_TOKEN,
  "access_token": ACCESS_TOKEN
}

In case there was an error, an error_description would be returned. This error_description is sent back to the frontend and shown to the event organizer.

{
  "error": "invalid_grant",
  "error_description": "Authorization code already used:                                               
                        AUTHORIZATION_CODE"
}

After successfully fetching the results, we save it inside the database and return the stripe_publishable_key which will be used by the Frontend when charging the ticket buyers later.

Lastly we can go over the Stripe Authorization model as well. The stripe_secret_key will be used to charge the customers later.

id = db.Column(db.Integer, primary_key=True)
stripe_secret_key = db.Column(db.String)
stripe_refresh_token = db.Column(db.String)
stripe_publishable_key = db.Column(db.String)
stripe_user_id = db.Column(db.String)
stripe_auth_code = db.Column(db.String)

References

 

Continue ReadingStripe Authorization in Open Event Server

Adding Defaults Prior to Schema Validation Elegantly

The Open Event Server offers features for events with several tracks and venues. When we were designing the models for the API, we wanted to add default values for some fields in case they aren’t provided by the client. This blog discusses how we have implemented in the project using python decorators that complies to the DRY principle and is easy to test and maintain.

Problem

Let’s first discuss the problem at hand in detail. We use Marshmallow extensively in our project. Marshmallow is an ORM/ODM/framework-agnostic library for converting complex data types, such as objects, to and from native python data types. We use it for Validating the input data, Deserializing the input data to app-level objects and Serializing app-level objects to primitive Python types.

We can define Schema’s very easily using Marshmallow. It also provides an easy way to declare default values to the fields. Below is a sample schema declaration:

class SampleSchema(Schema):
    """
    Sample Schema declaration
    """

    class Meta:
        """
        Meta class for the Sample Schema
        """
        type_ = 'sample-schema'

    id = fields.Str(dump_only=True)
    field_without_default = fields.Str(required=True)
    field_with_default = fields.Boolean(required=True, default=False)

We have defined an id field for storing the unique ID of the Model. We have also defined two other fields. One of them named as “field_with_default” is a Boolean field and has a default value specified as False.

When a client makes a POST/PATCH request to the server, we first validate the input data sent to us by the clients against the defined schema. Marshmallow also supports schema validation but it doesn’t support using default values during deserialization of the input data. It meant that whenever the input data had a missing field, the server would throw a validation error even for a field for which the default values are defined. It was clearly wrong since if the default values are defined, we would want that value to be used for the field. This defeats the entire purpose of declaring default values at the first place.

So, we would ideally like the following behaviour from the Server:

  1. If the values are defined in the input data, use it during validation.
  2. If the value for a required field is not defined but default value has been defined in the Schema, then use that value.
  3. If no value has been defined for a required field and it doesn’t have any default value specified, then throw an error.

Solution

Marshmallow provides decorators like @pre_load and @post_load for adding pre-processing and post-processing methods. We can use them to add a method in each of the Schema classes which takes in the input data and the schema and adds default values to fields before we validate the input.

The first approach which we took was to add the following method to each of the schema classes defined in the project.

@pre_load
def patch_defaults(schema, in_data):
        data = in_data.get('data')
        if data is None or data.get('attributes') is None:
            return in_data
        attributes = data.get('attributes')
        for name, field in schema.fields.items():
            dasherized_name = dasherize(name)
            attribute = attributes.get(dasherized_name)
            if attribute is None:
                attributes[dasherized_name] = field.default
        return in_data

The method loops over all the fields defined in the schema class using schema.fields.item(). dasherize is a helper function defined in the utils class which converts underscores(_) in the variable name to dashes(-). After replacing the underscores with dashes we check if the value for the attribute is None. If it is None, then we assign it the specified default value.

Enhancing the solution

The above solution works but there is a problem. We have around 50 schemas defined in the project. Copy pasting this method 50 times would definitely violate the DRY principle. Moreover if we need to change this method in the future, we would have to do it 50 times.

One way to avoid it would be to add the patch_defaults method in a separate file and add a helper method make_object in each of the schema classes which just calls it.

@pre_load
def make_object(self, in_data):
    return patch_defaults(self, in_data)

We would still be repeating the helper method in 50 different files but since it’s sole purpose is to call the patch_defaults method, we won’t have to make changes in 50 files.

It certainly works well but we can go a step further and make it even easier. We can define a class decorator which would add the above make_object method to the class.

def use_defaults():
    """
    Decorator added to model classes which have default values specified for one of it's fields
    Adds the make_object method defined above to the class.
    :return: wrapper
    """
    def wrapper(k, *args, **kwargs):
        setattr(k, "make_object", eval("make_object", *args, **kwargs))
        return k
    return wrapper

Now we can simply add the use_defaults() decorator on the schema class and it would work.

References

Continue ReadingAdding Defaults Prior to Schema Validation Elegantly

Handling Click Events using Custom Binding Adapters

The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. It is currently released in the Alpha phase on the Google Play Store here and is being actively developed by the community.

The Data Binding Library is one of the most popular libraries among the android developers. We use it extensively in the application in order to greatly simplify the UI binding logic. While trying to show the details of a speaker in the application, we wanted to list his/her social media links using Image buttons.

Upon clicking one of these buttons, the user was supposed to be directed to the link after opening the default web browser. This blog post discusses how we used custom Binding Adapters to handle click events on an Image Button by defining a custom attribute.

Defining the Binding Adapter

We defined a simple Binding Adapter for an Image button meant to handle social media links. We used “imageOnClick” as the custom attribute name for specifying the URL that will be opened once the button is clicked.

@BindingAdapter("imageOnClick")
public static void bindOnImageButtonClickListener(ImageButton imageButton, String url) {
  imageButton.setOnClickListener(view -> {
    if (url != null) {
      Context context = imageButton.getContext();
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setData(Uri.parse(url));
      if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
      } else {
        Toast.makeText(context, "No Web browser found", Toast.LENGTH_SHORT).show();
      }
    }
  });
}

 

The method can be named anything you want and can be placed anywhere in the project but we would recommend creating a separate class for all the Binding adapters.
The important things to take away from the above method are:

  • The method needs to be public otherwise the Data binding framework won’t be able to find it.
  • We need to pass in the view as the first parameter and the attribute value as the second parameter.

Then we simply set the familiar click listener to handle the click interaction. We use the Context from the view passed in the method as the first parameter. Then we create an Intent and set the passed in URL as the data. We make sure that the user has a browser installed on his/her android phone before we try to open the browser. We show a suitable error message if they don’t.

Using it in Layout

Using the custom attribute in the layout was extremely simple. We specified the url using the attribute “imageOnClick” and the rest was handled by the Binding Adapter and the Data binding framework.  

<ImageButton
     android:id="@+id/action_speakers_linkedin"
     android:layout_width="@dimen/spacing_larger"
     android:layout_height="match_parent"
     android:contentDescription="@string/linkedin_icon"
     app:imageOnClick="@{ speaker.linkedin }"
     android:background="#ededed"
     android:visibility="@{ (speaker.linkedin != null) ? View.VISIBLE : View.GONE }"
     app:srcCompat="@drawable/ic_linkedin_colored"/>

References

Continue ReadingHandling Click Events using Custom Binding Adapters