Selecting Multiple Tickets in Open Event Android

Open Event Android allows the user to select multiple tickets for an event this blog post will guide you on how its done. Ticket Id and quantity of ticket selected is stored in the form of List of pair. The first element of the List is the Id of the ticket and the second element is its quantity.

private var tickeIdAndQty = ArrayList<Pair<Int, Int>>()

Whenever the user selects any ticket using the dropdown the following snippet of code is executed. handleTicketSelect takes id and quantity of the selected ticket and search if any pair with passed id as the first element exists in the ticketIdAndQty list, if exists it updates that record else it inserts a new pair with given id and quantity into the list.

private fun handleTicketSelect(id: Int, quantity: Int) {
       val pos = ticketIdAndQty.map { it.first }.indexOf(id)
       if (pos == –1) {
           ticketIdAndQty.add(Pair(id, quantity))
       } else {
           ticketIdAndQty[pos] = Pair(id, quantity)
       }

When the user selects register the ticketIdAndQty list which contains details of selected tickets is added to the bundle along with the event id and are passed to attendee fragment. Attendee fragment allows the user to fill in other details such as country, payment option, first name etc.

Later attendee fragment with the help of attendee view model creates individual attendee on the server

rootView.register.setOnClickListener {
           if (!ticketsViewModel.totalTicketsEmpty(ticketIdAndQty)) {
               val fragment = AttendeeFragment()
               val bundle = Bundle()
               bundle.putLong(EVENT_ID, id)
               bundle.putSerializable(TICKET_ID_AND_QTY, ticketIdAndQty)
               fragment.arguments = bundle
               activity?.supportFragmentManager
                       ?.beginTransaction()
                       ?.replace(R.id.rootLayout, fragment)
                       ?.addToBackStack(null)
                       ?.commit()
           } else {
               handleNoTicketsSelected()
           }
       }

When the register is selected an attendee is created for every ticket id inside ticketIdandQty list wherever the second element of the pair that is quantity is greater than zero. FFirst name last name, email and other information is taken from the text views populated on the screen and attendee object is created using these data. Lastly, createAttendee view model function is called passing the generated attendee, country, and selected payment option. The former calls service layer function and created an attendee by making a post request to the server.

 ticketIdAndQty?.forEach {
                   if (it.second > 0) {
                       val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
                               firstname = firstName.text.toString(),
                               lastname = lastName.text.toString(),
                               email = email.text.toString(),
                               ticket = TicketId(it.first.toLong()),
                               event = eventId)
                       val country = if (country.text.isEmpty()) country.text.toString() else null
                       attendeeFragmentViewModel.createAttendee(attendee, id, country, selectedPaymentOption)
                   }
               }

The above-discussed procedure creates one attendee for every ticket quantity which means if a user select ticket id 1 with 3 quantity and ticket id 2 with 5 as quantity total 8 attendees will be generated. Later after successfully generating the attendee’s order is created. Every order has an array of attendees along with other other related information.

Resources

Continue Reading

Updating User information in Open Event Android

A user can update its information such as first name, last name from the Edit Profile Fragment in Open Event Android. Edit Profile Fragment can be accessed from the menu inside the Profile page. On opening Edit Profile Fragment user can interact with the simple UI to update his/her information. This blog post will guide you on how its implemented in Open Event Android.

To update a User we send a patch request to Open Event Server. The patch request contains the Updated User as body and auth token as header and it returns the updated user in response. Following it what the interface method looks like

@PATCH(“users/{id}”)
fun updateUser(@Body user: User, @Path(“id”) id: Long): Single<User>

This method is exposed to the View Model using a service layer function which calls the above function and also inserts the returned user in the database.

fun updateUser(user: User, id: Long): Single<User> {
       return authApi.updateUser(user, id).map {
           userDao.insertUser(it)
           it
       }
   }

On using map on Single<User> returned by updateUser we can access the user inside the scope which is then inserted into the database using the DAO method insert user and the same user object is returned by the function.

