Enable Server Configuration with Okhttp and Retrofit in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them.

We are using default API for eventyay app. Server configuration is something when we replace backend API with a new one and perform the same applications with the different server. As it is a fully open-source project on F-droid, so we have enabled the server configuration field for the F-droid build variant. 

  • Retrofit and okhttp for network calls
  • Create a feasible UI and set the link to preferences
  • Create interceptor for changing API URL
  • Add interceptor in okhttp client builder
  • Conclusion
  • Resources 

Let’s analyze every step in detail.

Retrofit and Okhttp for Network Call

Using Retrofit for your Android app’s networking can make your life so much easier. However, Retrofit’s design requires a single Retrofit instance for each API with a different base URL. Consequently, if your app is talking to two or more APIs (under different URLs), you’ll need to deal with at least two Retrofit instances.

Retrofit is a type-safe REST client for Android, Java, and Kotlin developed by Square. The library provides a powerful framework for authenticating and interacting with APIs and sending network requests with OkHttp.

OkHttp communicating with the server- 

Design UI and set the link to preferences with MVVM

Create a simple dialog with a checkbox with default URL and a EditText:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <CheckBox
        android:id="@+id/urlCheckBox"
        android:layout_margin="@dimen/layout_margin_large"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <com.google.android.material.textfield.TextInputLayout           style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
        android:id="@+id/urlTextInputLayout"
        android:layout_margin="@dimen/layout_margin_large"
        android:hint="@string/other_url"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/urlEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

Handle visibility if the dialog and display for only F-droid build:

preferenceScreen.findPreference<PreferenceCategory>(getString(R.string.key_server_configuration))?.isVisible = BuildConfig.FLAVOR == FDROID_BUILD_FLAVOR

Set current API to preference screen:

preferenceScreen.findPreference<Preference>(getString(R.string.key_api_url))?.title =
            settingsViewModel.getApiUrl()

Get API from View model: 

fun getApiUrl(): String {
        return preference.getString(API_URL) ?: BuildConfig.DEFAULT_BASE_URL
    }

Setup alert dialog:

if (preference?.key == getString(R.string.key_api_url)) {
            showChangeApiDialog()
        }
private fun showChangeApiDialog() {
        val layout = layoutInflater.inflate(R.layout.dialog_api_configuration, null)
        layout.urlCheckBox.text = BuildConfig.DEFAULT_BASE_URL

        val dialog = AlertDialog.Builder(requireContext())
            .setView(layout)
            .setPositiveButton(getString(R.string.change)) { _, _ ->
                val url = if (layout.urlCheckBox.isChecked) BuildConfig.DEFAULT_BASE_URL
                                else layout.urlEditText.text.toString()
                if (url === settingsViewModel.getApiUrl()) return@setPositiveButton
                settingsViewModel.changeApiUrl(url)
                view?.snackbar("API URL changed to $url")
                findNavController().popBackStack(R.id.eventsFragment, false)
            }
            .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() }
            .setCancelable(false)
            .show()
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false

        layout.urlCheckBox.setOnCheckedChangeListener { _, isChecked ->
            layout.urlTextInputLayout.isVisible = !isChecked
            dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = isChecked
        }

Set URL to preferences in the view model and end current session:

fun changeApiUrl(url: String) {
        preference.putString(API_URL, url)
        logout()
    }

Create Interceptor to Handle New API URL

Here default API URL is set to the retrofit already: 

Retrofit.Builder()
            .client(get())
            .baseUrl(baseUrl)
            .build()

As we discussed earlier OkHttp handles every network call for the application. So here we track the URL host from the okhttp interceptor. If the URL host is equaled to the default API URL host, then we can say that it is an API call and then we can replace same with the host getting from preferences if it is not null and set the interceptor to okhttp client builder.

Create host selection interceptor class to return interceptor with the API URL:

class HostSelectionInterceptor(private val preference: Preference) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var original = chain.request()
        val httpUrl = preference.getString(API_URL)?.toHttpUrlOrNull()
        if (original.url.host == BuildConfig.DEFAULT_BASE_URL.toHttpUrlOrNull()?.host && httpUrl != null) {
            val newUrl =
                original.url.newBuilder()
                    .scheme(httpUrl.scheme)
                    .host(httpUrl.host)
                    .port(httpUrl.port)
                    .build()
            original = original.newBuilder()
                .url(newUrl)
                .build()
        }
        return chain.proceed(original)
    }
}

Set the interceptor to okhttp client builder:

val builder = OkHttpClient().newBuilder()
            .addInterceptor(HostSelectionInterceptor(get()))

GIF

In a Nutshell

Server configuration provides better user experience for open-source platform and developer, as they can mention their own server and test it.

Resources

