Hiding the BottomNavigationView bar in Fragments in the Open Event Android App

The Open Event Android app has a lot of fragments where the BottomNavigationView bar is not required. Hiding the bar is quite a clever task to handle as it can’t be simply hidden by changing its visibility (It’s not an activity). So, this blog will illustrate about how the BottomNavigationView bar is indirectly hidden in some Fragments.

We will basically change the layout to be replaced. Let us start by making the bar invisible in EventDetailsFragment. Let us observe our initial code. Below is the previous code of changing the fragments using supportFragmentManager. The below code actually represents an RecyclerViewClickListener listener of a RecyclerView. In this case it represents the RecyclerView showing all the events in the Main Page.

val recyclerViewClickListener = object : RecyclerViewClickListener {

  override fun onClick(eventID: Long) {

      val fragment = EventDetailsFragment()

      val bundle = Bundle()

      bundle.putLong(EVENT_ID, eventID)

      fragment.arguments = bundle

      activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.frameContainer, fragment)?.addToBackStack(null)?.commit()

  }

}

The above code clearly mentions that the incoming fragment will replace the view with id frameContainer. Let us look at the previous xml code of activity_main.xml.

<android.support.design.widget.CoordinatorLayout

>
  <LinearLayout

      android:layout_width=“match_parent”

      android:layout_height=“match_parent”

      android:orientation=“vertical”>
      <FrameLayout

          android:id=“@+id/frameContainer”

          

           />
      <android.support.design.widget.BottomNavigationView

          android:id=“@+id/navigation”

          …

           />
  </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

Here we can clearly see that the Fragment will be inserted directly into the FrameLayout. The BottomNavigationView bar will always be present. There is only one way to prevent this and that is by replacing the whole layout! Below lies the latest code.

val recyclerViewClickListener = object : RecyclerViewClickListener {

  override fun onClick(eventID: Long) {

      val fragment = EventDetailsFragment()

      val bundle = Bundle()

      bundle.putLong(EVENT_ID, eventID)

      fragment.arguments = bundle

      activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.rootLayout, fragment)?.addToBackStack(null)?.commit()

  }

}

We clearly observe that frameContainer has been replaced by rootLayout. Our major problem has been solved. But by doing this, we now face an error of overlapping contents. The previous fragments contents are always visible. For example, The Events page is visible whenever the EventDetailsFragment is opened for any event. This clearly shows that the incoming page lacks a background color or an image. This is because the previous fragment is only visible but not clickable!

<android.support.design.widget.CoordinatorLayout

>
  <LinearLayout

      android:layout_width=“match_parent”

      android:layout_height=“match_parent”

      android:orientation=“vertical”>
      <FrameLayout

          android:id=“@+id/frameContainer”

          />
      <android.support.design.widget.BottomNavigationView

          android:id=“@+id/navigation”

          />
  </LinearLayout>

</android.support.design.widget.CoordinatorLayout>

So we clearly observe that we have set the background to a white colour, thus not letting previous fragments contents to be visible. This is how the BottomNavigationView bar is simply hidden in some Fragments in the app.

Additional Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, Fragments

Implementing Custom Forms viewing under Attendee Details in Open Event Android App

This blog will illustrate about how order custom forms are viewed for events for which they are required in Open Event Android. These forms help the event organizer in gathering more information from the user or attendee. For example, in an event X, the organizer might want to know the state from which the user is from. Let’s head onto the code.

1. Create the CustomForm model

The custom form model is to be created as per the API.

@Type(“custom-form”)
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
data class CustomForm(
@Id(IntegerIdHandler::class)
val id: Long,
val form: String,
val fieldIdentifier: String,
val type: String,
val isRequired: Boolean? = false,
val isIncluded: Boolean? = false,
val isFixed: Boolean? = false,
val ticketsNumber: Int? = null,
@Relationship(“event”)
var event: EventId? = null
)

We can observe that there are some special fields in the model. For example, field “field identifier”, identifies the extra field of an attendee to to be taken in while making an attendee POST request. The field “isRequired” specifies whether the field is required to be taken.

2. Add the function to get custom forms from API

    @GET(“events/{id}/custom-forms”)
fun getCustomFormsForAttendees(@Path(“id”) id: Long): Single<List<CustomForm>>