This service layer method is then used in updateUser method of EditProfileViewModel class which specifies how it is subscribed and on which thread observer should be set etc. The Edit Profile Fragment fragment calls this method whenever the user clicks on the Update button.

fun updateUser(firstName: String, lastName: String) {
       val id = authHolder.getId()
       if (firstName.isEmpty() || lastName.isEmpty()) {
           message.value = “Please provide first name and last name!”
           return
       }
       compositeDisposable.add(authService.updateUser(User(id = id,          firstName = firstName, lastName = lastName), id)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnSubscribe {
                   progress.value = true
               }
               .doFinally {
                   progress.value = false
               }
               .subscribe({
                   message.value = “User updated successfully!”
                   Timber.d(“User updated”)
               }) {
                   message.value = “Error updating user!”
                   Timber.e(it, “Error updating user!”)
               })
   }

UpdateUser takes two parameters first name and last name if any of these parameters is empty the function returns with an error message which is displayed on the UI else service layer update user function is called with argument a User object with first name and last name as provided to view model function and an id which is accessed using authHolder’s getId method. Whenever this is subscribed we set progress.value true which displays spinner on the UI this is set false after the operation is complete. If the patch request results in success then toast message is shown on screen and a success message is logged similar to this in case of error, an error toast is displayed and an error is logged.

This goes for the logic to update user we also need UI and menu item which launches this fragment.

Inside Menu.xml add the following snippet of code

<item
  android:id=“@+id/edit_profile”
  android:title=“@string/edit_profile” />

This will create a menu item inside the ProfileFragment. The next step is to wire this logic which tells what happens when the user selects this menu item. The following code wires it to EditProfileFragment.

R.id.edit_profile -> {
               val fragment = EditProfileFragment()              activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.frameContainer, fragment)?.addToBackStack(null)?.commit()
               return true

Resources

Continue Reading

Bottom Navigation Bar in Open Event Android

Bottom navigation bar provides an easy access and convenient way for users to navigate in between different sections of an App where the sections are of equal importance. We use bottom navigation move between different fragments such as events screen, favorites, tickets profile and so on. This blog post will guide you on how its implemented in Open Event Android.

To create a bottom navigation bar first create items for your menu. Menu items will be rendered as elements of the bottom navigation bar. Add icon and title to your navigation item here.

   <item
       android:id=“@+id/navigation_profile”
       android:icon=“@drawable/ic_baseline_account_circle_24px”
       android:title=“@string/profile” />

After we have written menu xml for our navigation view, we will have to create a view for it. The following snippet of code creates a BottomNavigationView type view element on the screen with different attributes as specified and menu file as specified by app:menu. Make sure you have android support design dependency set up before this.

<android.support.design.widget.BottomNavigationView
           android:id=“@+id/navigation”
           android:layout_width=“match_parent”
           android:layout_height=“wrap_content”
           android:layout_gravity=“bottom”
           android:background=“?android:attr/windowBackground”
           app:itemBackground=“@android:color/white”
           android:foreground=“?attr/selectableItemBackground”
           app:itemTextColor=“@color/colorPrimaryDark”
           app:menu=“@menu/navigation” />

No that our view will be created we need to wire menu items with different fragment and set up listener for item selected events.

Inside onCreate of MainActivity we attach a ItemSelectListener with the bottom navigation bar, navigation.setOnNavigationItemSelectedListener(listener) .

private val listener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
       val fragment: Fragment
       when (item.itemId) {
           R.id.navigation_profile -> {
               supportActionBar?.title = “Profile”
               fragment = ProfileFragment()
               loadFragment(fragment)
               return@OnNavigationItemSelectedListener true
           }
           R.id.navigation_favorite -> {
               supportActionBar?.title = “Likes”
               fragment = FavoriteFragment()
               loadFragment(fragment)
               return@OnNavigationItemSelectedListener true
           }
           R.id.navigation_tickets -> {
               supportActionBar?.title = “Tickets”
               fragment = OrdersUnderUserFragment()
               loadFragment(fragment)
               return@OnNavigationItemSelectedListener true
           }
       }
       false
   }