OkHttp client with retrofit: https://futurestud.io/tutorials/retrofit-2-share-okhttp-client-and-converters-between-retrofit-instances

Tags

Eventyay, open-event, OkHttp, Retrofit, FOSSASIA, GSoC, Android, Kotlin

Continue ReadingEnable Server Configuration with Okhttp and Retrofit in Open Event Attendee Application

Migration to Model-View-ViewModel Architecture and LiveData in Open Event Organizer App

Open Event Organizer App (Eventyay Organizer App) is the Android app used by event organizers to create and manage events on the Eventyay platform as well as check-in and check-out attendees along with other functionalities. The app used the MVP (Model-View-Presenter) architecture and is being ported to MVVM (Model-View-ViewModel). This article will explain the procedure of migrating MVP to MVVM architecture and implementing LiveData. 

Why migrate to MVVM?

The MVVM architecture is designed to store and manage UI-related data in a lifecycle conscious way. Configuration changes such as screen rotations are handled properly by ViewModels.

Tight Coupling:

The issue of tight coupling is resolved since only the View holds the reference to ViewModel and not vice versa. A single View can hold references to multiple ViewModels.

Testability:

Since Presenters are hard bound to Views, writing unit tests becomes slightly difficult as there is a dependency of a View.

ViewModels are more unit test friendly as they can be independently tested. There is no dependency of the View.

Here, the implementation is being described with the example of About Event module in the Open Event Organizer App.

First step is the creation of a new class AboutEventViewModel which extends ViewModel.

@Binds
@IntoMap
@ViewModelKey(AboutEventViewModel.class)
public abstract ViewModel bindAboutEventViewModel(AboutEventViewModel aboutEventViewModel);

The new ViewModel has to be added to the ViewModelModule:

Constructor for the ViewModel:

@Inject
public AboutEventViewModel(EventRepository eventRepository,  CopyrightRepository copyrightRepository,
DatabaseChangeListener<Copyright> copyrightChangeListener) {
    this.eventRepository = eventRepository;
    this.copyrightRepository = copyrightRepository;
    this.copyrightChangeListener = copyrightChangeListener;

    eventId = ContextManager.getSelectedEvent().getId();
}

We are using Dagger2 for dependency injection. 

LiveData

LiveData is a lifecycle-aware data holder with the observer pattern.

When we have a LiveData object (e.g. list of attendees), we can add some LifecycleOwner (it can be Activity or Fragment) as an observer. Using this:

The Activity or Fragment will remain updated with the data changes.

Observers are only notified if they are in the STARTED or RESUMED state which is also known as the active state. This prevents memory leaks and NullPointerExceptions because inactive observers are not notified about changes.

Now, let’s discuss about the implementation of LiveData. We will create objects of SingleEventLiveData<> class.

private final SingleEventLiveData<Boolean> progress = new SingleEventLiveData<>();
private final SingleEventLiveData<String> error = new SingleEventLiveData<>();
private final SingleEventLiveData<Event> success = new SingleEventLiveData<>();
private final SingleEventLiveData<Copyright> showCopyright = new SingleEventLiveData<>();
private final SingleEventLiveData<Boolean> changeCopyrightMenuItem = new SingleEventLiveData<>();
private final SingleEventLiveData<String> showCopyrightDeleted = new SingleEventLiveData<>();

The functions to get the LiveData objects:

public LiveData<Boolean> getProgress() {
    return progress;
}

public LiveData<Event> getSuccess() {
    return success;
}

public LiveData<String> getError() {
    return error;
}

public LiveData<Copyright> getShowCopyright() {
    return showCopyright;
}

public LiveData<Boolean> getChangeCopyrightMenuItem() {
    return changeCopyrightMenuItem;
}

public LiveData<String> getShowCopyrightDeleted() {
    return showCopyrightDeleted;
}

Now, we can remove getView() methods and instead, these objects will be used to call various methods defined in the fragment.

Let’s discuss the changes required in the AboutEventFragment now.

The Fragment will have ViewModelProvider.Factory injected.

@Inject
ViewModelProvider.Factory viewModelFactory;

Declare an object of the ViewModel.

private AboutEventViewModel aboutEventViewModel;

Then, in onCreateView(), viewModelFactory will be passed to the ViewModelProviders.of() method as the factory, which is the second parameter.

aboutEventViewModel = ViewModelProviders.of(this, viewModelFactory).get(AboutEventViewModel.class);

Replace all references to the Presenter with references to the ViewModel.

Add the Fragment as an observer to the changes by adding the following in the onStart() method:

aboutEventViewModel.getProgress().observe(this, this::showProgress);
aboutEventViewModel.getSuccess().observe(this, this::showResult);
aboutEventViewModel.getError().observe(this, this::showError);
aboutEventViewModel.getShowCopyright().observe(this, this::showCopyright);
aboutEventViewModel.getChangeCopyrightMenuItem().observe(this, this::changeCopyrightMenuItem);
aboutEventViewModel.getShowCopyrightDeleted().observe(this, this::showCopyrightDeleted);