3. Modify the code of Attendee Fragment

   attendeeFragmentViewModel.getCustomFormsForAttendees(eventId.id)

           attendeeFragmentViewModel.forms.observe(this, Observer {
               it?.let {
                   fillInformationSection(it)
                   if (!it.isEmpty()) {
                       rootView.moreAttendeeInformation.visibility = View.VISIBLE
                   }
               }
               rootView.register.isEnabled = true
           })

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

               val attendees = ArrayList<Attendee>()
               ticketIdAndQty?.forEach {
                   for (i in 0..it.second) {
                       val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
                               …
                               city = getAttendeeField(“city”),
                               address = getAttendeeField(“address”),
                               state = getAttendeeField(“state”),
                               …
                       attendees.add(attendee)
                   }
               }

2. Create two more methods

We create two more methods which help in filling the area, whether it should be a text view or image view. As we are only taking type of text, we are creating EditText for all fields!

   private fun fillInformationSection(forms: List<CustomForm>) {
val layout = rootView.attendeeInformation

for (form in forms) {
if (form.type == “text”) {
val inputLayout = TextInputLayout(context)
val editTextSection = EditText(context)
editTextSection.hint = form.fieldIdentifier.capitalize()
inputLayout.addView(editTextSection)
inputLayout.setPadding( 0, 0, 0, 20)
layout.addView(inputLayout)
identifierList.add(form.fieldIdentifier)
editTextList.add(editTextSection)
}
}
}

fun getAttendeeField(identifier: String): String {
val index = identifierList.indexOf(identifier)
return if (index == -1) “” else index.let { editTextList[it] }.text.toString()
}

Thus, we have successfully implemented Custom Forms viewing in the app.

Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, Custom Forms

Adding a no search results message

The Open Event Android app allows users to search any event. The results are fetched from the server and the data is displayed in the recycler view. But, how do we handle situations when there are no results found? This is where we arrive at implementing a No search results message in the Open Event android app.

First of all, we will create a search icon vector drawable. We can make our own vector drawable in Android Studio.

Right clicking on any folder, we then select new Vector Drawable option. After that, a dialogue will open where we can select our preferred icon. In this case, we select search icon. After that, on clicking next, we successfully complete the process!

Coming back to the actual matter, we create a layout containing the message and the drawable.

We will create a LinearLayout containing an Image View and a TextView. We add this layout in fragment_search.

<LinearLayout

  android:id=“@+id/noSearchResults”

  android:layout_width=“match_parent”

  android:layout_height=“wrap_content”

  android:orientation=“vertical”

  android:gravity=“center”

  android:visibility=“gone”

  android:layout_gravity=“center”>

  <ImageView

      android:layout_width=“@dimen/item_image_view”

      android:layout_height=“@dimen/item_image_view”

      app:srcCompat=“@drawable/ic_search_grey_24dp”/>

  <TextView

      android:layout_width=“wrap_content”

      android:layout_height=“wrap_content”

      android:textSize=“@dimen/text_size_medium”

      android:text=“@string/no_search_results”/>

</LinearLayout>

As we can clearly observe, the layout will not be visible at first. We have to make the layout visible only when there are no search results found.

Heading onto the backend code. We move onto the SearchFragment.kt file. There we will handle the visibility of the layout.

searchViewModel.events.observe(this, Observer {

  it?.let {

      eventsRecyclerAdapter.addAll(it)

      eventsRecyclerAdapter.notifyDataSetChanged()

      handleVisibility(it)

  }

  Timber.d(“Fetched events of size %s”, eventsRecyclerAdapter.itemCount)

})

We observe that we have added a line of code handleVisibility(it). This is a function which will take care of events fetched. Let’s head onto the code of that function.

fun handleVisibility(events: List<Event>){

  rootView.noSearchResults.visibility = if (events.isEmpty()) View.VISIBLE else View.GONE

}

We already know the id of the layout, that is noSearchResults. So, in the above function, we are setting the visibility of the layout as visible whenever the events list is empty. This satisifes our logic completely. If it is the other case around, we set the visibility of the layout as View.GONE, which means we are making the layout completely invisible.

Thus, this is how we are able to achieve a better UX effect whenever there are no results found in the Open Event android app.

Additional Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, CustomTabsIntent

Adding settings to the Open Event Android app

