Integrating Gravatar and Anonymizing Email Address in Feedback Section

SUSI skills are having a very nice feedback system that allows the user to rate skills from 1-star to 5-star and showing ratings in skills screens. SUSI also allow the user to post feedback about skills and display them. You can check out how posting feedback implemented here and how displaying feedback feature implemented here. To enhance the user experience, we are adding user gravatar in the feedback section and to respect user privacy, we are anonymizing the user email displayed in the feedback section. In this post, we will see how these features implemented in SUSI iOS. Integrating Gravatar - We are showing gravatar of the user before feedback. Gravatar is a service for providing globally-unique avatars. We are using user email address to get the gravatar. The most basic gravatar image request URL looks like this: https://www.gravatar.com/avatar/HASH where HASH is replaced with the calculated hash for the specific email address we are requesting. We are using the MD5 hash function to hash the user’s email address. The MD5 hashing algorithm is a one-way cryptographic function that accepts a message of any length as input and returns as output a fixed-length digest value to be used for authenticating the original message. In SUSI iOS, we have MD5Digest.swift file that gives the hash value of email string. We are using the following method to set gravatar: if let userEmail = feedback?.email { setGravatar(from: userEmail) } func setGravatar(from emailString: String) { let baseGravatarURL = "https://www.gravatar.com/avatar/" let emailMD5 = emailString.utf8.md5.rawValue let imageString = baseGravatarURL + emailMD5 + ".jpg" let imageURL = URL(string: imageString) gravatarImageView.kf.setImage(with: imageURL) } Anonymizing User’s Email Address - Before the implementation of this feature, the user’s full email address was displayed in the feedback section and see all review screen. To respect the privacy of the user, we are now only showing user email until the `@` sign. In Feedback object, we have the email address string that we modify to show until `@` sign by following way: if let userEmail = feedback?.email, let emailIndex = userEmail.range(of: "@")?.upperBound { userEmailLabel.text = String(userEmail.prefix(upTo: emailIndex)) + "..." }   Final Output - Resources - Post feedback for SUSI Skills in SUSI iOS Displaying Skills Feedback on SUSI iOS What is MD5?

Continue ReadingIntegrating Gravatar and Anonymizing Email Address in Feedback Section

Open Event Frontend – Settings Service

This blog illustrates how the settings of a particular user are obtained in the Open Event Frontend web app. To access the settings of the user a service has been created which fetches the settings from the endpoint provided by Open Event Server. Let's see why a special service was created for this. Problem In the first step of the event creation wizard, the user has the option to link Paypal or Stripe to accept payments. The option to accept payment through Paypal or Stripe was shown to the user without checking if it was enabled by the admin in his settings. To solve this problem, we needed to access the settings of the admin and check for the required conditions. But since queryRecord() returned a promise we had to re-render the page for the effect to show which resulted in this code: canAcceptPayPal: computed('data.event.paymentCurrency', function() {     this.get('store').queryRecord('setting', {}) .then(setting => { this.set('canAcceptPayPal', (setting.paypalSandboxUsername || setting.paypalLiveUsername) && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).paypal); this.rerender(); }); This code was setting a computed property inside it and then re-rendering which is bad programming and can result in weird bugs. Solution The above problem was solved by creating a service for settings. This made sense as settings would be required at other places as well. The file was called settings.js and was placed in the services folder. Let me walk you through its code. Extend the default Service provided by Ember.js and initialize store, session, authManager and _lastPromise. import Service, { inject as service } from '@ember/service'; import { observer } from '@ember/object'; export default Service.extend({ store       : service(), session     : service(), authManager : service(), _lastPromise: Promise.resolve(), The main method which fetches results from the server is called _loadSettings(). It is an async method. It queries setting from the server and then iterates through every attribute of the setting model and stores the corresponding value from the fetched result. /** * Load the settings from the API and set the attributes as properties on the service * * @return {Promise<void>} * @private */ async _loadSettings() { const settingsModel = await this.get('store').queryRecord('setting', {}); this.get('store').modelFor('setting').eachAttribute(attributeName => {   this.set(attributeName, settingsModel.get(attributeName)); }); }, The initialization of the settings service is handled by initialize(). This method returns a promise. /** * Initialize the settings service * @return {*|Promise<void>} */ initialize() { const promise = this._loadSettings(); this.set('_lastPromise', promise); return promise; } _authenticationObserver observes for changes in authentication changes and reloads the settings as required. /** * Reload settings when the authentication state changes. */ _authenticationObserver: observer('session.isAuthenticated', function() { this.get('_lastPromise')   .then(() => this.set('_lastPromise', this._loadSettings()))   .catch(() => this.set('_lastPromise', this._loadSettings())); }), The service we created can be directly used in the app to fetch the settings for the user. To solve the Paypal and Stripe payment problem described above, we use it as follows: canAcceptPayPal: computed('data.event.paymentCurrency', 'settings.paypalSandboxUsername', 'settings.paypalLiveUsername', function() { return (this.get('settings.paypalSandboxUsername') || this.get('settings.paypalLiveUsername')) && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).paypal; }), canAcceptStripe: computed('data.event.paymentCurrency', 'settings.stripeClientId', function() { return this.get('settings.stripeClientId') && find(paymentCurrencies, ['code', this.get('data.event.paymentCurrency')]).stripe; }), Thus, there is no need to re-render the page and dangerously set the property…