Two parameters are passed to the observe() method  –  first one is LifecycleOwner, which is our Fragment in this case. The second one is a callback along with a parameter and is used to call the required method.

With this, the implementation of MVVM and LiveData is brought to completion.

Resources:

Documentation: ViewModel, LiveData

Further reading:

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue ReadingMigration to Model-View-ViewModel Architecture and LiveData in Open Event Organizer App

Handle app links and apply unit tests in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them. Users following links on devices have one goal in mind: to get to the content they want to see. As a developer, you can set up Android App Links to take users to a link’s specific content directly in your app, bypassing the app-selection dialog, also known as the disambiguation dialog. Because Android App Links leverage HTTP URLs and association with a website, users who don’t have your app installed go directly to content on your site.

A unit test generally exercises the functionality of the smallest possible unit of code (which could be a method, class, or component) in a repeatable way. You should build unit tests when you need to verify the logic of specific code in your app.

  • Why unit test cases?
  • Setup app link intent in the app
  • Apply unit test cases
  • Conclusion
  • Resources

Let’s analyze every step in detail.

Why unit test cases?

As it is already discussed what the app link intents are, and it is basically for providing a better user experience for the application. These are some following reason why unit test cases should use – 

  1. Rapid feedback on failures.
  2. Early failure detection in the development cycle.
  3. Safer code refactoring, letting you optimize code without worrying about regressions.
  4. Stable development velocity, helping you minimize technical debt.

JUnit4 library is used for unit tests.

Setup app link intent in the app

Declare the frontend host according to build flavor in the app level gradle file:

buildTypes {
        release {
            resValue "string",  "FRONTEND_HOST", "eventyay.com"
        }
        debug {
            resValue "string", "FRONTEND_HOST", "open-event-fe.netlify.com"
        }
    }

Handle the app link intent in Manifest file by adding intent filter under main activity decleartion:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data
        android:scheme="https"
        android:host="@string/FRONTEND_HOST"/>
</intent-filter>

Manifest will through the intent in the main activity file.

Now handle the intent in main activity:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleAppLinkIntent(intent)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        handleAppLinkIntent(intent)
    }

    private fun handleAppLinkIntent(intent: Intent?) {
        val uri = intent?.data ?: return
        val bundle = AppLinkUtils.getArguments(uri)
        val destinationId = AppLinkUtils.getDestinationId(uri)
        if (destinationId != null) {
            navController.navigate(destinationId, bundle)
        }
    }

Here a new class/object AppLinkUtils is defined which will return destination fragment id and the argument/data according to the intent URI.

Apply unit test cases:

First, implement the libraries in the gradle file –  1. JUnit for unit tests, 2. Robolectric for using android classes in the test class:

testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:3.4.2'

Create a test class for testing the app link functions and run it with RoboLectricTestRunner:

private const val EVENT = "event"
private const val RESET_PASSWORD = "resetPassword"
private const val VERIFY_EMAIL = "verifyEmail"

@RunWith(RobolectricTestRunner::class)
class AppLinkUtilsTest {

    private fun getAppLink(type: String): Uri {
        return when (type) {
            EVENT -> Uri.parse("https://eventyay.com/e/5f6d3feb")
            RESET_PASSWORD -> Uri.parse("https://eventyay.com/reset-password?token=822980340478781748445098077144")
            VERIFY_EMAIL -> Uri.parse("https://eventyay.com/verify?token=WyJsaXZlLmhhcnNoaXRAaG")
            else -> Uri.parse("")
        }
    }

    @Test
    fun `should get event link`() {
        val uri = getAppLink(EVENT)
        assertEquals(R.id.eventDetailsFragment, AppLinkUtils.getDestinationId(uri))
        assertEquals("""
            5f6d3feb
        """.trimIndent(), AppLinkUtils.getArguments(uri).getString(EVENT_IDENTIFIER))
    }// Find more test cases in the GitHub Repo.

Testing response:

GIF

In a Nutshell

So, essentially the Eventyay Attendee should have this feature to handle all links i.e. Reset password, verify user email and open event details in the app itself. So, we can provide a better user experience in-app instead of redirecting to the frontend for them.

Resources

  1. Android app links: https://developer.android.com/studio/write/app-link-indexing
  2. Developing android unit testing: https://www.vogella.com/tutorials/AndroidTesting/article.html

Tags

Eventyay, open-event, JUnit, AndroidUnitTest, AppLinks, Fossasia, GSoC, Android, Kotlin

Continue ReadingHandle app links and apply unit tests in Open Event Attendee Application

Configure location feature using MVVM in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them. It deals with events based on location, but we have to take the location as an input from the user. While in many cases, we have to search for events on our current location only. To make this work, I have added a current location option, where the app will get our location and search for nearby events. Earlier we had to enter our current location as well to search nearby events.

Model–view–viewmodel is a software architectural pattern. MVVM facilitates separation of development of the graphical user interface – be it via a markup language or GUI code – from the development of the business logic or back-end logic (the data model).