The Open Event Android app has a lot of fragments and some activities. But, when it comes to settings of an app, we need not create a whole new fragment, with different text views (clickable), with multiple sections. This is where a specific fragment called PreferenceFragment comes into place. This blog will illustrate about how we have implemented Settings in the Open Event android app by using a Preference Fragment.

Initially, we need to add the preference dependency in the app.gradle file.

implementation “com.takisoft.fix:preference-v7:${versions.support_lib}.0″

After that, we need to set up our settings layout. We create an xml file. Let us head into the code below.

<?xml version=“1.0” encoding=“utf-8”?>

<PreferenceScreen xmlns:android=“http://schemas.android.com/apk/res/android” >

  <PreferenceCategory

      android:title=“@string/about_settings” >

      <Preference

          android:key=“@string/key_rating”

          android:title=“@string/rating_settings” />

      <Preference

          android:key=“@string/key_suggestion”

          android:title=“@string/suggestion_settings” />

  </PreferenceCategory>

  <PreferenceCategory

      android:title=“@string/profile” >

      <Preference

          android:key=“@string/key_profile”

          android:title=“@string/logged_in_account” />

  </PreferenceCategory>

  <PreferenceCategory

      android:title=“@string/app_name_capital” >

      <Preference

          android:key=“@string/key_version”

          android:title=“@string/version_settings” />

  </PreferenceCategory>

</PreferenceScreen>

Thus a settings layout is a Preference Screen. Inside a preference screen, we can put containers like PreferenceCategories. Inside a preference category, preferences are used. A preference contains a key and a title. The key is used in the actual backend code to refer to the Preference. We can perform actions if the Preference is clicked, by referring to that key. The above layout looks exactly like the feature image!

Let us dive into the backend part. We will observe that a PreferenceFragmentCompat will have methods similar to a Fragment. For example, the method onCreatePreferencesFix is a method overriden in a PreferenceFragmentCompat. In this method, we set the layout, which the Fragment will use. We can also set text details to any view using keys in this method.

override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {

  // Load the preferences from an XML resource

  setPreferencesFromResource(R.xml.settings, rootKey)

  val activity =  activity as? AppCompatActivity

  activity?.supportActionBar?.setDisplayHomeAsUpEnabled(true)

  activity?.supportActionBar?.title = “Settings”

  setHasOptionsMenu(true)

  //Set Email

  email = arguments?.getString(EMAIL)

  preferenceScreen.findPreference(resources.getString(R.string.key_profile)).summary = email

  //Set Build Version

  preferenceScreen.findPreference(resources.getString(R.string.key_version)).title = “Version ” + BuildConfig.VERSION_NAME

}

After this, we need to implement another method called onPreferenceTreeClick. This method is useful and is helpful for us to perform any actions whenever any preference is clicked! The below code demonstrates that.

override fun onPreferenceTreeClick(preference: Preference?): Boolean {

  if (preference?.key == resources.getString(R.string.key_rating)) {

      //Opens our app in play store

      startAppPlayStore(activity?.packageName.nullToEmpty())

      return true

  }

  if (preference?.key == resources.getString(R.string.key_suggestion)) {

      //Links to suggestion form

      context?.let {

          Utils.openUrl(it, FORM_LINK)

      }

      return true

  }

  if (preference?.key == resources.getString(R.string.key_profile)) {

      //Logout Dialog shown

      showDialog()

      return true

  }

  return false

}

Thus, we have our SettingsFragment. But, it’s not totally complete. We need to add a preference theme in styles.xml. Let us observe the code.

<style name=“AppTheme” parent=“@style/Theme.Preference”>

  <!– Customize your theme here. –>

  <item name=“colorPrimary”>@color/colorPrimary</item>

  <item name=“colorPrimaryDark”>@color/colorPrimaryDark</item>

  <item name=“colorAccent”>@color/colorAccent</item>

</style>

<style name=“Theme.Preference” parent=“@style/PreferenceFixTheme.Light.DarkActionBar”>

  <item name=“preferenceCategory_marginBottom”>0dp</item>

  <item name=“android:dividerHeight”>1dp</item>

</style>

So we see that we are changing the parent theme of AppTheme to Theme.Preference. The theme Theme.Preference is stated below the AppTheme. It has a parent theme of PreferenceFixTheme.Light.DarkActionBar. Thus all preference themes need to have a parent of the form PreferenceFixTheme. (…). This is how the Settings screen is implemented in the Open Event android app

Additional Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, Settings

Integrate static maps API in an android project