The above listener object listens for item selection in the bottom navigation bar. Whenever any item is selected lambda function is executed and item.itemId is matched with different menu item ids. On any successful match, an instance of the fragment corresponding to the selected item is loaded.

References

Continue Reading

Changing Profile Image in Open Event Android

A user can create a custom Avatar in Open Event Android. We use Picasso Library for easy image loading and caching in Open Event Android. This blog post will help you understand how its implemented in Open Event Android.

These are the steps we follow :

  • Create UI element which allows the user to pick an avatar
  • Checking and requesting Read External Storage permission
  • Compress and Encode Image
  • Use Picasso to display image on UI
  • Upload it on the server on user’s command

We will discuss the above steps in detail in the following section.

Create UI element which allows the user to pick an avatar

To display the selected avatar image as well as to fire image pick action when clicked we need a UI element. We display a placeholder in case no avatar image is provide, in this case, its ic_account_circle_grey_24dp which lies in drawable folder. Rest of the code is self explanatory.

<ImageView
       android:id=“@+id/profilePhoto”
       android:layout_width=“@dimen/item_image_view_large”
       android:layout_height=“@dimen/item_image_view_large”
       android:layout_gravity=“center_horizontal”
       android:contentDescription=“Profile Photo”
       android:padding=“@dimen/padding_large”
       app:srcCompat=“@drawable/ic_account_circle_grey_24dp” />

Once we have created the UI we need to wire it with the action to pick an image, the following code snippet does that. When this ImageView is clicked permissionGranted variable is checked which keeps a track of read external storage permission if this is true file chooser is displayed to allows user to select an image else an dialog for requesting permissions is shown.

rootView.profilePhoto.setOnClickListener { v ->
           if (permissionGranted) {
               showFileChooser()
           } else {
               requestPermissions(READ_STORAGE, REQUEST_CODE)
           }
       }

Checking and requesting Read External Storage permission

As discussed in the above section permission check is performed when ImageView is clicked and if not granted a dialog request for read external storage is displayed. Initially, the boolean permissionGranted is set to false which means clicking on ImageView fires requestPermission method with permissions array and request code and by overriding the onRequestPermissionResult method we can look at request status.

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
       if (requestCode == REQUEST_CODE) {
           if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
               permissionGranted = true
               Toast.makeText(context, “Permission to Access External Storage Granted !”, Toast.LENGTH_SHORT).show()
               showFileChooser()
           } else {
               Toast.makeText(context, “Permission to Access External Storage Denied :(“, Toast.LENGTH_SHORT).show()
           }
       }
   }

onRequestPermissionsResult is called everytime requestPermission finishes, to check for which request it’s called we can check the Request code if it matches with the one we passed in our requestPermissions method that means the grant result carries information about our request. We can check the grantResult and if permission is granted we set our permissionGranted variable to true and display the file chooser menu.

Compress and Encode Image

Once the user picks the image from the file chooser we need to convert it into an input stream and we encode it into our JPEG format and specify the image quality. The following code snippet will help you understand how this happens.

val imageUri = intentData.data
           var imageStream: InputStream? = null
           try {
               imageStream = activity?.contentResolver?.openInputStream(imageUri)
           } catch (e: FileNotFoundException) {
               Timber.d(e, “File Not Found Exception”)
           }
w
           val selectedImage = BitmapFactory.decodeStream(imageStream)
           encodedImage = encodeImage(selectedImage)

           Picasso.get()
                   .load(imageUri)
                   .placeholder(R.drawable.ic_person_black_24dp)
                   .transform(CircleTransform())
                   .into(rootView.profilePhoto)
       }

The above code is executed after picking the image from file chooser. The image is first converted into input stream (this process is put inside a try catch because the conversion process may result in file not found exception) and then BItmapFactory’s decodeStream is called on it, all this is done so that we can convert URI into to bitmap now this bitmap is converted into JPEG format with 100 percent quality and then into a Base64 type string using the function encodeImage.

Finally, we use Picasso standard functions to load the image into UI we also specify placeholder image which is displayed if anything fails or image is loading.

Upload Image on the server on user’s command