  • Why Model-view-ViewModel?
  • Setup Geo location View Model
  • Configure location feature with MVVM
  • Conclusion
  • Resources

Let’s analyze every step in detail.

Advantages of using Model-view-ViewModel

  1. A clean separation of different kinds of code should make it easier to go into one or several of those more granular and focused parts and make changes without worrying.
  2. External and internal dependencies are in separate pieces of code from the parts with the core logic that you would like to test.
  3. Observation of mutable live data whenever it is changed.

Setup the Geolocation view model

Created new kotlin class name GeoLocationViewModel which contains a configure function for current location:

package org.fossasia.openevent.general.search

class GeoLocationViewModel : ViewModel() {
    fun configure(activity: Activity) { }                                     
}

The GeoLocationViewModel class implement as VIewModel().

Now add the class in module inside Modules.kt :

val viewModelModule = module {
    viewModel { GeoLocationViewModel() }
}

Configure location feature with GeoLocationViewModel:

First, add play store location service implementation inside dependencies of build.gradle:

// Location Play Service
playStoreImplementation 'com.google.android.gms:play-services-location:16.0.0'

Now we need location permissions to implement this feature. Adding user permissions in the manifest file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

Now ask user for the location permission:

private fun checkLocationPermission() {
        val permission = context?.let {
            ContextCompat.checkSelfPermission(it, Manifest.permission.ACCESS_COARSE_LOCATION) }
        if (permission != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST)
        }
    }

Check for device location is enabled, if not send an intent to turn location on. The method is written inside configure function:

val service = activity.getSystemService(Context.LOCATION_SERVICE)
        var enabled = false
        if (service is LocationManager) enabled = service.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
        if (!enabled) {
            val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
            activity.startActivity(intent)
            return
        }

Now create Mutable live data for current location inside the view model class :

private val mutableLocation = MutableLiveData<String>()
val location: LiveData<String> = mutableLocation

Now implement location request and location callback inside configure method:

val locationRequest: LocationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_LOW_POWER
val locationCallback = object : LocationCallback() {
      override fun onLocationResult(locationResult: LocationResult?) {
           if (locationResult == null) {
               return
            }
            for (location in locationResult.locations) {
                if (location != null) {
                    val latitude = location.latitude
                    val longitude = location.longitude
                    try {
                        val geocoder = Geocoder(activity, Locale.getDefault())
                        val addresses: List<Address> = geocoder.getFromLocation(latitude, longitude, maxResults)
                        for (address: Address in addresses) {
                            if (address.adminArea != null) {
                                mutableLocation.value = address.adminArea
                            }
                        }
                    } catch (exception: IOException) {
                        Timber.e(exception, "Error Fetching Location")
                    }
                }
             }
        }
    }

Now call location service inside configure method:

LocationServices
                .getFusedLocationProviderClient(activity)
                .requestLocationUpdates(locationRequest, locationCallback, null)

Now observe data for current location inside the fragment from the view model. Save the current user location and go to the main activity:

private val geoLocationViewModel by viewModel<GeoLocationViewModel>()
geoLocationViewModel.location.observe(this, Observer { location ->
                searchLocationViewModel.saveSearch(location)
                redirectToMain()
            })

GIF

In a Nutshell

So, essentially the Eventyay Attendee should have this feature to show all the events nearby me or in my city, although the app was already doing the job, but we had to manually select the city or locality we wish to search, now after the addition of dedicated current location option, the app will be more user friendly and automated.

Resources

  1. Google location services API: https://www.toptal.com/android/android-developers-guide-to-google-location-services-api
  2. MVVM architecture in Android: https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-1-604f50cda1

Tags

Eventyay, open-event, PlayServices, Location, MVVM, Fossasia, GSoC, Android, Kotlin

Continue ReadingConfigure location feature using MVVM in Open Event Attendee Application

Add Navigation Architecture Component in Eventyay Attendee

The eventyay attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them.

The project earlier used Fragment transition to handle fragment with multiple activities. Multiple activities are used because it is very complex to handle all fragment with a single activity using fragment transition. If we try to make a single activity application with fragment transition, it contains a lot of bugs with the back stack.

But we can use Navigation Architecture Component to efficiently make Eventyay attendee a single activity. It is the perfect technology to handle all fragments and authentication in a single activity with no bigs or back stack.

  • Issues with fragment transition and multiple activities
  • Why navigation architecture component?
  • Process for using Navigation Architecture Component in the application
  • Animation of fragments with Navigation Architecture Component
  • Conclusion
  • Resources 