This blog article will illustrate about how an event is located on a map in the Open Event Android app. The app found it necessary to have a feature where users will be able to view the location of the event they are interested in. So we, at Open Event Android took a different approach to map out the location. Let us delve into that !

Google provides us with two ways to map out a location in an android app. One requires us to implement the GoogleMaps Sdk, which is a quite a tiresome process. But the other is a quite less popular way and it doesn’t receive enough recognition. It is the static maps API approach.

This API by Google had been released quite a few years ago and is seemingly one of the most useful and interesting APIs among all of them. So the question is, how does this fit in ?

It is a simple API to use. We provide latitude and longitude of a place. The API then sends us an image response which shows us exactly where the place is located in a world map. Quite Unique!

Let us run through the steps about how it is implemented in the Open Event Android app.

The project uses Picasso to load images as it has a cache feature, which basically means it stores the image as long as the app is running. To start with, the code below shows how to load an image.

//load image

Picasso.get()

      .load(loadMap(event))

      .placeholder(R.drawable.ic_map_black_24dp)

      .into(rootView.image_map)

This will seem to a bit complex at first, but lets just head into it steadily.

.load() function is used to load an image from a given URL. .placeholder() is used to provide the view with a placeholder image if the image hasn’t been loaded properly. .into() is used to load the image ( or the placeholder ) into the view.

We can see that in the .load() function, we are using another function .loadMap(). Let us go through the .loadMap() function

fun loadMap(event: Event): String{

  //location handling

  val mapUrlInitial = “https://maps.googleapis.com/maps/api/staticmap?center=”

  val mapUrlProperties = “&zoom=12&size=1200×390&markers=color:red%7C”

  val mapUrlMapType = “&markers=size:mid&maptype=roadmap”
  val latLong: String = “” +event.latitude + “,” + event.longitude
  return mapUrlInitial + latLong + mapUrlProperties + latLong + mapUrlMapType

}

The .loadMap() function has many declared variables. This is the heart of the whole process.

So what is required for the static maps API to give us an image is that we make an http request with a given url, for which an image response (URL) is received. Let us run through the meaning and utility of these variables. Yes, all of them have a completely different meaning!

The mapUrlInitial variable is always the same while making an API call. It has a query of center ( ?center ) which specifies that we want the location to be centered in the map.

The mapUrlProperties variable contains a string where you control the actual zooming of the image response you will get, the size ofthe image and the color of the marker which will point out our place.

The mapUrlMapType variable is a string where you can actually determine the marker size you want and the type of the map. We are using a roadtype map in the app.

Finally latLong is a string which concatenates the latitude and the longitude of the place we want to pinpoint!

We then concatenate all of these strings to form a feasible Url. The Url is then loaded as we have seen above, in the Picasso code. One thing we can notice is that an event object is always required for all of this to happen, because we are able to fetch the position details using the event object! Final Code:-

fun loadMap(event: Event): String{

  //location handling

  val mapUrlInitial = “https://maps.googleapis.com/maps/api/staticmap?center=”

  val mapUrlProperties = “&zoom=12&size=1200×390&markers=color:red%7C”

  val mapUrlMapType = “&markers=size:mid&maptype=roadmap”
  val latLong: String = “” +event.latitude + “,” + event.longitude
  return mapUrlInitial + latLong + mapUrlProperties + latLong + mapUrlMapType

}
//load image

Picasso.get()

      .load(loadMap(event))

      .placeholder(R.drawable.ic_map_black_24dp)

      .into(rootView.image_map)

After all this doing, we are finally able to receive the static image perfectly.

References

Tags: GSoC18, FOSSASIA, Open Event, Android, Static Maps API

Implementing Custom Tab Viewing on click of an Organizer link in Open Event Android

The Open Event Android app shows a list of organizer social links in the Event Details section. The API fetches the type of social link and the link to the page. For example, if a facebook icon based link is clicked, the default browser will open the facebook page of the Organizer. Our aim was to open the page in the app itself so that the user won’t have to juggle between two different apps. This blog will thus illustrate about how the implementation of a CustomTab is done.

We will move onto the SocialLinkViewHolder file. There we will remove the code for which an explicit intent used to be fired everytime a SocialLink was clicked. We initially add the dependency to the app.gradle file.

implementation “com.android.support:customtabs:27.1.1”

