Using routerLink to make result tabs as links

In Susper, it was required that different result sections i.e All, Images,Videos and News should behave as links. Links provide us an advantage of opening the different result tabs in a New Tab by right clicking on it. Also at the same time we wanted that Active tabs must not behave as links. We wanted that tabs should behave in a similar way as Google, Bing and other search engine.In this blog, I will describe how we have achieved this by using routerLink and [hidden] attribute in Angular.

Steps:

1.Adding routerLink attribute in anchor tag.

Now, To make any element as link we must enclose it in anchor tag, therefore we will use <a> tag enclosing the <li> tag to make it a link.

<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'all',mode:'text',nopagechange:'false',append:'false'}"><li [class.active_view]="Display('all')" (click)="docClick()">All</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,mode:'text',nopagechange:'false',append:'false',fq:'url_file_ext_s%3A(png%2BOR%2Bjpeg%2BOR%2Bjpg%2BOR%2Bgif)',resultDisplay:'images'}"><li [class.active_view]="Display('images')" (click)="imageClick()">Images</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'videos',mode:'text',nopagechange:'false',append:'false',fq:'url_file_ext_s%3A(avi%2BOR%2Bmov%2BOR%2Bflv%2BOR%2Bmp4)'}"><li [class.active_view]="Display('videos')" (click)="videoClick()">Videos</li></a>
<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'news',mode:'text',nopagechange:'false',append:'false'}"><li [class.active_view]="Display('news')" (click)="newsClick()">News</li></a>

Now we must navigate to a link on the click of this tabs, for this we can simply use href attribute which we earlier tried to use but there is a disadvantage of using href tag. Using href tab reloads the whole page when we try to navigate to another tab.

Therefore we will use routerLink to achieve the same functionality as Susper is a single page application, where the page should not be reloaded. routerLink navigates to the new url and the component is rendered in place of <router-outlet> without reloading the whole page.

After using routerLink, we will use queryParams attribute to pass routing parameters to a route. Since these parameters will be variable we must use string interpolation to set query parameters. We will use searchdata object and set query parameters according to its value.

Now we are done with making tabs as links. But as of now all tabs will behave a links even if it is an active tab. Therefore we need to hide the link property of any tab when it is active.

2.Hiding the link property of the active tab:

Now we will use to different elements for each tab, one as link and other as normal li element.For hiding the link elements when the tab is active we will use [hidden] attribute in angular. We will just hide the link element when the value of resultDisplay variable is similar to current tab.

<a [routerLink]="['/search']" [queryParams]="{query:this.searchdata.query,start:this.searchdata.start,rows:this.searchdata.rows,resultDisplay:'all',mode:'text',nopagechange:'false',append:'false'}" [hidden]="this.resultDisplay==='all'"><li [class.active_view]="Display('all')" (click)="docClick()">All</li></a>
        <li [class.active_view]="Display('all')" (click)="docClick()" *ngIf="Display('all')">All</li>

For hiding the simple li element we will use *ngIf attribute which will remove the simple li element from the DOM for un active tabs. We might have used *ngIf attribute to hide link elements but rendering a lot of elements on DOM slows down the page and affects performance.

We will follow this for rest of the tabs also and we are done with our changes.

Now we can the inactive tabs will behave as link and the active tabs will not behave as link similar to Google.

Resources

  1. Angular Routing: https://angular.io/guide/router
  2. ngIf and [hidden]: https://stackoverflow.com/questions/39777990/angular2-conditional-display-bind-to-hidden-property-vs-ngif-directive
  3. Angular queryParams: https://stackoverflow.com/a/46008187
Continue ReadingUsing routerLink to make result tabs as links

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 to export the speakers of the event as a CSV file. It returns the URL of the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the above-mentioned URL.

Resources

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

Continue ReadingBottom Navigation Bar in Open Event Android

Disable editing for non-editable skills for non-admin users

As the Skills in SUSI Skill CMS are publicly editable, any user has the access to edit them. Hence, there needed to be a better control over who can edit the Skills in CMS. We needed to implement a feature to allow Admins and higher user roles to change the status of a Skill to non-editable. The subsequent implementation on CMS would require disabling editing for non-editable Skills for non-admin users. This blog post explains how this feature has been implemented in SUSI.AI.

Adding a boolean parameter ‘editable’ to the Skill metadata