If the user wish to keep the selected image as its avatar he can click on update button in open event android. This button fires a post request which uploads the image data to open event server followed by a patch request to add the URL to User information.

@POST(“upload/image”)
   fun uploadImage(@Body uploadImage: UploadImage): Single<ImageResponse>

This is the post request made to the server the body contains an UploadImage object which essentially caries a String type data variable containing the Base64 encoded jpeg bitmap. In response to the request, the server returns a URL which is then added to the User object and a patch request is made to update the user on the server

@PATCH(“users/{id}”)
   fun updateUser(@Body user: User, @Path(“id”) id: Long): Single<User>

References

Continue Reading

Hiding Multiple Attendee Recycler for Single Tickets in Open Event Android

Multiple attendee recycler allows the user to easily write in attendee details for all tickets he/she is buying. The UI for attendee details fragment can be divided into two segments, one contains the details of the person creating the whole order and followed by multiple details section for the attendee of every ticket. This is a good way to take details when multiple ticket or tickets with greater than 1 quantity is involved. However, if there is only one attendee detail to be taken instead of asking the user for order creator’s details and then first attendee details it is better to add some views in the creator segment and not ask for the same details again from the user. This blog post will help you in understanding how it’s done in Open Event Android.

Keeping a track of orders with single attendee

We keep a boolean to track whether or not the sum of quantities of tickets selected by a user is one since one attendee is to be generated per ticket quantity. And incase it is boolean variable singleTicket is set to True which is false by default initially. Following is how it can be done

singleTicket = ticketIdAndQty?.map { it.second }?.sum() == 1

The above statements basically iterate over the list of pairs of tickets and their quantity and then sums over the second element of all pairs that is the quantities, if the summation leads to 1 single ticket is set to True else false.

Adding elements to recycler adapter

The next step is to update the logic as to when blank attendees are added to recycler adapter. The idea is to check the boolean singleTicket and if it is false dont insert any attendee.

attendeeFragmentViewModel.tickets.observe(this, Observer {
               it?.let {

                   if (!singleTicket)
                       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()
                       }
               }
           })

If a single ticket is false regular procedure to add blank attendee is carried out. The quantity of ticket is found and the same quantity of attendees are added to recycler adapter’s attendee’s list similary ticket information is also added to the recycler adapter. The id for blank attendees is taken from the attendeeViewModel’s getId function which in turn simply returns id using the authHolder’s getId function.

Modify the register method to check for the single attendee

So far what we have discussed helps us display the right no of details section on the UI. We will also have to modify the register method to take into account the above changes. Following is the implementation of what happens when the user selects register.

rootView.register.setOnClickListener {
               if (selectedPaymentOption == “Stripe”)
                   sendToken()

               val attendees = ArrayList<Attendee>()
               if (singleTicket) {
                   val pos = ticketIdAndQty?.map { it.second }?.indexOf(1)
                   val ticket = pos?.let { it1 -> ticketIdAndQty?.get(it1)?.first?.toLong() } ?: –1
                   val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
                           firstname = firstName.text.toString(),
                           lastname = lastName.text.toString(),
                           city = getAttendeeField(“city”),
                           address = getAttendeeField(“address”),
                           state = getAttendeeField(“state”),
                           email = email.text.toString(),
                           ticket = TicketId(ticket),
                           event = eventId)
                   attendees.add(attendee)
               } else {
                   attendees.addAll(attendeeRecyclerAdapter.attendeeList)
               }
               val country = if (country.text.isEmpty()) country.text.toString() else null
               attendeeFragmentViewModel.createAttendees(attendees, country, selectedPaymentOption)
           }

For create attendee object and then add it to the attendees ArrayList first we need to find the ticket it is to be created for as the attendee object accepts Ticket Id as one of the parameters. The logic to get the ticket is fairly straightforward if singleTicket is true first we find out the ticket for which quantity is 1 and then we get the ticket object for the same. Finally, we take the id field of the ticket and add it to our attendee constructor. Other fields for attendee such as first name, last name, email, and country are taken from the EditText View’s rendered on the screen. Else if the single ticket is false which means there are multiple attendee details section on the fragment we simply add the recycler adapter’s attendee list into attendees ArrayList which is later provided to the order function for creating an order of it.