We can clearly observe that custom tabs is supported by the Support library of Android.

We add another field in SocialLinksViewHolder model class. This field will be a Context type field. context will be required in future steps.

class SocialLinksViewHolder(itemView: View, private var context: Context) : RecyclerView.ViewHolder(itemView) {

}

Our next step is to change the overridden function  onCreateViewHolder in the the SocialLinks RecyclerAdapter class. We will now return both the view and the parent context in the method. We return parent context, as it is required in the ViewHolder.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SocialLinksViewHolder {

  val view = LayoutInflater.from(parent.context).inflate(R.layout.item_social_link, parent, false)

  return SocialLinksViewHolder(view, parent.context)

}

We now declare the function showCustomTab() in the bind function of the SocialLinkViewHolder class. Error will be resolved as we still haven’t initialised the function showCustomTab(). This function will be fired only when the link is clicked. That’s why we are firing the function in the onClickListener of itemView

fun bind(socialLink: SocialLink) {

 …

 …
  itemView.setOnClickListener{

      setUpCustomTab(context, socialLink.link)

  }

}

Heading onto initialising the function showCustomTab(). The below code represents the function.

private fun setUpCustomTab(context: Context, url: String) {

  var finalUrl = url

  if (!url.startsWith(“http://”) && !url.startsWith(“https://”)) {

      finalUrl = “http://$url

  }
  CustomTabsIntent.Builder()

          .setToolbarColor(ContextCompat.getColor(context, R.color.colorPrimaryDark))

          .setCloseButtonIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_arrow_back_white_cct_24dp))

          .setStartAnimations(context, R.anim.slide_in_right, R.anim.slide_out_left)

          .setExitAnimations(context, R.anim.slide_in_left, R.anim.slide_out_right)

          .build()

          .launchUrl(context, Uri.parse(finalUrl))

}

Breaking the function into two parts. Firstly, we are verifying the validity of the Url. We are appending http:// to the Url if the Url is not present like that. We skip it, if it is indeed a valid Url

var finalUrl = url

  if (!url.startsWith(“http://”) && !url.startsWith(“https://”)) {

      finalUrl = “http://$url

  }

Then, comes the actual part of the CustomTabsIntent builder. We create a Chrome CustomTab by setting properties regarding toolbar colour, close-button icon, starting and ending animations!

  CustomTabsIntent.Builder()

          .setToolbarColor(ContextCompat.getColor(context, R.color.colorPrimaryDark))

          .setCloseButtonIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_arrow_back_white_cct_24dp))

          .setStartAnimations(context, R.anim.slide_in_right, R.anim.slide_out_left)

          .setExitAnimations(context, R.anim.slide_in_left, R.anim.slide_out_right)

          .build()

          .launchUrl(context, Uri.parse(finalUrl))

setToolbarColor is used to set the toolbar colour of the intent. In this case it was our colourPrimaryDark. setCloseButtonIcon() takes a close icon Bitmap as input. setStartAnimations() takes care of the animation which will occur when the Tab is opened.

build will build the CustomTabIntent and launchUrl will launch the TabIntent with the url being finalUrl.

Thus, we can are clearly able to achieve the feat of showing the web page in the app itself using a CustomTab.

Additional Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, CustomTabsIntent

Show number of tickets in Orders Under User Fragment

This blog will illustrate about how we, at Open Event Android, have implemented the way to show the number of tickets of every type of ticket bought by the user in Orders Under User fragment itself!

1. Modify ordersUnderUser function in Order API

The ordersUnderUser function in Order API doesn’t include attendees. So, if we we need to show the number of attendees which is basically the number of tickets, we have to include all the attendee ids.

@GET(“/v1/users/{userId}/orders?filter=[{\”name\”:\”status\”,\”op\”:\”eq\”,\”val\”:\”completed\”}]&include=event,attendees&fields[attendees]=id”)

   fun ordersUnderUser(@Path(“userId”) userId: Long): Single<List<Order>>

1. Modify the orderUnderUser function in viewmodel

Now, we have to modify the OrderViewModel where we will make changes in the orderUnderUser function. We create a LiveData of attendees number which will contain a list of attendee ids received from the order.

val attendeesNumber = MutableLiveData<ArrayList<Int>>()

Then, in the orderUnderUser function, we add a line of code of setting the value of the LiveData.