Continue ReadingOpen Event Frontend – Settings Service

Open Event Server – Export Speakers as CSV File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested. The organizer can see all the speakers in a very detailed view in the event management dashboard. He can see the statuses of all the speakers. The possible statuses are pending, accepted and rejected. He/she can take actions such as editing/viewing speakers. If the organizer wants to download the list of all the speakers as a CSV file, he or she can do it very easily by simply clicking on the Export As CSV button in the top right-hand corner. Let us see how this is done on the server. Server side - generating the Speakers CSV file Here we will be using the csv package provided by python for writing the csv file. import csv We define a method export_speakers_csv which takes the speakers to be exported as a CSV file as the argument. Next, we define the headers of the CSV file. It is the first row of the CSV file. def export_speakers_csv(speakers):   headers = ['Speaker Name', 'Speaker Email', 'Speaker Session(s)',              'Speaker Mobile', 'Speaker Bio', 'Speaker Organisation', 'Speaker Position'] A list is defined called rows. This contains the rows of the CSV file. As mentioned earlier, headers is the first row. rows = [headers] We iterate over each speaker in speakers and form a row for that speaker by separating the values of each of the columns by a comma. Here, every row is one speaker. As a speaker can contain multiple sessions we iterate over each session for that particular speaker and append each session to a string. ‘;’ is used as a delimiter. This string is then added to the row. We also include the state of the session - accepted, rejected, confirmed. The newly formed row is added to the rows list. for speaker in speakers:   column = [speaker.name if speaker.name else '', speaker.email if speaker.email else '']   if speaker.sessions:       session_details = ''       for session in speaker.sessions:           if not session.deleted_at:               session_details += session.title + ' (' + session.state + '); '       column.append(session_details[:-2])   else:       column.append('')   column.append(speaker.mobile if speaker.mobile else '')   column.append(speaker.short_biography if speaker.short_biography else '')   column.append(speaker.organisation if speaker.organisation else '')   column.append(speaker.position if speaker.position else '')   rows.append(column) rows contains the contents of the CSV file and hence it is returned. return rows We iterate over each item of rows and write it to the CSV file using the methods provided by the csv package. with open(file_path, "w") as temp_file:   writer = csv.writer(temp_file)   from app.api.helpers.csv_jobs_util import export_speakers_csv   content = export_speakers_csv(speakers)   for row in content:       writer.writerow(row) Obtaining the Speakers CSV file: Firstly, we have an API endpoint which starts the task on the server. GET - /v1/events/{event_identifier}/export/speakers/csv Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server…

