FOSSASIA Virtual Summit 2021 Schedule and Highlights

We are very happy to share with you the schedule of the FOSSASIA Virtual Summit 2021!

  • 190+ speakers from 42 countries and 6 continents are joining us.
  • We are covering a diverse range of topics at the event such as Quantum computing, machine learning, open robotics, CAD and hardware development, search, databases, Covid-19 and open health, Blockchain, digital privacy, microservices, cloud, Kubernetes, kernel development, licensing and many more.
  • More than 180 sessions, talks, panels and workshops are taking place online. 
  • Plus, trainers conduct 28 hours of hands-on onsite sessions at the Lifelong Learning Institute in Singapore.

Get your free ticket here!

The summit will spread out over the week of March 13-21 and will run on our open source virtual event platform eventyay.

In the virtual exhibition you can meet our sponsors and partners like Microsoft, Oracle, MySQL, OpenTAP Keysight, Linux Professional Institute, Elasticsearch, OpnTec, Cloud Native Computing Foundation, FreeBSD, UI-licious, Lifelong Learning Institute Singapore and the UNESCO.

And you can get together with developers and contributors from Free and Open Source projects, makerspaces, developer clubs and university IT groups. At summit hubs across Asia we are connecting to participants online and locally.

FOSSASIA Summit Highlights

  • 5 keynotes from Hong Phuc Dang (Founder, FOSSASIA) and Mario Behling (Co-Founder FOSSASIA, CEO OpnTec), Frank Karlitschek (CEO, Nextcloud), Greg Kroah-Hartman (Linux Kernel Maintainer), Brian Behlendorf (Executive Director, Hyperledger) and Bunnie Huang (CEO, Precursor)
  • 150+ sessions covering tech areas of Quantum computing, PyTorch, Scikit-learn, pocket science, open robotics, FreeCAD, hardware development, search, databases, Debian packaging, Covid-19, Hyperledger, Open Source event solutions, digital privacy, microservices, cloud, Kubernetes, and Linux Kernel development.
  • 30+ hands-on workshops focusing on getting started with Python, creating MySQL shell utilities and custom plugins, setting up PostgreSQL databases, creating test automation, machine learning, and science measurements.
  • Panel discussions about Open Source licensing vs. available source licenses, firmware development, digital sovereignty, Blockchain and diversity in tech.
  • Virtual exhibition with video rooms and exhibitor tours where participants can connect with companies and active tech communities from across Asia.

FOSSASIA Summit Exhibition and Hubs

Apart from company partners you can meet communities, students and developers from Asia and around the world in our virtual exhibition. Groups include OpenFIESTA Tsinghua University, Shenzhen DIY Community, Open Source Hong Kong, OpenStack Indonesia, Mozilla Philippines, Ubuntu Korea, FOSS Myanmar, KDE, BuildingBloCS Singapore, SUTD, Python Software Foundation, LibreOffice, KiwiTCMS, Nextcloud Sourcefabric, LambdaChip, and more.

Hubs bring attendees together locally or online. Participating hubs include the International Organisation of Software Developers, Open Source Club at Saintgits Engineering College Kerala, Biohacking Space Peshawar Pakistan, Society for Data Science at BIT Mesra India, Mar Athanasius College of Engineering India, Developer Student Community in BHILAI Institute of Technology Durg and Team Aveon Racing of BIT Mesra India.

FOSSASIA Summit Daily Focus Topics

Each day has different focus topics and tracks.

Sat, 13 Mar: Robotics, Open Hardware, Pocket Science Lab, Python/Web development, Digital Sovereignty

Sun, 14 Mar: Open Hardware, open science, lightning talks, Python/Web development

Mon, 15 Mar: Blockchain, hardware, design, learn how to solder

Tue, 16 Mar: DevOps, cloud, containers, Kernel & Platform, Quantum computers, Kubernetes

Wed, 17 Mar: Databases, MySQL, PostgreSQL, Monitoring

Thu, 18 Mar: Security, Privacy,  Digital Sovereignty, IoT, cloud, Blockchain, Open Source voice assistants

Fri, 19 Mar: Artificial Intelligence, PyTorch, Visdom, search, scikit, NLP

Sat, 20 Mar: DevOps, Python bot programming, Container Regisitries, OSS Licenses vs. available source, FOSS Community in Asia, OpenStreetMap, Python/Web development

Sun, 21 Mar: Lightning talks, Open Health, Covid-19 apps around the world, Fdroid, platform, smart devices

Continue ReadingFOSSASIA Virtual Summit 2021 Schedule and Highlights

Internships for Python / EmberJS Developers for eventyay.com

As a FOSSASIA intern working on eventyay.com, you’ll collaborate together with our team to develop the Open Event project that runs the eventyay website. We use Flask as a backend and  Ember.js as a frontend technology. The team follows our best practices and uses scrum emails for the daily standup and Gitter for chat communication.

Before you apply please set up the Open Event project first on a Linux system and make some pull requests to show your ability of contributing code to the backend and frontend.

About the team

  • We are a team working with a community of FOSS developers
  • We work remotely in different timezones
  • Our system is built using Ember (frontend) and Flask/Python (backend)
  • We have an informal and collaborative environment
  • We embrace Continuous Integration

Responsibilities

  • Provide daily code commits
  • Write unit tests for all portions of our application
  • Support community developers and review PRs
  • Work according to FOSSASIA Best Practices
  • Provide daily scrums and communicate on chat

Requirements

  • Willingness working independently in a remote setting
  • Understanding and ability to code in HTML, CSS, and Javascript
  • Understanding of Flask/Python
  • Eagerness to learn and code Ember.js
  • Enjoy writing tested and modular code
  • Self-motivated and independent

Code

Please check out the project on GitHub before applying.
Open Event Server: https://github.com/fossasia/open-event-server
Open Event Frontend: https://github.com/fossasia/open-event-frontend/

Salary

Attractive Salary – Negotiable 

Other Benefits

  • Visit Singapore and participate in annual FOSSASIA Summit
  • Participate in Open Source meetups and conferences
  • Work with a community of enthusiastic software developers

Location

Remote India

Contact

Please apply through our form here.

Links

FOSSASIA Best Practices: https://blog.fossasia.org/open-source-developer-guide-and-best-practices-at-fossasia/

Continue ReadingInternships for Python / EmberJS Developers for eventyay.com

Creating an awesome ‘About Us’ page for the Open Event Organizer Android App

Open Event Organizer App (Eventyay Organizer App) is an Android app based on the Eventyay platform. It contains various features using which organizers can manage their events.

This article will talk about a library which can help you create great about pages for Android apps without the need of making custom layouts.

It is the Android About Page library.

Let’s go through the process of its implementation in the Eventyay Organizer App.

First add the dependency in the app level build.gradle file:

implementation 'com.github.medyo:android-about-page:1.2.5'

Creating elements to be added:

Element legalElement = new Element();
legalElement.setTitle("Legal");

Element developersElement = new Element();      
developersElement.setTitle(getString(R.string.developers));

Element shareElement = new Element();
shareElement.setTitle(getString(R.string.share));

Element thirdPartyLicenses = new Element();       
thirdPartyLicenses.setTitle(getString(R.string.third_party_licenses));

Setting image, description and adding items in the About Page:

AboutPage aboutPage = new AboutPage(getContext())
            .isRTL(false)
            .setImage(R.mipmap.ic_launcher)            
            .setDescription(getString(R.string.about_us_description))
            .addItem(new Element("Version " + BuildConfig.VERSION_NAME, R.drawable.ic_info))
            .addGroup("Connect with us")
            .addGitHub("fossasia/open-event-organizer-android")
            .addPlayStore(getContext().getPackageName())
            .addWebsite(getString(R.string.FRONTEND_HOST))
            .addFacebook(getString(R.string.FACEBOOK_ID))
            .addTwitter(getString(R.string.TWITTER_ID))
            .addYoutube(getString(R.string.YOUTUBE_ID))
            .addItem(developersElement)
            .addItem(legalElement)
            .addItem(shareElement);

if (BuildConfig.FLAVOR.equals("playStore")) {    
    aboutPage.addItem(thirdPartyLicenses);
}

View aboutPageView = aboutPage.create();

Now add the aboutPageView in the fragment.

To make the values configurable from build.gradle, add this is the defaultConfig:

resValue "string", "FACEBOOK_ID", "eventyay"
resValue "string", "TWITTER_ID", "eventyay"
resValue "string", "YOUTUBE_ID", "UCQprMsG-raCIMlBudm20iLQ"

That’s it! The About Page is now ready.

Resources:

Library used: Android About Page

Pull Request: #1904

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

Continue ReadingCreating an awesome ‘About Us’ page for the Open Event Organizer Android App

Mapbox implementation in Open Event Organizer Android App

Open Event Organizer Android App is used by event organizers to manage events on the Eventyay platform. While creating or updating an event, location is one of the important factors which needs to be added so that the attendees can be informed of the venue.

Here, we’ll go through the process of implementing Mapbox Places Autocomplete for event location in the F-Droid build variant.

The first step is to create an environment variable for the Mapbox Access Token. 

def MAPBOX_ACCESS_TOKEN = System.getenv('MAPBOX_ACCESS_TOKEN') ?: "YOUR_ACCESS_TOKEN"

Add the Mapbox dependency:

fdroidImplementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-places-v8:0.9.0'

Fetching the access token in EventDetailsStepOne as well as UpdateEventFragment:

ApplicationInfo applicationInfo = null;
        try {
            applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            Timber.e(e);
        }
        Bundle bundle = applicationInfo.metaData;

        String mapboxAccessToken = bundle.getString(getString(R.string.mapbox_access_token));

The app should not crash if the access token is not available. To ensure this, we need to put a check. Since, the default value of the access token is set to “YOUR_ACCESS_TOKEN”, the following code will check whether a token is available or not:

if (mapboxAccessToken.equals("YOUR_ACCESS_TOKEN")) {
    ViewUtils.showSnackbar(binding.getRoot(),                             R.string.access_token_required);
    return;
}

Initializing the PlacesAutocompleteFragment:

PlaceAutocompleteFragment autocompleteFragment = PlaceAutocompleteFragment.newInstance(
                mapboxAccessToken, PlaceOptions.builder().backgroundColor(Color.WHITE).build());

getFragmentManager().beginTransaction()
    .replace(R.id.fragment, autocompleteFragment)
    .addToBackStack(null)
    .commit();

Now, a listener needs to be set up to get the selected place and set the various fields like latitude, longitude, location name and searchable location name.

autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
                @Override
                public void onPlaceSelected(CarmenFeature carmenFeature) {
                    Event event = binding.getEvent();
                    event.setLatitude(carmenFeature.center().latitude());
                    event.setLongitude(carmenFeature.center().longitude());
                    event.setLocationName(carmenFeature.placeName());
                    event.setSearchableLocationName(carmenFeature.text());
                    binding.form.layoutLocationName.setVisibility(View.VISIBLE);
                    binding.form.locationName.setText(event.getLocationName());
                    getFragmentManager().popBackStack();
                }

                @Override
                public void onCancel() {
                    getFragmentManager().popBackStack();
                }
            });

This brings the process of implementing Mapbox SDK to completion.

GIF showing the working of Mapbox Places Autocomplete

Resources:

Documentation: Mapbox Places Plugin

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

Continue ReadingMapbox implementation in Open Event Organizer Android App

Implementation of Android App Links in Open Event Organizer App

Android App Links are HTTP URLs that bring users directly to specific content in an Android app. They allow the website URLs to immediately open the corresponding content in the related Android app.

Whenever such a URL is clicked, a dialog is opened allowing the user to select a particular app which can handle the given URL.

In this blog post, we will be discussing the implementation of Android App Links for password reset in Open Event Organizer App, the Android app developed for event organizers using the Eventyay platform.

What is the purpose of using App Links?

App Links are used to open the corresponding app when a link is clicked.

  • If the app is installed, then it will open on clicking the link.
  • If app is not installed, then the link will open in the browser.

The first steps involve:

  1. Creating intent filters in the manifest.
  2. Adding code to the app’s activities to handle incoming links.
  3. Associating the app and the website with Digital Asset Links.

Adding Android App Links

First step is to add an intent-filter for the AuthActivity.

<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"
         android:pathPrefix="/reset-password" />
</intent-filter>

Here, FRONTEND_HOST is the URL for the web frontend of the Eventyay platform.

This needs to be handled in AuthActivity:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    handleIntent(intent);
}
private void handleIntent(Intent intent) {
    String appLinkAction = intent.getAction();
    Uri appLinkData = intent.getData();

    if (Intent.ACTION_VIEW.equals(appLinkAction) && appLinkData != null) {
        LinkHandler.Destination destination = LinkHandler.getDestinationAndToken(appLinkData.toString()).getDestination();
        String token = LinkHandler.getDestinationAndToken(appLinkData.toString()).getToken();

        if (destination.equals(LinkHandler.Destination.RESET_PASSWORD)) {
            getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment_container,                     
                         ResetPasswordFragment.newInstance(token))
            .commit();
        }
    }
}

 Call the handleIntent() method in onCreate():

handleIntent(getIntent());

Get the token in onCreate() method of ResetPasswordFragment:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getArguments() != null)
        token = getArguments().getString(TOKEN_KEY);
}

Set the token in ViewModel:

if (token != null)
    resetPasswordViewModel.setToken(token);

The setToken() method in ViewModel:

if (token != null)
    resetPasswordViewModel.setToken(token);

LinkHandler class for handling the links:

package com.eventyay.organizer.utils;

public class LinkHandler {

    public Destination destination;
    public String token;

    public LinkHandler(Destination destination, String token) {
        this.destination = destination;
        this.token = token;
    }

    public static LinkHandler getDestinationAndToken(String url) {
        if (url.contains("reset-password")) {
            String token = url.substring(url.indexOf('=') + 1);
            return new LinkHandler(Destination.RESET_PASSWORD, token);
        } else if (url.contains("verify")) {
            String token = url.substring(url.indexOf('=') + 1);
            return new LinkHandler(Destination.VERIFY_EMAIL, token);
        } else
            return null;
    }

    public Destination getDestination() {
        return destination;
    }

    public String getToken() {
        return token;
    }

    public enum Destination {
        VERIFY_EMAIL,
        RESET_PASSWORD
    }
}

enum is used to handle links for both, password reset as well as email verification.

Finally, the unit tests for LinkHandler:

package com.eventyay.organizer.utils;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static org.junit.Assert.assertEquals;

@RunWith(JUnit4.class)
public class LinkHandlerTest {

    private String resetPassUrl = "https://eventyay.com/reset-password?token=12345678";
    private String verifyEmailUrl = "https://eventyay.com/verify?token=12345678";

    @Test
    public void shouldHaveCorrectDestination() {
        assertEquals(LinkHandler.Destination.RESET_PASSWORD,
            LinkHandler.getDestinationAndToken(resetPassUrl).getDestination());
        assertEquals(LinkHandler.Destination.VERIFY_EMAIL,
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getDestination());
    }

    @Test
    public void shouldGetPasswordResetToken() {
        assertEquals(LinkHandler.Destination.RESET_PASSWORD,
            LinkHandler.getDestinationAndToken(resetPassUrl).getDestination());
        assertEquals("12345678",
            LinkHandler.getDestinationAndToken(resetPassUrl).getToken());
    }

    @Test
    public void shouldGetEmailVerificationToken() {
        assertEquals(LinkHandler.Destination.VERIFY_EMAIL,
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getDestination());
        assertEquals("12345678",
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getToken());
    }
}

Resources:

Documentation: Link

Further reading: Android App Linking

Pull Request: feat: Add app link for password reset

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

Continue ReadingImplementation of Android App Links in Open Event Organizer App

Implementation of scanning in F-Droid build variant of Open Event Organizer Android 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.

Various features include:

  1. Event creation.
  2. Ticket management.
  3. Attendee list with ticket details.
  4. Scanning of participants etc.

The Play Store build variant of the app uses Google Vision API for scanning attendees. This cannot be used in the F-Droid build variant since F-Droid requires all the libraries used in the project to be open source. Thus, we’ll be using this library: https://github.com/blikoon/QRCodeScanner 

We’ll start by creating separate ScanQRActivity, ScanQRView and activity_scan_qr.xml files for the F-Droid variant. We’ll be using a common ViewModel for the F-Droid and Play Store build variants.

Let’s start with requesting the user for camera permission so that the mobile camera can be used for scanning QR codes.

public void onCameraLoaded() {
    if (hasCameraPermission()) {
        startScan();
    } else {
        requestCameraPermission();
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode != PERM_REQ_CODE)
            return;

    // If request is cancelled, the result arrays are empty.
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        cameraPermissionGranted(true);
    } else {
        cameraPermissionGranted(false);
    }
}



@Override
public boolean hasCameraPermission() {
    return ContextCompat.checkSelfPermission(this, permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}

@Override
public void requestCameraPermission() {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERM_REQ_CODE);
}


@Override
public void showPermissionError(String error) {
    Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
}

public void cameraPermissionGranted(boolean granted) {
    if (granted) {
        startScan();
    } else {
        showProgress(false);
        showPermissionError("User denied permission");
    }
}

After the camera permission is granted, or if the camera permission is already granted, then the startScan() method would be called.

@Override
public void startScan() {
    Intent i = new Intent(ScanQRActivity.this, QrCodeActivity.class);
    startActivityForResult(i, REQUEST_CODE_QR_SCAN);
}

QrCodeActivity belongs to the library that we are using.

Now, the processing of barcode would be started after it is scanned. The processBarcode() method in ScanQRViewModel would be called.

public void onActivityResult(int requestCode, int resultCode, Intent intent) {

    if (requestCode == REQUEST_CODE_QR_SCAN) {
        if (intent == null)
            return;

        scanQRViewModel.processBarcode(intent.getStringExtra
            ("com.blikoon.qrcodescanner.got_qr_scan_relult"));

    } else {
        super.onActivityResult(requestCode, resultCode, intent);
    }
}

Let’s move on to the processBarcode() method, which is the same as the Play Store variant.

public void processBarcode(String barcode) {

    Observable.fromIterable(attendees)
        .filter(attendee -> attendee.getOrder() != null)
        .filter(attendee -> (attendee.getOrder().getIdentifier() + "-" + attendee.getId()).equals(barcode))
        .compose(schedule())
        .toList()
        .subscribe(attendees -> {
            if (attendees.size() == 0) {
                message.setValue(R.string.invalid_ticket);
                tint.setValue(false);
            } else {
                checkAttendee(attendees.get(0));
            }
        });
}

The checkAttendee() method:

private void checkAttendee(Attendee attendee) {
    onScannedAttendeeLiveData.setValue(attendee);

    if (toValidate) {
        message.setValue(R.string.ticket_is_valid);
        tint.setValue(true);
        return;
    }

    boolean needsToggle = !(toCheckIn && attendee.isCheckedIn ||
        toCheckOut && !attendee.isCheckedIn);

    attendee.setChecking(true);
    showBarcodePanelLiveData.setValue(true);

    if (toCheckIn) {
        message.setValue(
            attendee.isCheckedIn ? R.string.already_checked_in : R.string.now_checked_in);
        tint.setValue(true);
        attendee.isCheckedIn = true;
    } else if (toCheckOut) {
        message.setValue(
            attendee.isCheckedIn ? R.string.now_checked_out : R.string.already_checked_out);
        tint.setValue(true);
        attendee.isCheckedIn = false;
    }

    if (needsToggle)
        compositeDisposable.add(
            attendeeRepository.scheduleToggle(attendee)
                .subscribe(() -> {
                    // Nothing to do
                }, Logger::logError));
}

This would toggle the check-in state of the attendee.

Resources:

Library used: QRCodeScanner

Pull Request: feat: Implement scanning in F-Droid build variant

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

Continue ReadingImplementation of scanning in F-Droid build variant of Open Event Organizer Android App

Implement JWT Refresh token in Open Event Attendee App

In open event attendee android app earlier only access token is used, which causes HTTP 401 many time due to expiry of the token. It needs to re sign-in for the user. Now we have implemented refresh token authorization with the open event server using retrofit and OkHttp. Retrofit is one of the most popular HTTP client for Android. When calling API, we may require authentication using a token. Usually, the token is expired after a certain amount of time and needs to be refreshed using the refresh token. The client would need to send an additional HTTP request in order to get the new token. Imagine you have a collection of many different APIs, each of them requires token authentication. If you have to handle refresh token by modifying your code one by one, it will take a lot of time and of course, it’s not a good solution. In this blog, I’m going to show you how to handle refresh token on each API calls automatically if the token expires.

  • How refresh token works?
  • Add authenticator to OkHttp
  • Network call and handle response
  • Conclusion
  • Resources 

Let’s analyze every step in detail.

How Refresh Token Works?

Whether tokens are opaque or not is usually defined by the implementation. Common implementations allow for direct authorization checks against an access token. That is, when an access token is passed to a server managing a resource, the server can read the information contained in the token and decide itself whether the user is authorized or not (no checks against an authorization server are needed). This is one of the reasons tokens must be signed (using JWS, for instance). On the other hand, refresh tokens usually require a check against the authorization server. 

Add Authenticator to OkHTTP

OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.

class TokenAuthenticator: Authenticator {

    override fun authenticate(route: Route?, response: Response): Request? {

        // Refresh your access_token using a synchronous api request
        val newAccessToken = service.refreshToken();

        // Add new header to rejected request and retry it
        return response.request().newBuilder()
            .header(AUTHORIZATION, newAccessToken)
            .build()
    }
}

Add the authenticatior to OkHttp:

val builder = OkHttpClient().newBuilder()
            .authenticator(TokenAuthenticator())

Network Call and Handle Response

API call with retrofit:

@POST("/auth/token/refresh")
    fun refreshToken(): Single<RefreshResponse>