Let’s analyze every step in detail.

Issues with fragment transition and multiple activities

  1. Need to handle back stack on each Fragment Transition
  2. Hard to debug
  3. This can leave your app in an unknown state when receiving multiple click events or during configuration changes.
  4. Fragment instances can be created
  5. Multiple activities make the app slower due to using multiple intents

Why navigation architecture component?

  1. Handling Fragment transactions
  1. Handling Up and Back actions correctly by default
  2. Providing standardized resources for animations and transitions
  3. Treating deep linking as a first-class operation
  4. Including Navigation UI patterns, such as navigation drawers and bottom navigation, with minimal additional work
  5. Providing type safety when passing information while navigating
  6. Visualizing and editing navigation graphs with Android Studio’s Navigation Editor

Steps involved in using Navigation Architecture Component in the application

Adding dependencies to build.gradle:

//Navigation
    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha09'
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha09'

Create new resources file for navigation graph under res -> navigation:

Add fragments in navigation_graph.xml with navigation editor. Let’s take one example:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"   android:id="@+id/mobile_navigation"
    app:startDestination="@id/eventsFragment">

    <fragment
        android:id="@+id/eventsFragment"
        android:name="org.fossasia.openevent.general.event.EventsFragment"
        android:label="EventsFragment" />
</navigation>

Here app:startDestination attribute is for the very first fragment after which app exit on back pressed.

Replace Frame layout with a fragment in the main activity layout file. And add the above navigation graph to it: 

<fragment
            android:id="@+id/frameContainer"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="@android:color/white"
            android:layout_weight="1"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navigation_graph"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

Now, this fragment layout is directly connected with all fragments added in the navigation graph. We only need to navigate the fragment.

Set bottom navigation drawer with the navigation architecture component – 

First, we need to make the ids of menu component same as the ids of fragments in the navigation graph. Bottom navigation menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/eventsFragment"
        android:icon="@drawable/ic_baseline_event_24px"
        android:title="@string/events" />
    <item
        android:id="@+id/searchFragment"
        android:icon="@drawable/ic_search_grey_24dp"
        android:title="@string/search" />
    <item
        android:id="@+id/favoriteFragment"
        android:icon="@drawable/ic_baseline_favorite_border_24px"
        android:title="@string/likes" />
    <item
        android:id="@+id/orderUnderUserFragment"
        android:icon="@drawable/ic_baseline_ticket_24dp"
        android:title="@string/tickets" />
    <item
        android:id="@+id/profileFragment"
        android:icon="@drawable/ic_baseline_account_circle_24px"
        android:title="@string/profile" />
</menu>

Setup the bottom navigation menu with navigation controller:

// import require libraries
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI.setupWithNavController

// Set nav controller with host fragment using Kotlin smart cast
val hostFragment = supportFragmentManager.findFragmentById(R.id.frameContainer)
 if (hostFragment is NavHostFragment)
     navController = hostFragment.navController
setupWithNavController(navigationMenu, navController)

Navigate fragment for bottom naviagtion whenever required:

navController.navigate(R.id.munuItemId)

Navigate any fragment which is added in navigation graph with arguments:

import androidx.navigation.Navigation
findNavController(rootView).navigate(R.id.fragmentId,  arguments)

Add Animations for fragments using navigation architecture component

Add require animations in utils as navigation options:

fun getAnimFade(): NavOptions {
        val builder = NavOptions.Builder()
        builder.setEnterAnim(R.anim.fade_in)
        builder.setExitAnim(R.anim.fade_out)
        builder.setPopEnterAnim(R.anim.slide_in_left)
        builder.setPopExitAnim(R.anim.slide_out_right)
        return builder.build()
    }

    fun getAnimSlide(): NavOptions {
        val builder = NavOptions.Builder()
        builder.setEnterAnim(R.anim.slide_in_right)
        builder.setExitAnim(R.anim.slide_out_left)
        builder.setPopEnterAnim(R.anim.slide_in_left)
        builder.setPopExitAnim(R.anim.slide_out_right)
        return builder.build()
    }

Resource files for animations (fade_in, fade_out, slide_in_right, slide_out_left, slide_in_left, slide_out_right) are added in res -> anim.

Now add nav options whenever navigate to fragments:

import androidx.navigation.Navigation
import org.fossasia.openevent.general.utils.Utils
findNavController(rootView).navigate(R.id.fragmentId,  arguments, getAnimFade())

GIF

In a Nutshell

Single activity application is the future of Android and many apps are shifting towards single activity Architecture from multiple activity ones. This brings in ease to use and smooth user experience. In shifting to a single activity, we have used Navigation Architecture Component which is much more efficient than Fragment transition which has many bugs associated with it while converting the app to a single activity.