References

Continue Reading

Displaying Events In Open Event Android

The user can browse through different events happening nearby or all over the world quickly from the events fragment, the latter is accessible from the bottom navigation bar and provides the user with some details about the event. The user if interested can select the event by clicking on it and will be taken to event details fragment where all the necessary information about the event is given. This blog post will help in understanding how events are fetched and shown in Open Event AndroidThe implementation of displaying events can be divided into two parts

  • Fetching and storing events
  • Displaying events on UI using recycler view

Fetching and Storing Events

To fetch an event we need to make get the request to the Open Event Server’s list events endpoint.

   @GET(“events?include=event-topic”)
   fun getEvents(): Single<List<Event>>

We are using retrofit to make the network calls @GET specifies that it a get request and the URL inside the parentheses is the endpoint we are hitting. This method returns a Single<List<Event>>.

Given below is the service layer method which uses DAO method and the API call and returns a Flowable<List<Event>>.

fun getEvents(): Flowable<List<Event>> {
       val eventsFlowable = eventDao.getAllEvents()
       return eventsFlowable.switchMap {
           if (it.isNotEmpty())
               eventsFlowable
           else
               eventApi.getEvents()
                       .map {
                           eventDao.insertEvents(it)
                           }

                       .toFlowable()
                       .flatMap {
                           eventsFlowable
                       }
       }
   }

Firstly all the events from the database are fetched using the DAO method getAllEvents. If the list returned from the DAO method is not empty this function simply returns the events otherwise it will make the get request to server endpoint using Event API’s getEvents method and the response of this is mapped and stored into the database using the DAO method insertEvents this enables local caching of events and the same list of events are converted into flowable and returned by the function.

The events fragment calls the view model function which specifies where to observe (we are observing on main thread because it is involved with UI related changes) and what to subscribe on also handles the displaying of progress bar. Given following is the implementation of the same.

fun loadEvents() {
       compositeDisposable.add(eventService.getEvents()
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnSubscribe({
                   progress.value = true
               }).doFinally({
                   progress.value = false
               }).subscribe({
                   events.value = it
               }, {
                   Timber.e(it, “Error fetching events”)
                   error.value = “Error fetching events”
               }))
   }

You would notice the following parts in the above snippet

  • doOnSubscribe – This part of code is executed whenever the source is subscribed
  • Subscribe – This part of the code will have the end result when subscribed and success
  • doFinally – This part of the code will be executed at the end  

Creating Recycler View

Recycler view is similar to ListView with some enhancements. When we are dealing with larger datasets our typical choice for displaying lists type UI is Recycler view because its views can be recycled and scrolled very efficiently. Every custom RecyclerView consists of three parts

  • Layout for individual list items
  • Recycler View adapter
  • View Holder that holds views for individual list items

The item UI for events in Open Event Android contains two FABs (favorite and share), an image view (cover image of the event), text view for the date, month, event name and description.

The Recycler Adapter is like a middleware that connects our Recycler view with the view holder. The events recycler adapter contains a list of events and other functions to manipulate the data of this events list.

 private val events = ArrayList<Event>()

Once you have created the recycler view, the last step would be to insert the list of events into the recycler adapter.

eventsViewModel.events.observe(this, Observer {
           it?.let {
               eventsRecyclerAdapter.addAll(it)
               eventsRecyclerAdapter.notifyDataSetChanged()
           }
           Timber.d(“Fetched events of size %s”, eventsRecyclerAdapter.itemCount)
       })

The above code snippet is the implementation of how we add events into our recycler adapter. The idea is set an observer on the events mutable live data and put the code to insert events into recycler inside the observer block. This block is executed whenever there is any change in live data. The addAll method of recyclerview takes the list of events obtained inside the observer scope and updates the recycler view’s ArrayList of events. The update takes care no duplicate events are created by clearing the ArrayList if any events are present and then inserting the list of events.

Resources

Continue Reading