Refresh Response:

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class RefreshResponse(
    val refreshToken: String
)

In a Nutshell

Refresh tokens improve security and allow for reduced latency and better access patterns to authorization servers. Implementations can be simple using tools such as JWT + JWS. If you are interested in learning more about tokens (and cookies), check our article here.

Resources

  1. Android – Retrofit 2 Refresh Access Token with OkHttpClient and Authenticator: https://www.woolha.com/tutorials/android-retrofit-2-refresh-access-token-with-okhttpclient-and-authenticator
  2. Refresh Tokens: When to Use Them and How They Interact with JWTs: https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

Tags

Eventyay, open-event, FOSSASIA, GSoC, Android, Kotlin, Refresh tokens

Continue ReadingImplement JWT Refresh token in Open Event Attendee App

Apply Shimmer Effect for Progress 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.

Shimmer effect was created by Facebook to indicate a loading status, so instead of using ProgressBar or the usual loader use Shimmer for a better design and user interface. They also open-sourced a library called Shimmer both for Android and iOS so that every developer could use it for free.

  • Add Shimmer library
  • Create a placeholder for shimmer
  • Apply the effect with live data
  • Conclusion
  • Resources

Let’s analyze every step in detail.

Add Shimmer Library 

Add Shimmer Library to build.gradle :

// Cards Shimmer Animation
implementation 'com.facebook.shimmer:shimmer:0.5.0'

Create reasouces

Add shimmer background color to colors.xml:

<color name="shimmer_background">#dddddd</color>

Create a placeholder layout:

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/layout_margin_medium"
    app:cardBackgroundColor="@android:color/white"
    app:cardCornerRadius="@dimen/card_corner_radius"
    app:cardElevation="@dimen/layout_margin_none"
    android:foreground="?android:attr/selectableItemBackground"
    android:background="@android:color/white">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/layout_margin_large"
        android:layout_gravity="center"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="@dimen/item_image_view_160dp"
            android:scaleType="centerCrop"
            android:background="@color/shimmer_background"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_marginTop="@dimen/layout_margin_medium"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <View
                android:layout_width="@dimen/card_width_45dp"
                android:layout_height="@dimen/item_image_view"
                android:background="@color/shimmer_background"
                android:layout_marginEnd="@dimen/padding_large"
                android:layout_marginRight="@dimen/padding_large"
                android:gravity="center_horizontal"
                android:orientation="vertical"
                android:layout_marginTop="@dimen/padding_medium">
            </View>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingBottom="@dimen/padding_large"
                android:paddingTop="@dimen/padding_medium">

                <View
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/view_height_25dp"
                    android:layout_marginBottom="@dimen/layout_margin_small"
                    android:background="@color/shimmer_background"/>

                <View
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/view_height_25dp"
                    android:background="@color/shimmer_background"/>

            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</androidx.cardview.widget.CardView>

Add shimmer in your fragment/activity layout resources file:

<com.facebook.shimmer.ShimmerFrameLayout
        android:id="@+id/shimmer_view_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="15dp"
        android:orientation="vertical"
        shimmer:duration="800">

        <!-- Adding 7 rows of placeholders -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
            <include layout="@layout/data_placeholder_layout" />
        </LinearLayout>

    </com.facebook.shimmer.ShimmerFrameLayout>

Apply Shimmer with LiveData

Declare live data variable in view model:

private val mutableShowShimmer = MediatorLiveData<Boolean>()
val showShimmer: MediatorLiveData<Boolean> = mutableShowShimmer

Handle progress in the view model:

compositeDisposable += eventPagedList
            .subscribeOn(Schedulers.io())
            .doOnSubscribe {
                mutableShowShimmer.value = true
            }.finally {
     mutableShowShimmer.value = false
}

Handle shimmer with observing the live data in fragment/activity:

eventsResultsViewModel.showShimmer
            .nonNull()
            .observe(viewLifecycleOwner, Observer {
                if (it) {
                    rootView.shimmer_view_container.startShimmer()
                } else {
                    rootView.shimmer_view_container.stopShimmer()
                }
                rootView.shimmer_view_container.isVisible = it
            })

GIF

Resources

Show shimmer progress in Android: https://medium.com/mindorks/android-design-shimmer-effect-fa7f74c68a93

Tags

Eventyay, open-event, Shimmer, Facebook, MVVM, Fossasia, GSoC, Android, Kotlin

Continue ReadingApply Shimmer Effect for Progress in Open Event Attendee Application

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