Resources

  1. Documentation for navigation architecture component: https://developer.android.com/topic/libraries/architecture/navigation/
  2. Google navigation codelab: https://codelabs.developers.google.com/codelabs/android-navigation/

Tags

Eventyay, open-event, Navigation, Fragment, FOSSASIA, GSoC, Android, Kotlin

Continue ReadingAdd Navigation Architecture Component in Eventyay Attendee

Add PayPal Payment integration in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them.

PayPal is a very common method to pay for anything throughout the world. It is a highly popular platform and it’s only right that there should be an option to pay through PayPal on Eventyay attendee for tickets. This blog will explain how and why I added PayPal payment feature in the application with a sandbox account.

  • Why PayPal?
  • Get API key for a sandbox account
  • Integration with Android Studio
  • Conclusion
  • Resources

Let’s analyze every step in detail.

Advantages of using PayPal Payment integration

  1. Provide UI to gather payment information from the user
  2. Get your credentials, which identify your PayPal account as the payment receiver. Specifically, obtain a client ID and secret.
  3. Returns a proof of payment to your app.
  4. Provides the user their goods or services.

Setup Sandbox account and get the API key

Go to https://developer.paypal.com/ and sign up for a developer account:

After Sign up, go to the dashboard and create an app: 

Now in app credentials go to sandbox accounts. Here you can find your API key.

Now, Create a new sandbox account with entering some amount of money for testing purposes:

PayPal SDK integration in the application

Add PayPal SDK in build.gradle dependencies:

//PayPal
compile 'com.paypal.sdk:paypal-android-sdk:2.16.0'

Store the API key in the android manifest file:

<meta-data
            android:name="com.paypal.android.API_KEY"
            android:value="${PAYPAL_CLIENT_ID}"/>

Get the API key in the fragment where PayPal payment is required:

private lateinit var PAYPAL_API_KEY: String
PAYPAL_API_KEY = activity?.packageManager?.getApplicati<meta-data
            android:name="com.paypal.android.API_KEY"
            android:value="${PAYPAL_CLIENT_ID}"/>onInfo(activity?.packageName, PackageManager.GET_META_DATA)
            ?.metaData?.getString(PAYPAL_KEY).toString()

Start PayPal services on create view:

val payPalConfiguration = PayPalConfiguration()
            .environment(PayPalConfiguration.ENVIRONMENT_SANDBOX) 
            .clientId(PAYPAL_API_KEY)val intent = Intent(context, PaymentActivity::class.java)intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, payPalConfiguration)activity?.startService(intent)

Now start the Payment conditionally with same intent: 

val payment = PayPalPayment(BigDecimal(amount.toString()), "USD", "Pay for tickets", PayPalPayment.PAYMENT_INTENT_SALE)
intent.putExtra(PaymentActivity.EXTRA_PAYMENT, payment)
startActivityForResult(intent, PAYPAL_REQUEST_CODE)

Handle the result after payment is done:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == PAYPAL_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                val paymentConfirmation = data?.getParcelableExtra<PaymentConfirmation>(PaymentActivity.EXTRA_RESULT_CONFIRMATION)
                if (paymentConfirmation != null) {
                    val paymentInfo = paymentConfirmation.toJSONObject()
                    val tokenId = paymentInfo.getJSONObject("response").getString("id")
                    Timber.d(paymentInfo.toString(4))
                    // Send the token to server
                    val charge = Charge(attendeeViewModel.getId().toInt(), tokenId, null)
                    attendeeViewModel.completeOrder(charge)
                }
            } else if (resultCode == Activity.RESULT_CANCELED)
                Toast.makeText(context, "Payment canceled!", Toast.LENGTH_SHORT).show()
            else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID)
                Toast.makeText(context, "Invalid Payment Configuration", Toast.LENGTH_SHORT).show()
        }
    }

GIF

In a nutshell

With almost 250 million users worldwide, PayPal is an extremely popular platform for monetary transactions and it’s quite essential that every application an option to use it. Given the nature of Eventyay attendee and its pan-world appeal, I have added PayPal as a payment system for tickets to any event.

Resources

  1. PayPal Android SDK guide: PayPal Android SDK
  2. PayPal SDK repo: PayPal-Android-SDK

Tags

PayPal, Android, Payments, FOSSASIA, GSoC, AndroidPayments, Kotlin

Continue ReadingAdd PayPal Payment integration in Open Event Attendee Application

Serializing Java objects for REST API Requests in Open Event Organizer App

Open Event Organizer App is a client side application which uses REST API for network requests. The server supports sending and receiving of data only in JSONAPI spec, so, we needed to serialize java models into JSON objects and deserialize JSON data into java models following JSONAPI spec. To achieve this we followed the following steps.

