Implementing Country Preference in Orga App

In the Open Event Orga App, there was no option earlier to save the country preference in the shared preference. So every time the user had to select the country while creating events. Hence an option to select a country was added to the Event Settings. So any value which gets selected here acts as a default country while creation of events.

Steps

  • Add the constant key to the Constants.java class.
public static final String PREF_PAYMENT_COUNTRY = “key”;
  • Create a class CountryPreference.java which extends DialogPreference. It is in this class that all the code related to the dialog which appears in selecting the Country preference will appear. First we create a layout for the dialog box. Following is the XML file for the same.
<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_margin=“@dimen/spacing_small”
  android:padding=“@dimen/spacing_small”
  android:orientation=“vertical”>

  <android.support.v7.widget.AppCompatSpinner
      android:id=“@+id/country_spinner”
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:layout_marginTop=“@dimen/spacing_small” />

</LinearLayout>
  • Now we create the CountryPreference constructor where we specify the UI Of the dialog box. It would include the positive and negative button.
private int layoutResourceId = R.layout.dialog_payment_country;
private int savedIndex;

public CountryPreference(Context context, AttributeSet attrs) {
  super(context, attrs, R.attr.preferenceStyle);
  setDialogLayoutResource(R.layout.dialog_payment_country);
  setPositiveButtonText(android.R.string.ok);
  setNegativeButtonText(android.R.string.cancel);
  setDialogIcon(null);
}
  • We override the method onSetInitialValue where we set the preference of the country in the shared preference. We call the method setCountry and pass the persisted value.
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
  setCountry(restorePersistedValue ? getPersistedInt(savedIndex) : (int) defaultValue);
  super.onSetInitialValue(restorePersistedValue, defaultValue);
}

 

public void setCountry(int index) {
  savedIndex = index;
  persistInt(index);
}
  • We create a class CountryPreferenceCompat which extends PreferenceDialogFragmentCompat. It is here that we initialize the spinner and set the adapter. It is here that we override the method onDialogClosed which should happen when the dialog box is closed. Following is the code for the same.
@Override
public void onDialogClosed(boolean positiveResult) {
  if (positiveResult) {
      DialogPreference preference = getPreference();
      if (preference instanceof CountryPreference) {
          CountryPreference countryPreference = ((CountryPreference) preference);
          countryPreference.setCountry(index);
      }
  }
}
  • In the PaymentPrefsFragment the code for initialization of the dialog is added. We override the onDisplayPreferenceDialog.
@Override
public void onDisplayPreferenceDialog(Preference preference) {
  CountryPreferenceFragmentCompat dialogFragment = null;
  if (preference instanceof CountryPreference)
      dialogFragment = CountryPreferenceFragmentCompat.newInstance(Constants.PREF_PAYMENT_COUNTRY);

  if (dialogFragment != null) {
      dialogFragment.setTargetFragment(this, 1);
      dialogFragment.show(this.getFragmentManager(),
          “android.support.v7.preference” +
              “.PreferenceFragment.DIALOG”);
  } else {
      super.onDisplayPreferenceDialog(preference);
  }
}
  • Now the PaymentCountry spinner can be seen on testing.

References

  1. Building Custom Preference https://www.hidroh.com/2015/11/30/building-custom-preferences-v7/
  2. StackOverflow solution https://stackoverflow.com/questions/16577173/spinnerpreference-how-to-embed-a-spinner-in-a-preferences-screen  

 

Continue Reading

Adding Multiple Attendees inside an Order in Open Event Android

Orders endpoint allows multiple attendees to be passed in the body of post request which essentially means that and an order may contain tickets for different attendees.

This blog post will guide you on how multiple attendees under an Order is handled and implemented in Open Event Android.

An attendee as the name implies gives us information about how many people are attending any particular event. Let’s say if a User selects two tickets with 3 and 7 quantity total 10 attendees will be generated and can be put inside one Order request.

Whenever multiple tickets are selected by the user following function is called to create Attendee

fun createAttendees(attendees: List<Attendee>, country: String?, paymentOption: String) {
       this.country = country
       this.paymentOption = paymentOption
       this.attendees?.clear()
       attendees.forEach {
           createAttendee(it, attendees.size)
       }
   }

 

Create attendees accepts a list of attendees and calls the createAttendees for every attendee in the list. Actual attendee creation is handled in createAttendee. The above function also set up the country and payment option as provided by the user.

fun createAttendee(attendee: Attendee, totalAttendee: Int) {        compositeDisposable.add(attendeeService.postAttendee(attendee)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnSubscribe {
                   progress.value = true
               }.doFinally {
                   progress.value = false
               }.subscribe({
                   attendees?.add(it)
                   if (attendees != null && attendees?.size == totalAttendee) {
                       loadTicketsAndCreateOrder()
                   }
                   message.value = “Attendee created successfully!”
                   Timber.d(“Success! %s”, attendees?.toList().toString())
               }, {
                   message.value = “Unable to create Attendee!”
                   Timber.d(it, “Failed”)
               }))
   }

Create an Attendee on the other hand deals with creating individual attendees on the server. It calls the service function attendeeService.postAttendee with attendee as the parameter if the post request made is succeeds the attendee returned from the API is added to the list of attendees, when the size of list of attendees become equal to the list of attendees that had to be generated loadTicketsAndCreateOrder is called which takes care of getting charges for user and creating an order. If the post request for the attendee is failed a message is displayed and a log is added to console with error details.

fun loadTicketsAndCreateOrder() {
       if (this.tickets.value == null) {
           this.tickets.value = ArrayList()
       }
       this.tickets.value?.clear()
       attendees?.forEach {
           loadTicket(it.ticket?.id)
       }
   }

Load tickets and create order also works in a similar way as of creating attendees as there are two functions in here also one of which takes a list of attendees and calls the other function which gets data for individual ticket items passes as the parameter. Here ticket details for every attendee are fetched using the loadTicket function which takes ticket id corresponding to an attendee (using it.tickets?.id) and returns the Ticket object. Given below is the implementation for loadTicket method.

fun loadTicket(ticketId: Long?) {        compositeDisposable.add(ticketService.getTicketDetails(ticketId)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe({
                   tickets.value?.add(it)
                   Timber.d(“Loaded tickets! %s”, tickets.value?.toList().toString())
                   if (tickets.value?.size == attendees?.size) {
                       createOrder()
                   }
               }, {
                   Timber.d(it, “Error loading Ticket!”)
               }))
   }

Load ticket uses the service method to fetch the ticket details using the passed ticket id and the returned ticket object is added to the list of ticket. When the size of the ticket becomes equal to the size of total no of attendees which means that ticket details of each attendee are fetched createOrder is called which is responsible for creating order. If anything fails in fetching tickets log is statement is printed along with error information.

fun createOrder() {

val order = Order(getId(), paymentMode, country, “pending”, amount, attendees = attendeeList, event = EventId(eventId))


           compositeDisposable.add(orderService.placeOrder(order)
                   .subscribeOn(Schedulers.io())
                   .observeOn(AndroidSchedulers.mainThread())
                   .doOnSubscribe {
                       progress.value = true
                   }.doFinally {
                       progress.value = false
                   }.subscribe({
                       orderIdentifier = it.identifier.toString()
                       message.value = “Order created successfully!”
                       Timber.d(“Success placing order!”)
                       if (it.paymentMode == “free”)
                           paymentCompleted.value = true
                   }, {
                       message.value = “Unable to create Order!”
                       Timber.d(it, “Failed creating Order”)
                   }))
       } else {
           message.value = “Unable to create Order!”
       }
   }

Create order function is the final step in the process it takes the list of attendees, payment modes, amount and other information related to order and create an order object out of it which is then passed to the service layer method which makes a post request to generate an order on the server. In case of an error or success whole message is logged and toast is displayed. This method is also involved with showing progress bar once this is called and closing the same when the procedure is completed.

Finally, in case of free events the user is taken to order completed screen and in case of paid events the user is taken to the payment screen where the user has to provide payment related information

Resources

Continue Reading

Ticket Quantity Spinner in Open Event Android

Spinners are basically drop down menu which provides an easy way to select an item from a set of items. Spinner is used in Open Event Android to allow user select quantity of tickets as shown in the image above. This blog post will guide you on how its implemented in Open Event Android.

Add Spinner to the XML file

<Spinner
           android:id=“@+id/orderRange”
           android:layout_width=“30dp”
           android:layout_height=“30dp”
           android:spinnerMode=“dialog”
           android:textSize=“@dimen/text_size_small” />

The above code spinnet will create a spinner type view with 30dp height and width, spinnerMode can be dialog or dropDown following are example spinner for both of the modes left being dialog type and right dropDown type.

                

(Image Source: Stack Overflow)

Set Up Adapter and Populate Data on Spinner

To show the list of acceptable Quantities for a Ticket, create an ArrayList of String and add all values from ticket.minOrder to ticket.maxOrder along with a zero option. Since ArrayList is of String and the values are integer they need to be converted to String using Integer.toString(i) method. Following code snippet will give you more understanding about it.

val spinnerList = ArrayList<String>()
           spinnerList.add(“0”)
           for (i in ticket.minOrder..ticket.maxOrder) {
               spinnerList.add(Integer.toString(i))
           }

We will also need to set up an onItemSelectedListener to listen for Item Selections and override onItemSelected and onNothingSelected functions in it. Whenever an item is selected onItemSelected is called with arguments such as view, position of the selected item, id etc, these can be used to find the selected Quantity from the ArrayList of acceptable Quantities.      

    itemView.orderRange.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
               override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
                   itemView.order.text = spinnerList[pos]
                   selectedListener?.onSelected(ticket.id, spinnerList[pos].toInt())
               }

               override fun onNothingSelected(parent: AdapterView<*>) {
               }
           }

Since Spinner is an Adapter Based view we need to create a SpinnerAdapter and attach it with the Spinner. If you want to create custom Spinners you would have to create a custom adapter for it along with a layout for its row elements or else one can use library-provided layout. Given below is the implementation for library-provided spinner row type. There are different options available for row type we are using R.layout.select_dialog_singlechoice because we need single selectable Quantity spinner along with Radio button for every ticket.

     itemView.orderRange.adapter = ArrayAdapter(itemView.context, R.layout.select_dialog_singlechoice, spinnerList)

Resources

Continue Reading
Close Menu