Hiding Payment Options for Free Tickets in Open Event Android

Hiding Payment Options for Free Tickets in Open Event Android

Payment Options spinner allows the user to pick any convenient payment method for an order however, Payment Options should not be shown if the total worth of order is zero or the event is free. This blog post will guide you on how its implemented in Open Event AndroidFollowing are the sequence of steps that are followed for this

  • Allow the user to select tickets and their quantity
  • Use DAO method to get the prices of the tickets selected by the user
  • Iterate through the List and keep storing the prices
  • Display Payment Selector for order with the total amount greater than zero

Given below is DAO method to get the list of prices for different tickets. The method makes a simple select statement and returns price attribute of tickets with id in passed ids. A single list of float is returned by the function.

 @Query(“SELECT price from Ticket WHERE id in (:ids)”)
   fun getTicketPriceWithIds(ids : List<Int>): Single<List<Float>>

This DAO method is then exposed to other parts (ViewModels, Fragments) using service layer class method getTicketPriceWithIds which just makes calls to DAO method and return the result provided by it. Service classes are used for separation of concerns.

fun getTicketPriceWithIds(ids: List<Int>): Single<List<Float>> {
       return ticketsDao.getTicketPriceWithIds(ids)
   }

When a list of tickets and their quantity is specified by the user the prices of the ticket are fetched using the above-explained methods. These are then stored it in a List of pair with the first element being ticket id and second its quantity.

fun updatePaymentSelectorVisibility(ticketIdAndQty: List<Pair<Int, Int>>?) {
       val ticketIds = ArrayList<Int>()
       ticketIdAndQty?.forEach { if (it.second > 0) ticketIds.add(it.first) }
       compositeDisposable.add(ticketService.getTicketPriceWithIds(ticketIds)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe({
                   var total = 0.toFloat()
                   it?.forEach {
                       if (it.toFloat() > 0) total += it.toFloat()
                   }
                   paymentSelectorVisibility.value = total != 0.toFloat()
               }, {
                   Timber.e(it, “Error Loading tickets!”)
               }))
   }

The above method controls the visibility boolean for payment selector, it essentially iterates over the List<Pair<Int, Int>> and add all the ticket id to a temporary list if quantity for that ticket is greater than zero after this prices for the tickets in temporary list is fetched and sum total is calculated. If this total is greater than zero the visibility boolean is set else reset.

Finally an observable is set on this boolean which automatically updates the UI whenever boolean changes accordingly.

attendeeFragmentViewModel.paymentSelectorVisibility.observe(this, Observer {
           if (it !=null && it) {
               rootView.paymentSelector.visibility = View.VISIBLE
           } else {
               rootView.paymentSelector.visibility = View.GONE
           }
        })

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

Mapping Events to Load from Database

Mapping Events to Load from Database

In Open Event Android whenever App is started events are fetched for the location given by the user, since we have locally added isFavorite extra field for every event it is necessary to be updated for all the events returned in the API response before inserting it into our database otherwise all the favorite related information would be lost. This blog post will guide you on how its done in Open Event Android.

The sequence of steps followed

  • Take the IDs of events being saved into the database
  • Use DAO method which does “SELECT id from Event where favorite = 1 AND id in :eventIds and pass API returned eventIds to this function
  • Set the old favorited on new event objects
  • Save them in database

Let’s see how all of these steps are performed in greater details. Whenever user gives in a location following function is called

fun getEventsByLocation(locationName: String): Single<List<Event>> {
       return eventApi.searchEvents(“name”, locationName).flatMap { apiList ->
           val eventIds = apiList.map { it.id }.toList()
           eventDao.getFavoriteEventWithinIds(eventIds).flatMap { favIds ->
               updateFavorites(apiList, favIds)
           }
       }
   }

Here we are extracting all the Ids of events returned in the API response and then calling getFavoriteEventWithinIds on it. The latter takes the list of eventIds and return the Ids of events which are favorite out of them. This is then passed to the function updateFavorite along with the API returned Events. Following is the implementation of updateFavorite method.

fun updateFavorites(apiEvents: List<Event>, favEventIds: List<Long>): Single<List<Event>> {
       apiEvents.map { if (favEventIds.contains(it.id)) it.favorite = true }
       eventDao.insertEvents(apiEvents)
       val eventIds = apiEvents.map { it.id }.toList()
       return eventDao.getEventWithIds(eventIds)
   }

updateFavorite checks for all events in the list of events whether if its Id is present in the favorite event ids it sets favorite field of that event true. After the favorites for the list of events are updated they are inserted into the database using DAO method insertEvents. The last task is to take the fetch these events again from the database, to do this first we extract the Ids of the events we just inserted into the database and call the DAO method getEventsWithIds passing the extracted eventIds, getEventsWithids simply returns the Events with given Ids.