We needed to add a boolean parameter in the Skill metadata for each Skill. The boolean parameter is ‘editable’. If its value is true, then it implies that editing should be allowed for that Skill. If it is set to false, then the Skill should not be editable for non-admin users. By default, its value has been set to true for all Skills. This is implemented as follows in the SusiSkill.java file:

    // in the getSkillMetadata() method
  skillMetadata.put("editable", getSkillEditStatus(model, group, language, skillname));

    // declaration of the getSkillEditStatus() method
    public static boolean getSkillEditStatus(String model, String group, String language, String skillname) {
        // skill status
        JsonTray skillStatus = DAO.skillStatus;
        if (skillStatus.has(model)) {
            JSONObject modelName = skillStatus.getJSONObject(model);
            if (modelName.has(group)) {
                JSONObject groupName = modelName.getJSONObject(group);
                if (groupName.has(language)) {
                    JSONObject languageName = groupName.getJSONObject(language);
                    if (languageName.has(skillname)) {
                        JSONObject skillName = languageName.getJSONObject(skillname);

                        if (skillName.has("editable")) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

 

Allowing Admin and higher user roles to change edit status of any Skill

This is facilitated by the endpoint ‘/cms/changeSkillStatus.json’. Its minimum base user role is set to Admin so that only Admins and higher user roles are able to change status of any Skill. A sample API call to this endpoint to change the edit status of any Skill to ‘false’ is as follows:

http://127.0.0.1:4000/cms/changeSkillStatus.json?model=general&group=Knowledge&language=en&skill=aboutsusi&editable=false&access_token=zdasIagg71NF9S2Wu060ZxrRdHeFAx

 

If we want to change the edit status of any Skill to ‘false’, then we need to add the Skill to the ‘skillStatus.json’ file. For this, we need to traverse inside the JSONObject in the ‘skillStatus.json’ file. We need to traverse inside the model, group and language as specified in the query parameters. This is done as follows:

   if(editable.equals("false")) {
       skill_status.put("editable", false);
   }

   JsonTray skillStatus = DAO.skillStatus;

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);

                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);

                    if(editable != null && editable.equals("false")) {
                        skillName.put("editable", false);
                    }
                    else if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }

                    skillStatus.commit();
                    result.put("accepted", true);
                    result.put("message", "Skill status changed successfully.");
                    return new ServiceResponse(result);
                }
            }
        }
    }

 

If we want to change the edit status of any Skill to ‘true’, then we need to remove the Skill from the ‘skillStatus.json’ file. We also need to remove all the empty JSONObjects inside the ‘skillStatus.json’ file, if they are created in the process of removing Skills from it. This is done as follows:

   if (skillStatus.has(model_name)) {
        modelName = skillStatus.getJSONObject(model_name);
        if (modelName.has(group_name)) {
            groupName = modelName.getJSONObject(group_name);
            if (groupName.has(language_name)) {
                languageName = groupName.getJSONObject(language_name);
                if (languageName.has(skill_name)) {
                    skillName = languageName.getJSONObject(skill_name);
                    if(editable != null && editable.equals("true")) {
                        skillName.remove("editable");
                    }
                    if(skillName.length() == 0) {
                        languageName.remove(skill_name);
                        if(languageName.length() == 0) {
                            groupName.remove(language_name);
                            if(groupName.length() == 0) {
                                modelName.remove(group_name);
                                if(modelName.length() == 0) {
                                    skillStatus.remove(model_name);
                                }
                            }
                        }
                    }
                    skillStatus.commit();
                }
            }
        }
    }

 

Disabling editing for non-editable Skills for non-admin users on Skill CMS

For the Skills whose edit status has been set to ‘false’ by the Admins, we need to allow the non-admin users to only be able to view the code of the Skill, and not permit them to change the code and save the changes to the Skill. We need to display a message to the users about the possible reasons. All the code for displaying the message is put in an if() condition as follows:

   if (
      cookies.get('loggedIn') &&
      !this.state.editable &&
      !this.state.showAdmin
    )

 

This is how the Skill edit page for a non-editable Skill would look like for a non-admin user:

For an Admin user, this would look exactly same like an editable Skill page. Admin user would be able to edit and make changes to the Skill code and save the changes.

This is how editing of non-editable Skills have been disabled for non-admin users.

Resources

Continue ReadingDisable editing for non-editable skills for non-admin users

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 the task to get the status of the export task. A sample response is as follows:

{
  "task_url": "/v1/tasks/b7ca7088-876e-4c29-a0ee-b8029a64849a"
}

The user can go to the above-returned URL and check the status of his/her Celery task. If the task completed successfully he/she will get the download URL. The endpoint to check the status of the task is:

and the corresponding response from the server –

{
  "result": {
    "download_url": "/v1/events/1/exports/http://localhost/static/media/exports/1/zip/OGpMM0w2RH/event1.zip"
  },
  "state": "SUCCESS"
}

The file can be downloaded from the above-mentioned URL.

Resources

Continue ReadingOpen Event Server – Export Sessions as CSV File

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 ReadingChanging Profile Image in Open Event Android

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 ReadingHiding Multiple Attendee Recycler for Single Tickets in Open Event Android

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

Continue ReadingGenerating Order QR codes in Open Event Android