Specifications

We will be using jasminb/jsonapi-converter which handles request/response parsing of models following JSONAPI Spec and Retrofit plugin of jackson converter to serializing JSON to Java Models and vice versa.

Let’s create a java model. We are using some annotations provided by Lombok library to avoid writing boilerplate code. @JsonNaming annotation is used to apply KebabCaseStrategy while serializing fields

@Data
@Type(“order”)
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@Table(database = OrgaDatabase.class, allFields = true)
public class Order {

@PrimaryKey
@Id(LongIdHandler.class)
public Long id;

public float amount;
public String completedAt;
public String identifier;
public String paidVia;
public String paymentMode;
public String status;

@Relationship(“event”)
@ForeignKey(stubbedRelationship = true, onDelete = ForeignKeyAction.CASCADE)
public Event event;

public Order() { }
}

In the NetworkModule class, there is a method providesMappedClasses() containing a list of classes that needs to be serialized/deserialized. We need to add the above model in the list. Then, this list is provided to Singleton instance of JSONAPIConvertorFactory through Dagger. JSONAPIConvertorFactory uses the Retrofit ObjectMapper and maps the classes that are handled by this instance.

@Provides
Class[] providesMappedClasses() {
return new Class[]{Event.class, Attendee.class, Ticket.class, Order.class};
}

Further, various serialization properties can be used while building Singleton ObjectMapper instance. Adding any properties here ensures that these are applied to all the mapped classes by JSONAPIConvertorFactory. For eg, we are using the serialization property to throw an exception and fail whenever empty beans are encountered.

@Provides
@Singleton
ObjectMapper providesObjectMapper() {
return new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
// Handle constant breaking changes in API by not including null fields
// TODO: Remove when API stabilizes and/or need to include null values is there
.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
}

Resources

  1. Github Repository for jsonapi-converter https://github.com/jasminb/jsonapi-converter
  2. Github repository for Jackson Retrofit Plugin https://github.com/square/retrofit/tree/master/retrofit-converters/jackson
  3. Official Website for Project Lombok https://projectlombok.org/

Github Repository for Open-Event-Orga-App https://github.com/fossasia/open-event-orga-app

Continue ReadingSerializing Java objects for REST API Requests in Open Event Organizer App

Adding Preference Settings using Preference Fragment Compat

It is very much likely that one needs to add preferences to their app which span the entire application and therefore can be accessed anywhere in the app without storing anything in database or making global variables. For an instance, in Open Event Organizer App we added the preferences to store the privacy policy, cookie policy etc. The user can access these items in Settings Preference which in device settings. In this blog post we will see how to add preference settings to the app by storing the data in shared preferences.

Specifications

The benefit of storing the data in shared preference and not in local storage is that the access time for the data is drastically reduced and the data persists even when the app is closed. We will use this library which is built on top of official preference-v7 library.

Firstly, we will make a preference resource layout file and add the preference for privacy policy and cookie policy in the preference screen.

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

<Preference
android:key=”@string/privacy_policy_key”
android:title=”@string/privacy_policy” />

<Preference
android:key=”@string/cookie_policy_key”
android:title=”@string/cookie_policy” />

</PreferenceScreen>

Make a separate preference fragment class named LegalPreferenceFragment which extends PreferenceFragmentCompat. Then we will override onCreatePreferenceFix() method.

Inside this, we will create an instance of Preference Manager and set shared preference name for it and set the preference using the layout file. This enables us to use findPreference() method to retrieve the layout preferences by their key. After, retrieving the preference we will set onClick listener to launch activity with an intent to open browser for the url passed in data bundle.

@Override
public void onCreatePreferencesFix(@Nullable Bundle bundle, String rootKey) {
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Constants.FOSS_PREFS);

setPreferencesFromResource(R.xml.legal_preferences, rootKey);

findPreference(getString(R.string.privacy_policy_key)).setOnPreferenceClickListener(preference -> {
BrowserUtils.launchUrl(getContext(), PRIVACY_POLICY_URL);
return true;
});
findPreference(getString(R.string.cookie_policy_key)).setOnPreferenceClickListener(preference -> {
BrowserUtils.launchUrl(getContext(), COOKIE_POLICY_URL);
return true;
});
}

References

  1. Preference Fragment Compat library by Takisoft https://github.com/Gericop/Android-Support-Preference-V7-Fix
  2. Android Preference Documentation https://developer.android.com/reference/android/preference/PreferenceGroup
Continue ReadingAdding Preference Settings using Preference Fragment Compat

Implementing Timeline for Attendees Activity in Organizer App

Open Event Organizer App offers the functionality to Checkin/checkout attendees but the Organizer was unable to view when a particular attendee was checkin or checkout. We decided to implement a feature to view the timeline of checkin/checkout for each attendee.