Given below are the implementations of the functions getEventWithIds and getFavoriteEventWithinIds

@Query(“SELECT * from Event WHERE id in (:ids)”)
   fun getEventWithIds(ids: List<Long>): Single<List<Event>>

@Query(“SELECT id from Event WHERE favorite = 1 AND id in (:ids)”)
   fun getFavoriteEventWithinIds(ids : List<Long>): Single<List<Long>>

getEventWithIds simply makes a select query and checks for events whose ids lies in the ids passed to the method

getFavoriteEventWithinids returns the Ids of the favorite event out of the list of event id passed to the method.

Resources

Continue Reading

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 Reading

Setting an Event Favorite in Open Event Android

The favorite events feature in Open Event Android allows any user to favorite an event and those events can are available in the favorite events fragment which is easily accessible from the bottom navigation bar. This blog article will walk you through on how favorite works in Open Event Android. There are a couple of different ways to do it, in Open Event Android we are keeping track of favorite events using a favorite Boolean stored along with the information of the event ie we have added a favorite boolean field inside the event entity class. However, this method has its own pros and cons. The big plus point of this method is the fact that we can simply check the favorite field of the event object to check if that particular event is favorite or not. But since we have set onConflict strategy as replace in the event insert DAO method (shown below)

 @Insert(onConflict = REPLACE)
   fun insertEvents(events: List<Event>)

This leads to a big problem when the same event is fetched or is present in the response while inserting the favorite field will be set to default value (false) and the favorite information would be lost. Hence extra care needs to be taken while updating events.

Given following is the event model class for serializing / deserializing JSON responses. Note the extra field favorite added here which helps us to provide user favorite feature locally.

@Type(“event”)
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
@Entity
data class Event(
      @Id(LongIdHandler::class)
      @PrimaryKey
      val id: Long,
      val name: String,
      val identifier: String,
      val isMapShown: Boolean = false,
      val favorite: Boolean = false
)

Since we added a new field to the Event model class, to access the favorite events we will have to create DAO methods. To fetch all the favorite events we will have to create a Query method which returns all the events wherever favorite property is set. This can be done using a simple select statement which looks for events with favorite boolean set ie. 1, the method implementation is shown below.

@Query(“SELECT * from Event WHERE favorite = 1”)
   fun getFavoriteEvents(): Flowable<List<Event>>

To set an event favorite we will have to add one more method to the EventDao. We can create a generalized method which sets according to the boolean passed as a parameter. The function implementation is shown below, setFavorite takes in EventId, Id of the event which has to be updated and the boolean favorite which is what the new value for the favorite field is. setFavorite makes an SQL update query and matches for EventId and sets favorite for that event.

@Query(“UPDATE Event SET favorite = :favorite WHERE id = :eventId”)
   fun setFavorite(eventId: Long, favorite: Boolean)

In Open Event Android we use the service class which can be used to expose the DAO methods to the rest of the project. The idea behind creating service layer is the separation of concerns this service method is then called from the view model function (following the MVVM approach). Given following is the implementation of the get and set favorite methods.

fun getFavoriteEvents(): Flowable<List<Event>> {
       return eventDao.getFavoriteEvents()
   }
fun setFavorite(eventId: Long, favourite: Boolean): Completable {
       return Completable.fromAction {
           eventDao.setFavorite(eventId, favourite)
       }
   }

The method getFavoriteEvents calls the DAO method to fetch the favorite evens and returns a flowable list of favorite events and setFavorite method generates a completable from the DAO action to favorite any event.

EventViewModel uses these methods and specifies where the observables should be observedOn and subscribedOn also defines what should happen when some error occurs. Given below is implementation for the two methods.

fun setFavorite(eventId: Long, favourite: Boolean) {
       compositeDisposable.add(eventService.setFavorite(eventId, favourite)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe({
                   Timber.d(“Success”)
               }, {
                   Timber.e(it, “Error”)
                   error.value = “Error”
               }))
   }

For every event, we have a floating action bar to toggle the favorite state of the same whenever user clicks on the FAB it checks the current state of the event and calls the view model function setFavorite passing the Event Id of the clicked event and the negation of current event.favorite state.

val favouriteFabClickListener = object : FavoriteFabListener {
           override fun onClick(eventId: Long, isFavourite: Boolean) {
               eventsViewModel.setFavorite(eventId, !isFavourite)
           }
       }

What we discussed in this blog post works well unless we are not inserting the events with the same event id, this will cause replace on the stored events and current favorite information will be lost. To overcome this limitation we follow the following procedure

  • Fetch event id’s of all the events returned by the API response
  • Use the DAO method to find out the id’s of favorite events out of these
  • Set the favorite in the new events where IDs are in the ids returned in the second point
  • Insert the events into the database
  • Return the events from the database with ID’s as of point one

References

Continue Reading
  • 1
  • 2
Close Menu
%d bloggers like this: