Implementation of Role Invites in Open Event Organizer Android App

Open Event Organizer Android App consists of various features which can be used by event organizers to manage their events. Also, they can invite other people for various roles. After acceptance of the role invite, the particular user would have access to features like the event settings and functionalities like scanning of tickets and editing of event details, depending on the access level of the role.

There can be various roles which can be assigned to a user: Organizer, Co-Organizer, Track Organizer, Moderator, Attendee, Registrar.

Here we will go through the process of implementing the feature to invite a person for a particular role for an event using that person’s email address.

The ‘Add Role’ screen has an email field to enter the invitee’s email address and select the desired role for the person. Upon clicking the ‘Send Invite’ button, the person would be sent a mail containing a link to accept the role invite.

The Role class is used for the different types of available roles.

@Data
@Builder
@Type("role")
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class Role {

    @Id(LongIdHandler.class)
    public Long id;

    public String name;
    public String titleName;
}

The RoleInvite class:

@Data
@Builder
@Type("role-invite")
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class RoleInvite {

    @Id(LongIdHandler.class)
    public Long id;

    @Relationship("event")
    public Event event;

    @Relationship("role")
    public Role role;

    public String email;
    public String createdAt;
    public String status;
    public String roleName;
}

A POST request is required for sending the role invite using the email address of the recipient as well as the role name.

@POST("role-invites")
Observable<RoleInvite> postRoleInvite(@Body RoleInvite roleInvite);

On clicking the ‘Send Invite’ button, the email address would be validated and if it is valid, the invite would be sent.

binding.btnSubmit.setOnClickListener(v -> {
        if (!validateEmail(binding.email.getText().toString())){            
            showError(getString(R.string.email_validation_error));
            return;
        }
        roleId = binding.selectRole.getSelectedItemPosition() + 1;
        roleInviteViewModel.createRoleInvite(roleId);
});

createRoleInvite() method in RoleInviteViewModel:

public void createRoleInvite(long roleId) {

    long eventId = ContextManager.getSelectedEvent().getId();
    Event event = new Event();
    event.setId(eventId);
    roleInvite.setEvent(event);
    role.setId(roleId);
    roleInvite.setRole(role);

    compositeDisposable.add(
        roleRepository
            .sendRoleInvite(roleInvite)
            .doOnSubscribe(disposable -> progress.setValue(true))
            .doFinally(() -> progress.setValue(false))
            .subscribe(sentRoleInvite -> {
                success.setValue("Role Invite Sent");
            }, throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));
}

It takes roleId as an argument which is used to set the desired role before sending the POST request.

We can notice the use of sendRoleInvite() method of RoleRepository. Let’s have a look at that:

@Override
public Observable<RoleInvite> sendRoleInvite(RoleInvite roleInvite) {
    if (!repository.isConnected()) {
        return Observable.error(new Throwable(Constants.NO_NETWORK));
    }

    return roleApi
        .postRoleInvite(roleInvite)
        .doOnNext(inviteSent -> Timber.d(String.valueOf(inviteSent)))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
}

Resources:

API Documentation: Roles, Role Invites

Pull Request: feat: Implement system of role invites

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

Continue ReadingImplementation of Role Invites in Open Event Organizer Android App

Implementation of Pagination in Open Event Organizer Android App

Pagination (Endless Scrolling or Infinite Scrolling) breaks down a list of content into smaller parts, loaded one at a time. It is important when the quantity of data to be loaded is huge and loading all the data at once can result in timeout.

Here, we will discuss about the implementation of pagination in the list of attendees in the Open Event Organizer App (Eventyay Organizer App).

It is an Android app used by event organizers to create and manage events on the Eventyay platform. Features include event creation, ticket management, attendee list with ticket details, scanning of participants etc.

In the Open Event Organizer App, the loading of attendees would result in timeout when the number of attendees would be large. The solution for fixing this was the implementation of pagination in the Attendees fragment.

First, the API call needs to be modified to include the page size as well as the addition of page number as a Query.

@GET("events/{id}/attendees?include=order,ticket,event&fields[event]=id&fields[ticket]=id&page[size]=20")
Observable<List<Attendee>> getAttendeesPageWise(@Path("id") long id, @Query("page[number]") long pageNumber);