This basically takes the orders as a list and as List<AttendeeId> is a variable inside the object of Order, we map out the same ArrayList full of attendee ids!

           }.subscribe({

               order = it

               attendeesNumber.value = it.map { it.attendees?.size } as ArrayList<Int>

               val query = buildQuery(it)

               if (idList.size != 0)

                   eventsUnderUser(query)

               else

                   progress.value = false

           }

2. Modify OrdersUnderUserFragment code

We add a observer in the Fragment file. The observer will fire up whenever there is a value received for attendeesNumber. It will fire up the function setAttendeeNumber in ordersRecyclerAdapter.

ordersUnderUserVM.attendeesNumber.observe(this, Observer {

   it?.let {

       ordersRecyclerAdapter.setAttendeeNumber(it)

   }

})

3. Modify OrdersRecyclerAdapter code

We add a variable called attendeesNumber in the file. Another function called setAttendeeNumber is also made to set the Attendee number. We then proceed to bind the number to the view holder.

var attendeesNumber =  ArrayList<Int>()

fun setAttendeeNumber(number: ArrayList<Int>) {

   attendeesNumber = number

}

override fun onBindViewHolder(holder: OrdersViewHolder, position: Int) {

   holder.bind(eventAndOrderIdentifier[position].first, clickListener, eventAndOrderIdentifier[position].second, attendeesNumber[position])

}

4. Modify the ViewHolder of an Order

We create a conditional which thus sets up the message of “See N Tickets” or “See 1 Ticket” based on the ticket number.

if (attendeesNumber == 1) {

   itemView.ticketsNumber.text = “See ${attendeesNumber} Ticket”

} else {

   itemView.ticketsNumber.text = “See ${attendeesNumber} Tickets”

}

Thus, this was how, tickets number viewing was implemented in the app.

References

Tags: GSoC18, FOSSASIA, Open Event, Android, Number of Tickets

Implementing Forgot Password Feature in the Open Event Android app

This blog will illustrate about how forgot password feature is implemented in the Open Event Android app. This feature is quite necessary for users as the user might forget his or her password whenever he needs to login. Thus, this feature utilises only the email of the user. When clicked on “I forgot my password” option, the user is sent an email to reset his/her password!

1. Create the RequestToken, Email and RequestToken response models

The first step is to create all required models. We actually send a POST request to the server and the body of it contains the RequestToken model. The response received is contained in the RequestToken  response model. We create an Email model for our use.

RequestToken class:-

data class RequestToken(val data: Email)

Email class:-

data class Email(val email: String)

RequestTokenResponse class:-

data class RequestTokenResponse(val message: String)

2. Show the option only when correct Email Id is entered.

We create a verification method to verify the email. The app will only send a POST request using a correct email. This method is present in the ViewModel.

fun showForgotPassword(email: String): Boolean {

   if (email.isNotEmpty() && Patterns.EMAIL_ADDRESS.matcher(email).matches()) {

       return true

   }

   return false

}

2. Update the Login Fragment file.

We add an onclicklistener to the forgot password option. This will call a function in the ViewModel. The function is sendResetPasswordEmail which utilises the actual email of the user entered.

rootView.forgotPassword.setOnClickListener {

   loginActivityViewModel.sendResetPasswordEmail(email.text.toString())

}

3. Update the ViewModel code

We create the sendResetPasswordEmail method. Below code represents that.

fun sendResetPasswordEmail(email: String) {

   compositeDisposable.add(authService.sendResetPasswordEmail(email)

           .subscribeOn(Schedulers.io())

           .observeOn(AndroidSchedulers.mainThread())

           .doOnSubscribe {

               progress.value = true

           }.doFinally {

               progress.value = false

           }.subscribe({

               requestTokenSuccess.value = verifyMessage(it.message)

           }, {

               error.value = “Email address not present in server. Please check your email”

           }))

}

3. Create an observer for the requestTokenSuccess variable in the Fragment.

Thus, the final task at hand is to create an observer to observe the vaue of requestTokenSuccess variable. Once the variable is set to a message “Email Sent”, we can clearly notify the user that the email is sent perfectly! (The feature image represents this)

Thus, this is how we have implemented the forgot password feature in the app.

References

Tags: GSoC18, FOSSASIA, Open Event, Android, Forgot Password

Adding Acknowledgements section in the Settings of the app

The Open Event Android app uses innumerable open source softwares. Fortunately, android allows us to give credit to each OSS (Open Source Software). We can do this just by writing a few lines of code. In this blog we will illustrate about how we are showing a list of acknowle- dgements in the Open Event Android app.

First of all we will add all the dependencies required. In the app level gradle file, we will add the plugin.

apply plugin: ‘com.google.gms.oss.licenses.plugin’

We then add the implementation of oss licences in the app level file of build.gradle

implementation ‘com.google.android.gms:play-services-oss-licenses:16.0.0’

After that we add the classpath to the project level gradle file in dependencies.

classpath “com.google.gms:oss-licenses:0.9.2”

The google() function must be added in maven for this to work. So, after adding all these dependencies, we click the Sync button in Android Studio. We wait for the project to refresh.

In our Settings Fragment file, we add another preference called “Acknowledgements”. Let us head into the code below. In the SettingsFragment file.

if (preference?.key == resources.getString(R.string.key_acknowledgements)) {

startActivity(Intent(context, OssLicensesMenuActivity::class.java))

return true

}

As we can see, we have added a conditional in the onPreferenceClickListener. If the user clicks on a preference whose key is “key_acknowledgements”, an OssLicensesMenyActivity opens in the app. This is a predefined activity and is only available after we have successfully integrated oss licences. Heading onto some xml code for the feature. We move onto settings.xml file.

<Preference

android:key=”@string/key_acknowledgements”

android:title=”@string/acknowledgements_settings” />

This section is present under the main PreferenceScreen. It is nested and is actually present under a PreferenceCategory named “About”. The final thing we do to complete the process successfully is store our strings in the strings.xml file.

<string name=”key_acknowledgements”>key_acknowledgements</string>

<string name=”acknowledgements_settings”>Acknowledgements</string>

Thus, this is how we are able to show the user a list of acknowledgements successfully in the Open Event android app.

Additional Resources

Tags: GSoC18, FOSSASIA, Open Event, Android, OSS

Show social links in open event android

This blog will illustrate about how social links are are cached in the Open Event Android Project. We needed social link viewing in the app so that the User is able to reach out to the organizer in a much better way. After that we needed caching to store the social links in our database. Let’s head to the code.

1. Change the social link model

The first step is to change the social link model. We are caching social links using the Room Database. So, we need to add the annotation @Entity to the top of the model to represent the model as a table of type SocialLink in the database. We then add the annotation @PrimaryKey to the id field as every table in a room database requires a primary key for reference.

Social link model code :-

@Type(“social-link”)

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)