Let’s begin by adding the dependency in build.gradle.

implementation “com.github.vipulasri:timelineview:”1.0.6”

In the recyclerview item layout add the TimeLineView layout. Following are some of the useful attributes.

  1. app:markerInCenter – This defines the position of the round marker within the layout. Setting it to true, position it in center.
  2. app:marker – Custom drawables can be set as marker.
<com.github.vipulasri.timelineview.TimelineView
android:id=”@+id/time_marker”
android:layout_width=”wrap_content”
android:layout_height=”match_parent”
app:marker=”@drawable/ic_marker_active”
app:line=”#aaa4a4″
app:lineSize=”2dp”
app:linePadding=”3dp”
app:markerInCenter=”true”
app:markerSize=”20dp” />

The ViewHolder class will extend the RecyclerView,ViewHolder class. In the constructor, we will add a parameter viewType and then set it to TimeLine Marker layout using method initLine.

public CheckInHistoryViewHolder(CheckInHistoryLayoutBinding binding, int viewType) {
super(binding.getRoot());
this.binding = binding;
binding.timeMarker.initLine(viewType);
}

In RecyclerViewAdapter, we will override the getItemViewType() method. Here we will use the getTimeLineViewType method which takes in position and total size of the recycler view list and returns a TimeLineView type object.

@Override
public int getItemViewType(int position) {
return TimelineView.getTimeLineViewType(position, getItemCount());
}

References

  1. TimeLineView library by VipulAsri https://github.com/vipulasri/Timeline-View
  2. Android Documentation for RecyclerViewAdapter https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter
  3. Android Documentation for RecyclerViewView https://developer.android.com/reference/android/support/v7/widget/RecyclerView
Continue ReadingImplementing Timeline for Attendees Activity in Organizer App

Adding List Preference Settings using Preference Fragment Compat

In this blog post we will see how we can add a Preference List in Settings which will display a list of radio buttons in UI which user can choose from. In Open Event Orga App, the Organizer had a choice to switch between viewing Net Sales or Gross Sales in the App’s Dashboard. We decided to use a preference list to allow the user to select between using Net or Gross Sales.

The benefit of using Preference List and not any other storage media (like SQLite) to store the information is that, Preference List stores the information as key-value pair in SharedPreferences which makes it easy to store and extract small amount of data with strong consistency guarantees and using less time. Let’s move on to the implementation.

Implementation

Firstly add the dependency in build.gradle.

implementation “com.takisoft.fix:preference-v7:27.1.0.0”

In the preferences layout file, we will use checkboxes.

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

<CheckBoxPreference
android:key=”@string/gross_sales_key”
android:title=”@string/gross_sales”
android:defaultValue=”true” />

<CheckBoxPreference
android:key=”@string/net_sales_key”
android:title=”@string/net_sales”
android:defaultValue=”false” />
</PreferenceScreen>

We will create SalesDataSettings class which extends PreferenceFragmentCompat and override onCreatePreferenceFix method. We will request PreferenceManager and set SharedPreferencesName. The manager will be used to store and retrieve key-value pairs from SharedPreferences. Using setPreferencesFromResource we will attach the layout file to the fragment.

PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Constants.FOSS_PREFS);

setPreferencesFromResource(R.xml.sales_data_display, rootKey);

We are using CheckBox Preferences and modifying their behaviour to work as a Radio Preference List because Radio reference is not provided by Android. We are initializing two checkboxes and attaching a preference listener to unset all other checkboxes which one is selected.

CheckBoxPreference netSales = (CheckBoxPreference) findPreference(NET_SALES);
CheckBoxPreference grossSales = (CheckBoxPreference) findPreference(GROSS_SALES);

Preference.OnPreferenceChangeListener listener = (preference, newValue) -> {
String key = preference.getKey();

switch (key) {
case GROSS_SALES:
netSales.setChecked(false);
break;
case NET_SALES:
grossSales.setChecked(false);
break;
default:
break;
}
return (Boolean) newValue;
};

netSales.setOnPreferenceChangeListener(listener);
grossSales.setOnPreferenceChangeListener(listener);

We can load SalesDataDisplay Fragment class when a preference button is clicked using fragment transactions as shown below.

findPreference(getString(R.string.sales_data_display_key)).setOnPreferenceClickListener(preference -> {
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, SalesDataSettings.newInstance())
.addToBackStack(null)
.commit();
return true;
});

References

  1. Shared Preferences Documentation https://developer.android.com/reference/android/content/SharedPreferences
  2. Gericop Takisoft Android-Support-Preference-V7-Fix https://github.com/Gericop/Android-Support-Preference-V7-Fix
  3. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
Continue ReadingAdding List Preference Settings using Preference Fragment Compat