Now, we need to modify the logic of fetching the list of attendees to include the page number. Whenever one page ends, the next page should be fetched automatically and added to the list.

The page number needs to be passed as an argument in the loadAttendeesPageWise() method in AttendeesViewModel.

public void loadAttendeesPageWise(long pageNumber, boolean forceReload) {

    showScanButtonLiveData.setValue(false);

    compositeDisposable.add(
        getAttendeeSourcePageWise(pageNumber, forceReload)
            .doOnSubscribe(disposable -> progress.setValue(true))
            .doFinally(() -> progress.setValue(false))
            .toSortedList()
            .subscribe(attendees -> {
                attendeeList.addAll(attendees);
                attendeesLiveData.setValue(attendees);
                showScanButtonLiveData.setValue(!attendeeList.isEmpty());
            }, throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));
}

Also in the getAttendeeSourcePageWise() method:

private Observable<Attendee> getAttendeeSourcePageWise(long pageNumber, boolean forceReload) {
    if (!forceReload && !attendeeList.isEmpty())
        return Observable.fromIterable(attendeeList);
    else
        return attendeeRepository.getAttendeesPageWise(eventId, pageNumber, forceReload);
}

Now, in the AttendeesFragment, a check is needed to increase the current page number and load attendees for the next page when the user reaches the end of the list. 

if (!recyclerView.canScrollVertically(1)) {

    if (recyclerView.getAdapter().getItemCount() > currentPage * ITEMS_PER_PAGE) {
        currentPage++;
    } else {
        currentPage++;                       
        attendeesViewModel.loadAttendeesPageWise(currentPage, true);
    }
}

When a new page is fetched, we need to update the existing list and add the elements from the new page.

@Override
public void showResults(List<Attendee> attendees) {
    attendeeList.addAll(attendees);
    fastItemAdapter.setNewList(attendeeList);
    binding.setVariable(BR.attendees, attendeeList);
    binding.executePendingBindings();
}

Now, list of attendees would be fetched pagewise, thus improving the performance and preventing timeouts.

Resources:

Further reading:

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

Continue ReadingImplementation of Pagination in Open Event Organizer Android App

Feature to generate Config File in PSLab Android application

In this blog, I will explain the feature to generate “Config File” in PSLab Android Application 

What is a Config File?

The main aim of this feature is to make PSLab board a self data logger, which would read user-defined configs from a config file stored on SD card connected to PSLab board and based on instrument, parameters and time interval stored in config file PSLab board would automatically log those values. 

Now as the first step of this feature, an option is added to PSLab Android application, where user can create a config file. User can select an instrument, parameters associated with that instrument and time interval. With this feature, user can easily generate a config file which can later be used by PSLab board for logging.

User Interface

The option to generate a config file is given in the side navigation menu on the main screen. 

(Figure 1: Generate Config file menu)

Once the user selects the “Generate Config File” option, the user will be directed to the following screen where user can create a config file with intended parameters

(Figure 2: Generate Config File UI)

As can be seen in the screenshot above the user can select instruments for which the config file needs to be created from a drop-down menu. User can specify the time interval, for which the data should be logged by the PSLab board. Based on the instrument selected by the user corresponding parameters will be shown at the bottom. User can select whichever parameters are required and click on “CREATE CONFIG FILE” button and a config file will be saved on device local storage. 

A config file for Oscilloscope with 25-sec interval and CH1, CH2 and CH3 parameters would look something like below,

(Figure 3: Sample config File )

Implementation

When a user clicks on Create Config File button, First we check whether the user has provided a time interval, if not a toast message appears to let the user know that time interval is missing. This is done using the following lines of code,

createConfigFileBtn.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
     interval = intervalEditText.getText().toString();
     if (interval.length() == 0) {
        Toast.makeText(CreateConfigActivity.this, getResources().getString(R.string.no_interval_message), Toast.LENGTH_SHORT).show();
                }

Once the user sets the time interval and selects the parameters, the following lines of code generates a string array containing params selected by the user.

ArrayList<String> selectedParamsList = new ArrayList<>();
for (int i = 0; i < paramsListContainer.getChildCount(); i ++) {
    CheckBox checkBox = (CheckBox) paramsListContainer.getChildAt(i);
    if (checkBox.isChecked()) {
       selectedParamsList.add(instrumentParamsList.get(selectedItem)[i]);
    }
}