Saving Current State when Redirected to Login Fragment in Open Event Android

Some features in Open Event Android requires the user to be logged in, for example, buying tickets or viewing bought tickets. If the user selects to buy tickets for an event and he/she is not logged in, the user will be redirected to login fragment and on successful login, the user will return to the last state. This blog post will help you understand how its done in Open Event Android we will be taking the example of buying tickets without logged in.

To give you an overview once a user selects to buy tickets he is taken to a Tickets Fragment which contains different tickets and allows the user to pick their respective quantity. After selecting the right quantities the user can select on the register and he/she will be taken to Attendee Fragment which requires the user to be logged in, if not he will be redirected to login fragment and on successful login he can continue from attendee fragment without losing the selected tickets and their quantity information

To do this when redirecting from Attendee Fragment to the AuthActivity we modify the redirect method and send additional information with the intent such as even id and list of ticket id and quantity selected by the user. Following is how it can be done.

val intent = Intent(activity, AuthActivity::class.java)
       val bundle = Bundle()
       bundle.putLong(EVENT_ID, id.toLong())
       if (ticketIdAndQty != null)
           bundle.putSerializable(TICKET_ID_AND_QTY, ticketIdAndQty as ArrayList)
intent.putExtras(bundle)
 startActivity(intent)

Now that we have sent additional information with intent we will also have to modify the login fragment to use this information.

The above-discussed method will redirect the user to AuthActivity which handles all the authentication part. The user can either select to log in with an existing account or he may sign up with a new account. For existing accounts login fragment is shown and similarly for new sign up sign up fragment is shown.

The AuthActivity simply takes the bundle it received using intent.getExtras and passes the same to the fragment it is rendering. The load fragment function in Auth Activity looks like below. Whatever fragment is opened (signup/login) it passes the bundle that AuthActivity received as arguments to the fragments. This is done by setting fragment.arguments = bundle

private fun loadFragment(fragment: Fragment) {
       if (bundle != null)
           fragment.arguments = bundle
       supportFragmentManager.beginTransaction()
               .replace(R.id.frameContainerAuth, fragment)
               .commit()

Now we have all the bundle information in our login fragment next what we want is whenever login completes successfully send the user back to Attendee Fragment along with the event and ticket id and selected quantity information.

The following snippet of code takes the user to MainActivity whenever user information is received which means the user has successfully logged in

 loginActivityViewModel.user.observe(this, Observer {
           redirectToMain(bundle)
       })

redirectToMain accepts bundle as a parameter this bundle contains the ticket id and quantity list and the event id this is what we received from AuthActivity.

 private fun redirectToMain(bundle: Bundle?) {
       val intent = Intent(activity, MainActivity::class.java)
       if (((bundle != null) && !id.equals(-1)) && (ticketIdAndQty != null)) {
           intent.putExtra(LAUNCH_ATTENDEE, true)
           intent.putExtras(bundle)
       }
       startActivity(intent)
       activity?.overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right)
       activity?.finish()
   }

The above code snippet shows the redirectToMain function in Login Fragment, this method opens the Main Activity and passes the bundle as a parameter to it along with a trigger LAUNCH_ATTENDEE set. This trigger is responsible for telling MainAcitivty that the login fragment was actually a redirect from attendee fragment and MainActivity uses the bundle information to recreate the Attendee Fragment screen.

The following code handles this part in Main Activity.

if (bundle != null && bundle.getBoolean(LAUNCH_ATTENDEE)) {
           val fragment = AttendeeFragment()
           fragment.arguments = bundle
           loadFragment(fragment)
           openEventsFragment = false
       }

If the bundle information is available and the Launch attendee trigger is set AttendeeFragment is created with arguments as the bundle (fragment.arguments = bundle).

Resources

Continue Reading

Generating Order QR codes in Open Event Android

QR codes provide a very fast and convenient way to share information to other device, one device generates a bitmap image with information encoded in it, this bitmap can be easily scanned from the other device. QR codes are used in Open Event Android to share order identifier information, the latter can be used to check in attendee in Open Event Orga App. This blog post will guide you on how its implemented in Open Event Android.