@Entity

data class SocialLink(

       @Id(IntegerIdHandler::class)

       @PrimaryKey

       val id: Int,

       val link: String,

       val name: String

)

2. Create the SocialLinksDao file

This file lists all the possible methods by which the entity in a database can be reached. These methods help in fetching information from the database, as well as updating information in the database. We implement three methods, insertSocialLinks(), deleteAll() and getAllSocialLinks(). insertSocialLinks() helps us in putting social links in the database. deleteAll() helps in deleting all social links from the database. getAllSocialLinks() helps in fetching all social links of a specific eventId

@Dao

interface SocialLinksDao {

   @Insert(onConflict = OnConflictStrategy.REPLACE)

   fun insertSocialLinks(socialLinks: List<SocialLink>)

   @Query(“DELETE FROM SocialLink”)

   fun deleteAll()

   @Query(“SELECT * from SocialLink WHERE event = :eventId”)

   fun getAllSocialLinks(eventId: Long): Flowable<List<SocialLink>>

}

We receive Social Links in the form of a Flowable while fetching from database. As Flowable cannot handle errors correctly, doOnFinally() will not work and thus doOnNext() is used in the SocialLinksViewModel file.

fun loadSocialLinks(id: Long) {

   compositeDisposable.add(socialLinksService.getSocialLinks(id)

           .subscribeOn(Schedulers.io())

           .observeOn(AndroidSchedulers.mainThread())

           .doOnSubscribe({

               progress.value = true

           }).doOnNext({

               progress.value = false

           }).subscribe({

               socialLinks.value = it

           }, {

               Timber.e(it, “Error fetching Social Links”)

               error.value = “Error fetching Social Links”

           }))

}

3. Change the SocialLinksService file for storing social links in Database

We change the SocialLinksService file. We change it in such a way that social links are fetched from the server, only when they don’t exist in the database ( for a particular eventId )