After we have the list of selected parameters we call the following function to create the config file

private void createConfigFile(ArrayList<String> params) {
        String instrumentName = instrumentsList.get(selectedItem);
        String fileName = "pslab_config.txt";
        String basepath = Environment.getExternalStorageDirectory().getAbsolutePath();

        File baseDirectory = new File(basepath + File.separator + CSVLogger.CSV_DIRECTORY);
        if (!baseDirectory.exists()) {
            try {
                baseDirectory.mkdir();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        File configFile = new File(basepath + File.separator + CSVLogger.CSV_DIRECTORY + File.separator + fileName);
        if (!configFile.exists()) {
            try {
                configFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            FileWriter writer = new FileWriter(configFile);
            writer.write("instrument: " + instrumentName + "\n");
            writer.write("interval: " + interval + " " + intervalUnit + "\n");
            String param = String.join(",", params);
            writer.write("params: " + param);
            writer.flush();
            writer.close();
            CustomSnackBar.showSnackBar(rootView, getString(R.string.file_created_success_message), null, null, Snackbar.LENGTH_SHORT);
        } catch (IOException e) {
            e.printStackTrace();
            CustomSnackBar.showSnackBar(rootView, getString(R.string.file_created_fail_message), null, null, Snackbar.LENGTH_SHORT);
        }

    }

In the first part of this function, we check whether there exists a PSLab directory in the local storage of the device, if not the directory is created. After that, we create a file named “pslab_config.txt”. After that, we use FileWriter to write data to the file. 

In a nutshell with this feature user can create config files easily. The following GIF demonstrated this functionality.

(Figure 4: GIF of the functionality)

References

Tags: PSLab, Android, GSoC 19, Config File, data logger

Continue ReadingFeature to generate Config File in PSLab Android 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

Tracking location on Android – using GPS to record data in Neurolab

In the Neurolab-Android app, we have a feature for recording data. It uses data incoming from the hardware device and stores it in a data table format with various parameters. Two of these parameters happened to be the latitude and longitude (location) of the user using the app. For that, we needed to implement a location tracking feature which can be used while recording the data in the data table.

Let’s start off with adding the required permission uses in the Android Manifest file.

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

These will be used to ask permissions from the user to access the devices’ internet and location/GPS.

Now, we will be making our Location Tracker class that will be used in tracking the location and getting the corresponding latitude and longitude. 

Firstly, we are going to define some variables – an array of manifest permissions for enabling GPS, some constant values for requesting specific permissions, a provider for the GPS provider.

private String[] mapPermissions = new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION
    };
    public static final int GPS_PERMISSION = 103;
    private static final int UPDATE_INTERVAL_IN_MILLISECONDS = 400;
    private static final int MIN_DISTANCE_CHANGE_FOR_UPDATES = 1;
    private String provider = LocationManager.GPS_PROVIDER;

We also need to initialize and have a location manager ready to request location updates from time to time. Defining a locationManager need to get the system service for location. We define it in the following way:

LocationManager locationManager = (LocationManager)getContext().getSystemService(Context.LOCATION_SERVICE)

Next, we set up a location listener which listens to location changes of the device. We define that in the following way:

private LocationListener locationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            bestLocation = location;
        }

Now, that we have our variables, permissions, location manager and listener set up, we can start capturing the location.

@SuppressLint("MissingPermission")
    public void startCaptureLocation() {
        if (PermissionUtils.checkRuntimePermissions(context, mapPermissions)) {
            locationManager.requestLocationUpdates(provider, UPDATE_INTERVAL_IN_MILLISECONDS, MIN_DISTANCE_CHANGE_FOR_UPDATES,
                    locationListener);
        } else {
            PermissionUtils.requestRuntimePermissions(context, mapPermissions, GPS_PERMISSION);
        }
    }

Firstly, we need to check for the runtime permissions required for our task. We achieve this with the function ‘checkRuntimePermissions’ which we have created in a utility class named ‘PermissionUtils’. The code of this utility class can be found here: PermissionUtils.java. It basically self checks individual permissions from the array passed in the arguments with the Android system.

We then use the location manager instance to request current location updates using the constants and the location listener we defined earlier.

So now, that we have started capturing the user device location, we can get the device Location object values (latitude and longitude). 

@SuppressLint("MissingPermission")
    public Location getDeviceLocation() {
        if (bestLocation == null) {
            if (PermissionUtils.checkRuntimePermissions(context, mapPermissions)) {
                locationManager.requestLocationUpdates(provider,
                        UPDATE_INTERVAL_IN_MILLISECONDS, MIN_DISTANCE_CHANGE_FOR_UPDATES,
                        locationListener);
                return locationManager.getLastKnownLocation(provider);
            } else {
                return defaultLocation();
            }
        } else {
            return bestLocation;
        }
    }

This method requests the location and returns a Location object. If there is an internet connection problem or the device location is disabled from the device settings, it returns a default location object. Here in the Neurolab project, we had set the default location latitude and longitude to 0.0.

Now we can use this location while creating the recorded file in our app directory which can be used after importing that recorded file in the app. We will be storing the location in two columns in the recorded file which is in a table format.

We write the latitude and longitude of the device in the file using a PrintWriter object and get the latitude and longitude of the device in the following way:

long latitude = locationTracker.getDeviceLocation().getLatitude()
long longitude = locationTracker.getDeviceLocation().getLongitude()

Then, using the PrintWriter object we can write the data into the file in the following way:

PrintWriter out = new PrintWriter(new BufferedWriter(new                        FileWriter(csvFile, true)));
out.write(data + "\n");

Here, the ‘data’ contains the latitude and longitude converted to String type.

That’s it! Now you can use the LocationTracker object to capture the location and get the current device location using it in your own app as well.

Hope this blog, adds value to your Android development skills.

References:

  1. https://developer.android.com/reference/android/location/LocationManager.html
  2. https://stackoverflow.com/a/43319075 
  3. https://youtu.be/Ak8uRvlpGS0 

Tags: FOSSASIA, Android, GPS, GSOC 19, Neurolab, Location

Continue ReadingTracking location on Android – using GPS to record data in Neurolab

Gas sensor (MQ-135) support In PSLab Android application

Along with lots of sensors provided in the PSLab Android application, recently support for a new sensor – MQ-135 gas sensor has been added to the app. In this blog, I will discuss what is this gas sensor and how to use it with PSLab Android application

MQ-135 Gas sensor

The MQ-135 gas sensors are used in air quality control equipment and are suitable for detecting or measuring of NH3, NOx, Alcohol, Benzene, Smoke, CO2. 

The Pin layout of MQ-135 sensor

(Figure 1: MQ-135 pin layout)

How to Connect MQ-135 to PSLab Board

The following diagram shows how a user can connect MQ-135 sensor to a PSLab Board. 

(Figure 2: MQ-135 and PSLab connections)

As can be seen in the diagram above connect Voltage pin of MQ-135 sensor to one of the VDD pins on the PSLab board. Connect the Ground pin of MQ-135 sensor to one of GND pins on the PSLab board. And connect Analog Output pin to CH1 pin on the PSLab board. Once these connections are made user can connect PSLab board to their mobile phone and start reading data using Gas Sensor instrument in PSLab Android application

Gas Sensor Instrument in PSLab Android Application

To provide users an interface to read values collected by MQ-135 sensor connected to PSLAb board, a new instrument screen has been added to the PSLab Android application. The UI of the screen is shown below,

(Figure 3: Gas Sensor instrument UI)

As can be seen, the user is provided with a circular meter, a text box and a graph, all of which indicates the amount of different gases sensed by MQ-135 in PPM (parts per million) unit. The data is collected by very simple lines of codes. Since we are connecting Analog Output of MQ-135 to CH1 on PSLab board, we need to read the voltage at CH1 pin. Which would be in the range of 0 – input voltage (which is 3.3V in our case). To convert the voltage values to PPM, we map these output voltages to a range of 0 – 1024. This is done by following lines of code.

double volt = scienceLab.getVoltage("CH1", 1);
double ppmValue = (volt / 3.3) * 1024.0;

As provided in all the other instruments in PSLab Android application, Gas Sensor also has data logging and importing feature. User can record the data and store it as a CSV file and import previously recorded data into the PSLab application easily.

So in conclusion, now users can utilize and experiment with MQ-135 sensor effortlessly using PSLab Android application.

A working demo of this feature can be seen in the following video

https://drive.google.com/file/d/1-KxOaqE_Y5EYquMkebYpBOEc0d7GAdLS/view?usp=sharing

References:

Tags: PSLab, Android, GSoC 19, Sensors, Gas Sensor, MQ-135

Continue ReadingGas sensor (MQ-135) support In PSLab Android application

Enhancing Network Requests by Chaining or Zipping with RxJava

In Eventyay Attendee, making HTTP requests to fetch data from the API is one of the most basic techniques used. RxJava comes in as a great method to help us making asynchronous requests and optimize the code a lot. This blog post will deliver some advanced RxJava used in Eventyay Attendee.

  • Why using RxJava?
  • Advanced RxJava Technique – Chaining network calls with RxJava
  • Advanced RxJava Technique – Merging network calls with RxJava
  • Conclusions
  • Resources

WHY USING RXJAVA?

There are many reasons why RxJava is a great API in Android Development. RxJava is an elegant solution to control data flow in programming, where developers can cache data, get data, update the UI after getting the data, handle asynchronous tasks. RxJava also works really well with MVVM architectural pattern.

CHAINING NETWORK CALLS WITH RXJAVA

Chaining RxJava is a technique using flatMap() operator of Rxjava. It will use the result from one network call in order to make the next network call. 

In Eventyay Attendee, this technique is used when we want to update the user profile image. First, we need to upload the new profile image to the server in order to get the image URL, and then we use that URL to update the user profile

compositeDisposable += authService.uploadImage(UploadImage(encodedImage)).flatMap {
   authService.updateUser(user.copy(avatarUrl = it.url))
}.withDefaultSchedulers()
   .doOnSubscribe {
       mutableProgress.value = true
   }
   .doFinally {
       mutableProgress.value = false
   }
   .subscribe({
       mutableMessage.value = resource.getString(R.string.user_update_success_message)
       Timber.d("User updated")
   }) {
       mutableMessage.value = resource.getString(R.string.user_update_error_message)
       Timber.e(it, "Error updating user!")
   }

In conclusion, zipping RxJava helps to make HTTP requests more continuous and reduce unnecessary codes. 

ZIPPING NETWORK CALLS WITH RXJAVA

Zipping RxJava is a technique using zip() operator of Rxjava. It will wait for items from two or more Observables to arrive and then merge them together for emitting. This technique would be useful when two observables emit the same type of data.

In Eventyay Attendee, this technique is used when fetching similar events by merging events in the same location and merging events in the same event type.

var similarEventsFlowable = eventService.getEventsByLocationPaged(location, requestedPage, 3)
if (topicId != -1L) {
   similarEventsFlowable = similarEventsFlowable
       .zipWith(eventService.getSimilarEventsPaged(topicId, requestedPage, 3),
           BiFunction { firstList: List<Event>, secondList: List<Event> ->
               val similarList = mutableSetOf<Event>()
               similarList.addAll(firstList + secondList)
               similarList.toList()
           })
}

compositeDisposable += similarEventsFlowable
   .take(1)
   .withDefaultSchedulers()
   .subscribe({ response ->
       ...
   }, { error ->
       ...
   })

In conclusion, zipping RxJava helps running all the tasks in parallel and return all of the results in a single callback.

CONCLUSION

Even though RxJava is pretty hard to understand and master, it is a really powerful tool in Android Development and MVVM models. These techniques above are really simple to implement and they could improve the app by r

RESOURCES

Eventyay Attendee Source Code: 

https://github.com/fossasia/open-event-attendee-android/pull/2010

https://github.com/fossasia/open-event-attendee-android/pull/2117

RxJava Documentation: http://reactivex.io/documentation

Continue ReadingEnhancing Network Requests by Chaining or Zipping with RxJava

Two flavors of PSLab Android App to support Google Maps (in Play Store flavor) and Open Street maps (in Fdroid flavor)

What are the flavors of an App? And why are they needed in PSLab Android App?

While working on the PSLab Android Project, I ran into the need to create different variants of the app with different dependencies. In this blog, I have tried to explain the process of creating various flavors of the app in the easiest way possible. 

Android Allows Developers to create different variants of the same app with the same code base but having some functionalities different across the variants. These functionalities may include some special/pro features, some different dependencies, etc. Such variants are called flavors of the App. Most common flavors are Paid and Free version of the app.

In the PSLab Android Application, we needed to generate flavors, when we required to use Google Maps in the App. The app is also published on the Fdroid, which doesn’t allow dependencies of Google Maps. Hence 2 flavors of the app have been created, 

  1. Play Store Flavor (With Google Maps)
  2. F-Droid Flavor (With Open Street Maps)

Declaring Flavors in the build.gradle File

In order to create flavors of the app, first, we need to declare flavors in the Gradle file. In PSLab Android app we are creating 2 flavors, which are declared in the build.gradle file as under

flavorDimensions 'default'
productFlavors {
   fdroid {
       dimension = 'default'
   }
   playstore {
       dimension = 'default'
   }
}

flavorDimensions is used to package flavors if there are many flavors for an App. Since we have only two flavors fdroid and playstore, hence we are using single dimension default for both the flavors. Once this has been added to the build.gradle file we need to sync the gradle. 

After the Sync is complete, if we open the Build Variants tab from the left corner of the Android Studio, it would look something like this: 

(Figure 1: Build Variant Window of Android Studio)

As can be seen in the screenshot above, once the gradle is successfully synced, Android Studio automatically creates debug and release build variants for each flavor and we can easily toggle between variants and build/ run / make apk for each variant. Congratulations! We have successfully finished the first step towards creating flavors of an app.

Directory Structure after creating Flavors

Apart from creating the build variants of different flavors, Android studio also creates src/<flavor name> folders for us. Now if we want to add new activities and classes to these flavors we can create java, res, values folders inside this folder. We can define separate Manifest file as well for each flavor individually. The directory structure of the PSLab Android project after creating required packages inside the automatically generated src/fdroid and src/playstore folders looks like below,  

(Figure 2: Directory structure after creating flavors)

Defining Flavor specific dependencies

We can have some dependencies for one app flavor and some for others. For example, in PSLab Android app, we need Google Maps dependencies only in playstore flavor and Open Street Maps dependencies only in fdroid flavor. We can easily define flavor specific dependencies by adding flavor name before Implementation command in gradle file. Like below,

// Map libraries
fdroidImplementation "org.osmdroid:osmdroid-android:$rootProject.osmVersion"
fdroidImplementation "org.osmdroid:osmdroid-mapsforge:$rootProject.mapsforgeVersion"
fdroidImplementation "org.osmdroid:osmdroid-geopackage:$rootProject.geoPackageVersion"
playstoreImplementation "com.google.android.gms:play-services-maps:$rootProject.googleMapsVersion"

Same Activity/Class with different Flavor 

Now the main purpose of creating flavors is to have some different functionalities between the flavors. For that we need the base app to call different class/activity from the src/<flavor name> folder depending on the selected flavor. We will discuss this in reference to PSLab Android app. 

So, for PSLab android app we want app to open Google Maps in Play Store flavor and Open Street Maps in froid flavor. For this we need to create a duplicate Activity. Which means we will have two separate implementation of same Activity MapsActivity.java , one in the F-Droid source folder and one in playstore source folder. So MapsActivity.java will only be declared once in the src/main/AndroidManifest.xml file, but there will be two different classes for this activity in each flavor folder. Now when the main app will call MapsActivity.class from any intent depending on the selected build variant either playstore version of MapsActivity will be launched or the F-Droid version. So after creating two instances of the MapsActivity.java the directory structure would look something like given in the screenshot below,

(Figure 3: Directory structure after creating MapsActivity)

As can be seen in the directory structure, now both Play Store and F-Droid folders have their own instances of MapsActivity.java , and now we can easily implement code for Open Street Maps and Google Maps in the respective MapsActivity.java and we have two versions of the app working flawlessly. 

References

Tags: GSoC ‘19, PSLab, Android, Flavors, GoogleMaps, OpenStreetMaps, Build Variants

Continue ReadingTwo flavors of PSLab Android App to support Google Maps (in Play Store flavor) and Open Street maps (in Fdroid flavor)

Implementing Stripe payment in Eventyay Attendee

In Eventyay Attendee, getting tickets for events has always been a core function that we focus on. When searching for events based on location, autosuggestion based on user input really comes out as a great feature to increase the user experience. Let’s take a look at the implementation

  • Why using Stripe?
  • Implementing Stripe Payment in Eventyay Attendee
  • Conclusion
  • Resources

WHY USING STRIPE?

There are many great APIs to be taken into consideration for making payments but we choose Stripe as one of our payment gateways because of simple implementations, detailed documentation, a good number of supported card type and good security support

IMPLEMENTING STRIPE PAYMENT IN EVENTYAY ATTENDEE

Step 1: Setup dependency in the build.gradle

// Stripe
implementation 'com.stripe:stripe-android:10.3.0'

Step 2: Set up UI to take card information

The information needed for making payments are Card Number, CVC, Expiration Date, which can be made with simple UI (EditText, Spinner,…). Stripe support getting information with CardInputWidget but we made a custom UI for that. Here is the UI we created.

Step 3: Create a card and validate information

Stripe has an object called Card, which takes card number, expiration date and CVC number as parameter to detect the card type and validate the card information with function .validateCard()

PAYMENT_MODE_STRIPE -> {
   card = Card.create(rootView.cardNumber.text.toString(), attendeeViewModel.monthSelectedPosition,
       rootView.year.selectedItem.toString().toInt(), rootView.cvc.text.toString())

   if (!card.validateCard()) {
       rootView.snackbar(getString(R.string.invalid_card_data_message))
       false
   } else {
       true
   }
}

Step 4: Send the token to the server

If card information is valid, we can create a token from the Card and then send it to the server. The token will act as the identifier of the card in order for the server to charge the payment and create tickets for the user. 

private fun sendToken(card: Card) {
   Stripe(requireContext())
       .createToken(card, BuildConfig.STRIPE_API_KEY, object : TokenCallback {
           override fun onSuccess(token: Token) {
               val charge = Charge(attendeeViewModel.getId().toInt(), token.id, null)
               attendeeViewModel.chargeOrder(charge)
           }
           override fun onError(error: Exception) {
               rootView.snackbar(error.localizedMessage.toString())
           }
       })
}

Step 5: So the rest is already handled by the server. Android application will then just receive the response from the server to see if the order is charged successfully or not.

CONCLUSION

With Stripe, user can easily make payments to get tickets for events. Stripe is a great payment gateway as it is really easy to implement in Android. Hopefully, this blog post will help you create a great shopping cart app or any kind of application that requires fast, simple and easy payments.

RESOURCES

Eventyay Attendee Pull Request on Stripe: https://github.com/fossasia/open-event-attendee-android/pull/1863

Documentation from Stripe for Android: https://stripe.com/docs/mobile/android


Continue ReadingImplementing Stripe payment in Eventyay Attendee

The Robotic Arm Controller Feature in PSLab Android Application.

Recently while working on PSLab Android Project, a new feature was implemented to add a controller for a Robotic Arm in the Android Application. In this blog, I will explain what this new feature is, how it has been implemented and what are some of the functionalities of it.

What is Robotic Arm?

Robotic Arm, as the name suggests is a small arm-like structure, which moves with the help of 4 servo motors connected to it. The image of the robotic arm is as under, 

(Figure 1 : Robotic Arm)

What is the Robotic Arm Controller Feature in Android App?

As mentioned earlier, the robotic arm uses 4 servos for the movements. The aim of the controller feature in the Android app is to allow users to adjust the rotation of each servo with a very intuitive UI, so they can move the robotic arm as they wish. Further, in this blog, I will discuss UI, implementation and some cool features of the robotic Arm Controller.

User Interface


(figure 2: UI of the robotic arm controller)

In the screenshot above, there are 4 circular controllers for each respective servo. The user can use the knob or enter the values using the keyboard by tapping on the value at the center of the knobs. Each value indicates the value in degrees the user wishes to move that servo.

Timeline

Below the 4 servo controllers is a black timeline. There are 60 boxes in each of the 4 timelines. These 60 boxes indicates the seconds. So basically if the user wants the robotic arm to perform some set of actions sequentially, using these timelines, users can set the rotation for each servo at each second, then user can use the play button on the red control panel to play this timeline and the app will send the degree value at each second to the respective servo. A filled timeline would look something like below, 


(Figure 3: Timeline)

As can be seen, there are different values for each servo at each second, so when the user starts the timeline, values at each second will be sent to the respective servos. servo4() function of the ScienceLab class is called to set values for all for servos at each second.

How to add the values to the timeline?

There is a small handle on the top right corner of each servo controller, user can long-press the handle and drag and drop the values to desired seconds for respective servos.  This functionality can be found in this video between 0:33 to 0:43

This feature uses Android’s drag and drops listener. The code for this drag and drop function is as under,

private View.OnDragListener servo1DragListener = new View.OnDragListener() {
        @Override
        public boolean onDrag(View v, DragEvent event) {
            if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED) {
                View view = (View) event.getLocalState();
                TextView text = view.findViewById(R.id.degreeText);
                if (view.getId() == R.id.servo_1) {
                    ((TextView) v.findViewById(R.id.timeline_box_degree_text)).setText(text.getText());
                }
            }
            return true;
        }
    };