Add the dependency

We use Zixing’s Barcode scanning library in Open Event Android for generating QR bitmaps. Add the following dependency in your app level gradle file. Please visit zxing GitHub page for latest version information.

implementation ‘com.journeyapps:zxing-android-embedded:3.4.0’

Create View for QR code

To display QR code we need to create and bitmap and then populate it on an Image View. The following code snippet generated an Image View on the screen, we have set the scale type to center crop so that image is scaled uniformly and the dimension of the image is equal to the corresponding dimension of the view.

    <ImageView
               android:layout_width=“200dp”
               android:layout_gravity=“center”
               android:layout_height=“200dp”
               android:id=“@+id/qrCodeView”
               android:layout_margin=“@dimen/layout_margin_medium”
               android:scaleType=“centerCrop” />

Generating the QR Bitmap

The following code snippet generates QR bitmap for order identifier.

try {
           val bitMatrix = MultiFormatWriter().encode(orderIdentifier, BarcodeFormat.QR_CODE, 200, 200)
           itemView.qrCodeView.setImageBitmap(BarcodeEncoder().createBitmap(bitMatrix))
       } catch (e: WriterException) {
           e.printStackTrace()
       }

MultiformatWriter encodes the provided string in the format specified in the second parameter of encode function and generates a bitMatrix of specified dimensions in our case orderIdentifier is the string to be encoded and the format is QR_CODE. The last two parameters in the encode method are the width and height respectively for the Image Matrix to be generated. This bitMatrix can be then used to create QR bitmaps, BarcodeEncode() function creates a bitmap from this bitMatrix which is then set on the image View using setImageBitmap method. As a writer exception may occur while encoding string to specified format this whole block of code is put inside a try catch block which catches and logs the WriterException.

References

Continue Reading

Creating Orders in Open Event Android

An Order is generated whenever a user buys a ticket in Open Event Android. It contains all the details regarding the tickets and their quantity, also information regarding payment method and relation to list of attendees, through this blog post we will see how Orders are generated in Open Event Android. Implementing Order system can be divided into following parts

  • Writing model class to serialize/deserialize API responses
  • Creating TypeConverter for Object used in Model class
  • Creating the API interface method
  • Wiring everything together

Model Class

Model class server two purpose –

  • Entity class for storing orders in room
  • Serialize / Deserialize API response

The architecture of the Order Model Class depends upon the response returned by the API, different fields inside the Entity Class defines what different attributes an Order consists of and their data types. Since every Order has a relationship with Event and Attendee we also have to define foreign key relations with them. Given below is the implementation of the Order Class in Open Event Android.

@Type(“order”)
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
@Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = [“id”], childColumns = [“event”], onDelete = ForeignKey.CASCADE)), (ForeignKey(entity = Attendee::class, parentColumns = [“id”], childColumns = [“attendees”], onDelete = ForeignKey.CASCADE))])
data class Order(
       @Id(IntegerIdHandler::class)
       @PrimaryKey
       @NonNull
       val id: Long,
       val paymentMode: String? = null,
       val country: String? = null,
       val status: String? = null,
       val amount: Float? = null,
       val orderNotes: String? = null,
       @ColumnInfo(index = true)
       @Relationship(“event”)
       var event: EventId? = null,
       @Relationship(“attendees”)
       var attendees: List<AttendeeId>? = null
)

 

We are using Jackson for serializing/deserializing JSON response, @Type(“order”) annotation tells jackson that the following object is for key order in json response. Since we are using this as our room entity class we will also have to add a @Entity annotation to this class. Order contains attendee and event id fields which are foreign keys to other entity classes, this also has to be explicitly mentioned while writing the @Entitty annotation as shown in the snippet above . All relationship must be annotated with @Relationship annotation. All the variables serves as attributes in the order table and key name for json conversions.

The fields of this class are the attributes for the Order Table. Payment mode, country, status are all made up of primitive data type and hence require no type convertors whereas we will have to specify type converter for objects like eventId and List<Attendees>

Type Converter