fun getSocialLinks(id: Long): Flowable<List<SocialLink>> {

  //fetch social links

   val socialFlowable = socialLinksDao.getAllSocialLinks(id)

   return socialFlowable.switchMap {

       if (it.isNotEmpty())

           socialFlowable

       else

           socialLinkApi.getSocialLinks(id)

                   .map {

                       socialLinksDao.insertSocialLinks(it)

                   }

                   .flatMap {

                       socialFlowable

                   }

   }

}

We will go through the code in steps. First of all, we declare a variable, socialFlowable, which is assigned a value of a Flowable<List<SocialLinks>> fetched from the database. The getAllSocialLinks(id) takes eventId as parameter. Secondly, we assign a switchMap which is used to return the value of socialFlowable if it is not empty. So, if there have been some social links fetched from the database, we just return them without even making a network call.

If socialFlowable is actually empty, the else part of the conditional takes care of the network call. getSocialLinks(id) return a Flowable<List<SocialLinks>> object. .map is used to insert all the fetched social links. .flatMap is used to return socialFlowable to the function after the process.

4. Modify the network call in SocialLinkApi file

SocialLinks have relationship with event. To store them in the database, we distinguish each of the SocialLinks using eventId. The eventId will not be fetched in the regular network call, so we modify it.

interface SocialLinkApi {

   @GET(“events/{id}/social-links?include=event&fields[event]=id&page[size]=0”)

   fun getSocialLinks(@Path(“id”) id: Long): Flowable<List<SocialLink>>

}

We thus include event relationship. The JSON response will now contain a an event model inside relationships. The event model will have its id and type (event).

4. Create EventId model

This model will contain the eventId. The model will be of type event.

@Type(“event”)

data class EventId(

       @Id(LongIdHandler::class)

       val id: Long

)

4. Modify the SocialLink model to include relationship with event

We modify the model to include event relationship. As we need to store just the id of the event, we create another class named EventId and use a TypeConverter to convert the EventId model to a primitive type Long while compilation. We also include a foreign key which references the id of the event with the event field in the SociaLink model.

@Type(“social-link”)

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)

@Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = [“id”], childColumns = [“event”], onDelete = CASCADE))])

data class SocialLink(

       @Id(IntegerIdHandler::class)

       @PrimaryKey

       val id: Int,

       val link: String,

       val name: String,

       @ColumnInfo(index = true)

       @Relationship(“event”)

       var event: EventId? = null

)

4. Create the TypeConverter

We create the TypeConverter for converting EventId to just the id (Long).

class EventIdConverter {

   @TypeConverter

   fun fromEventId(eventId: EventId): Long{

       return eventId.id

   }

   @TypeConverter

   fun toEventId(id: Long): EventId{

       return EventId(id)

   }

}

4. Modify the Modules.kt file and the OpenEventDatabase

In the OpenEventDatabase file, we include the SocialLink entity to the Database. The annotation @TypeConverters is used to specify the type of TypeConverter to be used. Here we use EventIdConverter. In the abstract class, we include a function socialLinksDao() of type SocialLinksDao. It will fetch all the available functions from the file.

@Database(entities = [Event::class, User::class, SocialLink::class, Ticket::class], version = 1)

@TypeConverters(EventIdConverter::class)

abstract class OpenEventDatabase : RoomDatabase() {

   abstract fun eventDao(): EventDao

   abstract fun userDao(): UserDao

   abstract fun ticketsDao(): TicketsDao

   abstract fun socialLinksDao(): SocialLinksDao

}

In the Modules.kt file we add EventId class to the JSONAPIConverterfactory.

Retrofit.Builder()

       .client(get())

       .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

       .addConverterFactory(JSONAPIConverterFactory(objectMapper, Event::class.java, User::class.java, SignUp::class.java, Ticket::class.java, SocialLink::class.java, EventId::class.java))

       .addConverterFactory(JacksonConverterFactory.create(objectMapper))

       .baseUrl(baseUrl)

       .build()

In the same file, we add another factory object. This is about the fetching of the function of socialLinksDao in the OpenEventDatabase file.

factory {

   val database: OpenEventDatabase = get()

   database.socialLinksDao()

}

Thus, after all the modifications, we are able to achieve the feat of caching of social links in the app successfully!

References

Tags: GSoC18, FOSSASIA, Open Event, Android, SocialLinks Caching