Such drag listeners are created for each servo controller.

Save Timeline Feature

Suppose user has set a whole 60 seconds timeline for some action, and the user wants to use that set of values again and again, it doesn’t make sense if the user has to set the values every time. So there is a feature to save the timeline as well. Whenever user has created some timeline, user can just click on the save button on the control panel and the timeline will be saved both as a CSV file and as a realm object. So the timeline will be visible in the DataLoggerActivity as well. Whenever user opens a logged timeline, values for the respective servo timeline will be set automatically.

The code to save the timeline is as below.  For each second a new ServoData object is created with degree values for all 4 servos at that second.

private void saveTimeline() {
        long block = System.currentTimeMillis();
        servoCSVLogger.prepareLogFile();
        servoCSVLogger.writeMetaData(getResources().getString(R.string.robotic_arm));
        String data = "Timestamp,DateTime,Servo1,Servo2,Servo3,Servo4,Latitude,Longitude\n";
        long timestamp;
        recordSensorDataBlockID(new SensorDataBlock(block, getString(R.string.robotic_arm)));

So first we create a block variable using the current timestamp, this block will be used for all the realm object stored for this timeline. We also prepare a CSV file and CSV header for the log. 

String degree1, degree2, degree3, degree4;
for (int i = 0; i < 60; i++) {
     timestamp = System.currentTimeMillis();
     degree1 = degree2 = degree3 = degree4 = "0";
     if (((TextView) servo1TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).getText().length() > 0) {
         degree1 = ((TextView) servo1TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).getText().toString();
     }

Now, we run a for loop for each 60 second timeline and store the set value of each servo in degree1, degree2, degree3 and degree4 variables. In the above code snippet only degree1 is shown, but the same thing is done for other values as well. Once we have 4 degree values for each servo at for a second, we store it as an ServoData Object in realm and also write it to the CSV file using the following lines of code,

recordSensorData(new ServoData(timestamp, block, degree1, degree2, degree3, degree4, lat, lon);
servoCSVLogger.writeCSVFile(data);

Here the data variable is a string with comma-separated values of the degree values of each servo.

Once the user saves the timeline the generated CSV looks something like below,


(Figure 4: Saved timeline CSV)

To set the timeline from the saved logged data, a small function just iterates over all the ServoData objects stored in the realm and set the value for the respective servo in the timeline. The function is as under,

private void setReceivedData() {
        ArrayList servoDataList = new ArrayList(recordedServoData);
        for (int i = 0; i < servoDataList.size(); i++) {
            ServoData servoData = (ServoData) servoDataList.get(i);
            ((TextView) servo1TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).setText(servoData.getDegree1());
            ((TextView) servo2TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).setText(servoData.getDegree2());
            ((TextView) servo3TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).setText(servoData.getDegree3());
            ((TextView) servo4TimeLine.getChildAt(i).findViewById(R.id.timeline_box_degree_text)).setText(servoData.getDegree4());
        }
    }

Conclusion

This feature enables the user to control the robotic arm with a very intuitive user interface. And the save timeline feature allows the user to use/share already stored timeline for some actions with other users. 

A small video to explain the save timeline functionality can be seen below,

References

Tags : GSoC ‘19, PSLab, Android, Robotic Arm

Continue ReadingThe Robotic Arm Controller Feature in PSLab Android Application.