Type Converter allows us to store any custom object type inside room database. Essentially we break down the Object into smaller primitive data types that Object is composed of and which can be stored by room database.

To create a TypeConverter we have to add a @TypeConverter annotation to it, this tells room that this is a special function. For every custom Object, you have to create two different TypeConverter functions. One takes the Object and converts it into primitive data type and the other takes the primitive data type and constructs your custom Object from it. For the Order data class we discussed in the above section we will need two type converters, for EventId and List<Attendee> objects. We will take the example of List<Attendee>

class ListAttendeeIdConverter {
   @TypeConverter
   fun fromListAttendeeId(attendeeIdList: List<AttendeeId>): String {
       val objectMapper = ObjectMapper()
       return objectMapper.writeValueAsString(attendeeIdList)
   }
   @TypeConverter
   fun toListAttendeeId(attendeeList: String): List<AttendeeId> {
       val objectMapper = ObjectMapper()
       val mapType = object : TypeReference<List<AttendeeId>>() {}
       return objectMapper.readValue(attendeeList, mapType)
   }

A type converter shows how we can store an object in the form of primitive data type by performing some operation on it. Here we can see that List<AttendeeId> Object is converted into String (primitive data type) using jackson object mapper and similarly we will have to restore or recreate the List<AttendeeId> Object from the string converted output of the same. The first function fromListAttendeeId deals with converting Object into string type and toListAttendeeId deals with converting string output to List<AttendeeId> type Object.

Not that we have created a TypeConverter for our custom Object type we have to add it to the Open Event Database. This can be done by simply adding it to @TypeConverters list separated by commas as shown below.

@TypeConverters(EventIdConverter::class, EventTopicIdConverter::class, TicketIdConverter::class, AttendeeIdConverter::class, ListAttendeeIdConverter::class)

API Interface Method

Till now we have seen how Order body looks like and how we can store it in room database but we would also need an Order API class which specifies which endpoint to hit and what body and response type it is expecting.

Given below is the placeOrder function which hits the Order endpoint (baseURL/orders), with the body as an Order and returns a Single<Order> as a response. Since we are using retrofit for making network requests endpoint path is simply added inside @Path annotation and the body can be passed in parameters of the function using @Body annotation.

interface OrderApi {
   @POST(“orders”)
   fun placeOrder(@Body order: Order): Single<Order>
}

OrderService is the class which exposes the OrderApi functions and make it available to its ViewModel.

The placeOrder function inside the service class takes Order as body parameter, when this function is called it makes a call to the API function to place an order with the passed parameter the response of which is inserted into the database (caching of Orders) also returns the same value

class OrderService(private val orderApi: OrderApi, private val orderDao: OrderDao) {
   fun placeOrder(order: Order): Single<Order> {
       return orderApi.placeOrder(order)
               .map {
                   orderDao.insertOrder(it)
                   it
               }
   }

To create an Order from the Fragment or Activity one call implement the following function createOrder.

createOrder calls the service layer function placeOrder and subscribes to this observable. Here we are observing on main thread because all the UI related changes has to be done from the main thread. As soon as we subscribe to the function we also sets progress.value = true this allows us to show a progress bar on the UI this is changed to false once the response is received (see doFinally).

You can find the following function in the AttendeeViewModel class in Open Event Android project

fun createOrder(order: Order) {
       compositeDisposable.add(orderService.placeOrder(order)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnSubscribe {
                   progress.value = true
               }.doFinally {
                   progress.value = false
               }.subscribe({
                   message.value = “Order created successfully!”
                   Timber.d(“Success placing order!”)
               }, {
                   message.value = “Unable to create Order!”
                   Timber.d(it, “Failed creating Order”)
               }))
   }

Whenever user fills in details of all the attendees sequence of calls and methods are invoked. Firstly attendees are created for the event with ticket details and the data as provided from the UI. On the successful generation of all the attendees ie when total ticket quantity equals the no of attendees an order object is generated with attendees as the list of attendee previously generated and other details as required, this is then passed to createOrder function which internally interacts with the service layer function to create Order.

Resources

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
  • 1
  • 2
Close Menu