Continue ReadingOpen Event Server – Export Speakers as CSV File

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 Simplified Coding Bottom Navigation Bar- https://www.simplifiedcoding.net/bottom-navigation-android-example/ Google Developers Bottom Naviagtion Bar - https://developer.android.com/reference/com/google/android/material/bottomnavigation/BottomNavigationView

Continue ReadingBottom Navigation Bar in Open Event Android

Open Event Server – Export Sessions as CSV File

FOSSASIA‘s Open Event Server is the REST API backend for the event management platform, Open Event. Here, the event organizers can create their events, add tickets for it and manage all aspects from the schedule to the speakers. Also, once he/she makes his event public, others can view it and buy tickets if interested. The organizer can see all the sessions in a very detailed view in the event management dashboard. He can see the statuses of all the sessions. The possible statuses are pending, accepted, confirmed and rejected. He/she can take actions such as accepting/rejecting the sessions. If the organizer wants to download the list of all the sessions as a CSV file, he or she can do it very easily by simply clicking on the Export As CSV button in the top right-hand corner. Let us see how this is done on the server. Server side - generating the Sessions CSV file Here we will be using the csv package provided by python for writing the csv file. import csv We define a method export_sessions_csv which takes the sessions to be exported as a CSV file as the argument. Next, we define the headers of the CSV file. It is the first row of the CSV file. def export_sessions_csv(sessions):   headers = ['Session Title', 'Session Speakers',              'Session Track', 'Session Abstract', 'Created At', 'Email Sent'] A list is defined called rows. This contains the rows of the CSV file. As mentioned earlier, headers is the first row. rows = [headers] We iterate over each session in sessions and form a row for that session by separating the values of each of the columns by a comma. Here, every row is one session. As a session can contain multiple speakers we iterate over each speaker for that particular session and append each speaker to a string. ‘;’ is used as a delimiter. This string is then added to the row. The newly formed row is added to the rows list. for session in sessions:   if not session.deleted_at:       column = [session.title + ' (' + session.state + ')' if session.title else '']       if session.speakers:           in_session = ''           for speaker in session.speakers:               if speaker.name:                   in_session += (speaker.name + '; ')           column.append(in_session[:-2])       else:           column.append('')       column.append(session.track.name if session.track and session.track.name else '')       column.append(strip_tags(session.short_abstract) if session.short_abstract else '')       column.append(session.created_at if session.created_at else '')       column.append('Yes' if session.is_mail_sent else 'No')       rows.append(column) rows contains the contents of the CSV file and hence it is returned. return rows We iterate over each item of rows and write it to the CSV file using the methods provided by the csv package. writer = csv.writer(temp_file) from app.api.helpers.csv_jobs_util import export_sessions_csv content = export_sessions_csv(sessions) for row in content:   writer.writerow(row) Obtaining the Sessions CSV file: Firstly, we have an API endpoint which starts the task on the server. GET - /v1/events/{event_identifier}/export/sessions/csv Here, event_identifier is the unique ID of the event. This endpoint starts a celery task on the server to export the sessions of the event as a CSV file. It returns the URL of…

Continue ReadingOpen Event Server – Export Sessions as CSV File

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 Android. The 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…

Continue ReadingDisplaying Events In Open Event Android

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)…

Continue ReadingSaving Current State when Redirected to Login Fragment in Open Event Android

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 Zxing Library:  https://github.com/zxing/zxing Google Developers Documentation: https://developer.android.com/reference/android/graphics/Bitmap

Continue ReadingGenerating Order QR codes in Open Event Android

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…

Continue ReadingCreating Orders in Open Event Android

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…

Continue ReadingAdding Multiple Attendees inside an Order in Open Event Android