Jugaad Fest Hyderabad on 30th September 2018 – Hack, Fix, Trick It, or Simply Make Open Tech Work for You

Jugaadfest is a gathering of developers and FOSS contributors who get together to learn, share, and hack on Open Source projects for one day with the goal of making things work. 30+ mentors and successful FOSSASIA Google Summer of Code students from India and Europe will guide you during the day. At Jugaadfest you can:

  • join various hands-on activities, e.g. set up FOSSASIA apps, install conversational voice assistants, do experiments with PSLab
  • learn from developers how to find innovative fixes or simple work-arounds to get FOSSASIA prototypes working
  • learn how to build up an awesome developer profile that improves your job chances by collaborating in the FOSS community
  • improve your coding and tech skills while contributing to open source software and hardware projects
  • enjoy presentations conducted by veteran developers

Jugaadfest is a hands-on event. Please bring your laptop.

We kick-off in the morning with short introductions by developers of FOSS projects. After that participants have the chance to join teams at different tables and start learning and collaborating on Open Tech projects.

At the lunch break participants mingle and discuss what they have learned before they continue browsing  projects. At the end of the event day we ask developers to share their learnings in short lightning talks.

Certificates: Participants of Jugaadfest receive a certificate of participation. (Prerequisite: Full-day participation)

Contributors Discount Tickets

You are a contributor to FOSSASIA project or other Open Source communities? Apply for a discount ticket. There is a limited number of contributor tickets for 90, 80, and 70% discounts available. Please fill in the form here: https://jugaadfest.com/community-ticket

AGENDA

Jugaadfest Hyderabad

  • Location: BVRIT HYDERABAD College of Engineering for Women
  • Date: Sunday, September 30, 2018
  • 8:30 Registration and Breakfast Snacks
  • 9:30 Introduction and Opening Sessions
  • 9:45 How to Participate in FOSSASIA Coding Programs, Google Summer of Code and Codeheat
  • 10:00 Project Presentations: SUSI.AI, Open Event Solutions, Pocket Science Lab, Meilix, Badgeyay
  • 11:00 Developers Join Project Tables
  • 12:30 Lunch Break
  • 13:30 Developers Learn and Participate at Project Tables
  • 16:00 How to Collaborate in International Open Source Projects and Global Enterprises Using Best Practices
  • 16:15 Lightning Talks
  • 16:30 What’s Next?
  • 17:00 End of Event

Social Event (Ticket Required)

  • Date: Sunday, September 30, 2018
  • 19:30 Dinner in Hyderabad City

Jugaadfest Extended (Contributors – Invitation Only)

  • Location: Co-Working Hyderabad
  • Developer Workshops: Monday October 1 – Wednesday, October 3, 2018
  • Brunch: Thursday, October 4, 2018

LINKS

UNESCO Hackathon in Ho Chi Minh City, Vietnam

Join UNESCO Hackathon in Ho Chi Minh City on Oct 13 -14, 2018 to learn about climate change and environmental challenges in Vietnam, meet regional sustainable development experts and listen to their successful startup stories by doing sustainable and green businesses.

There is no restriction of age or backgrounds of participants. Students, NGOs reps, journalists, bloggers, developers and all open source contributors are invited to join! The hackathon is open for all and awesome prizes are waiting for you!

Each winner of the three top teams will receive these prizes.

The objective of the hackathon is to propose innovative solutions that help journalists to monitor and report on climate change and sustainable development issues in Asia and the Pacific.

The participants will be introduced to UNESCO’s Guidebook for Journalists Reporting on Climate Change and Sustainable Development in Asia and the Pacific which includes information and knowledge on climate science, related international and regional treaties and policy frameworks including the 2030 Agenda for Sustainable development, and tips for journalists for finding and telling stories.

Time and Location

Time: Saturday October 13 – Sunday October 14, 2018
Location: Officience Vietnam, 16A Le Hong Phong, Ward 12, District 10, Ho Chi Minh City

Why should I participate?

  • Learn how to create a chatbot within an hour with SUSI.AI
  • Carry out experiment with electronic devices PSlab.io
  • Update yourselves with knowledge of technology and sustainable development in Vietnam
  • Meet special guest speakers from the UNESCO, Embassy of Sweden and many more.
  • Improve your language skills, presentation skills and build up your leadership abilities
  • Receive certificates from UNESCO, T-shirts, swags, and special prizes from the sponsors

How do I know if I am qualified to join?

The hackathon is open for everyone, especially for those:

  • Curious and willing to learn new things
  • Interested in technology and sustainable development
  • Like to make new friends and expand their networks
  • Able to communicate in English
  • No prior coding skill is required

How do I sign up?

  1. Get your ticket to the Event on eventyay.com
  2. Sign up on Devpost as you will need to submit your final hack there.
  3. Join the Gitter channel at https://gitter.im/fossasia/hackathon (requires login with Github).
  4. Find team members and form a team with at least 2 members and maximum 4 contributors. You are also welcome to sign up and then wait until the Presentation of Ideas on Saturday before deciding to join a team, however we’d encourage you to form/join a team in advance if you already have an idea that you’d like to work on.
  5. Join the event at the Officience Vietnam on Saturday, Oct 13 at the opening at 8.30am until 9.00pm and on Sunday, Oct 14 from 8.00am until 5.00pm.

Visit the website at unesco.sciencehack.asia and stay connected, join the event on Facebook and follow FOSSASIA on Twitter.

Prizes

All participants will receive a gift bag (Tshirt, sticker, wristband and lanyard) and a certificate from UNESCO for participating in the hacking.

Each winner of the three top teams will be awarded special gift package including:

  • A Pocket Science Lab – hardware device by FOSSASIA
  • Special Developer Helmet by FOSSASIA
  • Winner Medal
  • Team Building Buffet Dinner Voucher
  • Team Hack-Away Mekong Delta Tour (floating Market, hackerspace, hotel)
  • Tiki Techie Gift Voucher
  • 6-month coworking space membership

Links

UNESCO Hackathon: https://unesco.sciencehack.asia

Tickets: https://eventyay.com/e/dbd7567d

Project Signup: https://unesco-hackathon.devpost.com

Communication Channel: https://gitter.im/fossasia/hackathon

Facebook: https://www.facebook.com/events/1713085622073681

FOSSASIA: https://twitter.com/fossasia

Adding different metrics sections to the start page

In the initial version of the SUSI.AI Skill CMS we simply displayed all the skills present in the system in the form of cards. Once the skill analytics was incorporated into the CMS we got a bunch of skill statistics and thus we enhanced the start page by incorporating horizontally scrollable skill cards as per skill metrics like top rated skills, most used skills, skills which have received the most feedback etc. I worked on adding the skills with most feedback section and the section for the top games. This post will majorly deal with how the metrics sections are implemented on the start page and how any new metrics can be incorporated into the system and thus displayed on the CMS.

About the API

/cms/getSkillMetricsData.json?language=${language}

Sample API call:

https://api.susi.ai/cms/getSkillMetricsData.json?language=en

 

This will return a JSON which contains the skill data for all the metrics.

{
 "accepted": true,
 "model": "general",
 "group": "All",
 "language": "en",
 "metrics": {
        "newest": [...],
     "rating": [...],
      ...
 }
 "message": "Success: Fetched skill data based on metrics",
   "session": {"identity": {
           "type": "host",
          "name": "162.158.23.7_68cefd16",
          "anonymous": true
   }}
}

 

All of the data for several metics comes from the metrics object of the response which in turn contains arrays of skill data for each metric.

CMS Implementation

Once the BrowseSkill component is mounted we make an API call to the server to fetch all the data and save it to the component state, this data is then fed to the ScrollCardList component as props and the scroll component is rendered with appropriate data for different metrics.

loadMetricsSkills = () => {
   let url;
   url =
           urls.API_URL +
           '/cms/getSkillMetricsData.json?language=' +
           this.state.languageValue;
   let self = this;
   $.ajax({
           url: url,
           dataType: 'jsonp',
           jsonp: 'callback',
           crossDomain: true,
           success: function(data) {
                   self.setState({
                           skillsLoaded: true,
                           staffPicksSkills: data.metrics.staffPicks,
                           topRatedSkills: data.metrics.rating,
                           topUsedSkills: data.metrics.usage,
                           latestUpdatedSkills: data.metrics.latest,
                           newestSkills: data.metrics.newest,
                           topFeedbackSkills: data.metrics.feedback,
                           topGames: data.metrics['Games, Trivia and Accessories'],
                   });
           },
           error: function(e) {
                   console.log('Error while fetching skills based on top metrics', e);
                   return self.loadMetricsSkills();
           },
   });
};

 

We are using a single component for skill metrics and skill listing which show up on applying any filter or visiting any category. Thus we think of a condition when the skill metrics are to be displayed and conditionally render the metrics section depending on the condition.

So the metrics section shows up only when we have not visited any category or language page, there’s no search query in the search bar, there’s no rating refine filter applied and no time filter applied.

let metricsHidden =
         this.props.routeType ||
         this.state.searchQuery.length > 0 ||
         this.state.ratingRefine ||
         this.state.timeFilter;

 

Depending on the section you want to display, pass appropriate data as props to the SkillCardScrollList component, say we want to display the section with most feedback

{this.state.topFeedbackSkills.length &&
!metricsHidden ? (
   <div style={metricsContainerStyle}>
           <div
                   style={styles.metricsHeader}
                   className="metrics-header"
           >
                   <h4>
                           {'"SUSI, what are the skills with most feedback?"'}
                   </h4>
           </div>
           {/* Scroll Id must be unique for all instances of SkillCardList*/}
           {!this.props.routeType && (
                   <SkillCardScrollList
                           scrollId="topFeedback"
                           skills={this.state.topFeedbackSkills}
                           modelValue={this.state.modelValue}
                           languageValue={this.state.languageValue}
                           skillUrl={this.state.skillUrl}
                   />
           )}
   </div>
) : null}

 

So if there are skills preset in the topFeedbackSkills array which was saved in the state from the server initially and the condition to hide metrics is false we render the component and pass appropriate props for scrollId, skills data, language and model values and skill url.

In a similar way any metrics section can be implemented in the CMS, if the data is not present in the API, modify the endpoint to enclose the data you need, fetch data data from the server and just render it.

So I hope after reading through this you have a more clearer understanding about how the metrics sections are implemented on the CMS.

Resources

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

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

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

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

Performing Database Migrations using DbFlow

In Open Event Organizer Android App we decided to add database migrations for every change in database while development. Two of the reasons behind this –

  1. The users have some version of the app installed and then during development the developers had to modify the database, let’s say, add a table or modify existing ones. This makes the existing database incompatible with the new app. On adding database migration the upgradation of database takes place automatically and prevent the situation of reinstalling the app.
  2. Migrations makes it possible to rollback or upgrade to some particular database state. Thus, help in debugging certain changes in isolation.

Let’s discuss the implementation details. Consider the scenario when a user adds a new table named SpeakersCall. For creating migration for this change we need to add migration class inside OrgaDatabase class annotated with @Database. We will break it down to look closely at each step.

  1. Use @Migration annotation in DbFlow library and specify the new database version (by incrementing the existing version) and the database class.
  2. Extend BaseMigration and override migrate method.
  3. The logic used inside the migrate method can be different for different tasks. In the present case we first need to delete any existing table (if exists) with the name SpeakersCall and then recreate that table in database.
  4. Create an array of java classes which are created/modified.
  5. We wrap the SQL query inside a Database Wrapper class which prevents it from running recursively.
  6. FlowManager uses reflection to look up and construct the generated database holder class used in defining the structure for all databases used in this application. So we will getModelAdapter for the class to be recreated and use creation query returned by Model Adapter and execute it.
@Migration(version = 15, database = OrgaDatabase.class)
public static class MigrationTo15 extends BaseMigration {

@Override
public void migrate(@NonNull DatabaseWrapper databaseWrapper) {
Timber.d(“Running migration for DB version 14”);

Class<?>[] recreated = new Class[] {SpeakersCall.class};

for (Class<?> recreate: recreated) {
ModelAdapter modelAdapter = FlowManager.getModelAdapter(recreate);
databaseWrapper.execSQL(DROP_TABLE + modelAdapter.getTableName());
databaseWrapper.execSQL(modelAdapter.getCreationQuery());
}
}
}

Similarly, we can write migration for changing a column of table(s).

Testing the ViewModels in Open Event Organizer App

In Open Event Organizer Android App we follow Test Driven Development Approach which means the features added in the app are tested thoroughly by unit tests. More tests would ensure better code coverage and fewer bugs. This blog explains how to write tests for Viewmodel class in MVVM architecture.

Specifications

We will use JUnit4 to write unit tests and Mockito for creating mocks. The OrdersViewModel class returns the list of Order objects to the Fragment class. The objects are requested from OrderRepository class which fetches them from Network and Database. We will create a mock of OrderRepository class since it is out of context and contain logic that doesn’t depend on Orders Respository. Below is the getOrders method that we will test.

 public LiveData<List<Order>> getOrders(long id, boolean reload) {
if (ordersLiveData.getValue() != null && !reload)
return ordersLiveData;

compositeDisposable.add(orderRepository.getOrders(id, reload)
.compose(dispose(compositeDisposable))
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.toList()
.subscribe(ordersLiveData::setValue,
throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));

return ordersLiveData;
}

We will be using InstantTaskExecutorRule() which is a JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously. We will use setUp() method to load the RxJavaPlugins, RxAndroid plugins and reset them in tearDown method which will ensure each test runs independently from the other and avoid memory leaks. After doing this initialization and basic setup for tests we can begin code the method shouldLoadOrdersSuccessfuly() to test the getOrders method present in ViewModel class. Let’s see the step by step approach.

  1. Use Mockito.when to return Observables one by one from ORDERS_LIST whenever the method getOrders of the mock orderRepository is called.
  2. We will use Mockito.InOrder and pass orders, orderRepository and progress to check if they are called in a particular order.
  3. We will use .observeForever method to observe on LiveData objects and add a ArrayList on change.
  4. Finally, we will test and verify if the methods are called in order.
@Test
public void shouldLoadOrdersSuccessfully() {
when(orderRepository.getOrders(EVENT_ID, false))
.thenReturn(Observable.fromIterable(ORDERS_LIST));

InOrder inOrder = Mockito.inOrder(orders, orderRepository, progress);

ordersViewModel.getProgress().observeForever(progress);

orders.onChanged(new ArrayList<>());

ordersViewModel.getOrders(EVENT_ID, false);

inOrder.verify(orders).onChanged(new ArrayList<>());
inOrder.verify(orderRepository).getOrders(EVENT_ID, false);
inOrder.verify(progress).onChanged(true);
inOrder.verify(progress).onChanged(false);
}

Similar approach can be followed for writing tests to check other behaviour of the ViewModel.

References

  1. Official Documentation for testing. https://developer.android.com/reference/android/arch/core/executor/testing/InstantTaskExecutorRule
  2. Official Documentation for JUnit.  https://junit.org/junit4/
  3. Official documentation for Mockito.  http://site.mockito.org/
  4. Open Event Organizer App codebase.  https://github.com/fossasia/open-event-orga-app

Swipe to Check In/Out in Open Event Organizer App

Open Event Organizer App didn’t provide any option for the Event Organizer to view the list of Attendees present under an Order and check them in/out the event. Therefore, we designed a system such that the Organizer can just swipe the attendee present under an order to check them in or out. In this blog post, I will discuss how we implemented this functionality in Open Event Organizer App without using any third party libraries.

Specifications

We will create a separate class SwipeController.java which extends ItemTouchHelper.SimpleCallback and provide the swiping functionalities to our plain old recyclerview. We will call the super constructor with ItemTouchHelper.LEFT and ItemTouchHelper.RIGHT as arguments to provide left as well as right movements in each recyclerview list item. The bitmaps and paint object initialized here will be used later in onDraw.

public SwipeController(OrderDetailViewModel orderDetailViewModel, OrderAttendeesAdapter orderAttendeesAdapter, Context context) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.orderDetailViewModel = orderDetailViewModel;
this.orderAttendeesAdapter = orderAttendeesAdapter;

closeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.close);
doneIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.done);

paintGreen.setColor(context.getResources().getColor(R.color.light_green_500));
paintRed.setColor(context.getResources().getColor(R.color.red_500));
}

Next, we will override getMovementFlags method. This method decides the allowed movement directions for each recyclerview item. The deciding logic is that, if an attendee is checked in then the allowed movement is left to check out and if an attendee is checked out then the allowed movement is right to check in. If neither of the above case, then both movements are allowed.

@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;

If (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition()) == null)
makeMovementFlags(dragFlags, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);

if (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition())) {
return makeMovementFlags(dragFlags, ItemTouchHelper.LEFT);
} else {
return makeMovementFlags(dragFlags, ItemTouchHelper.RIGHT);
}
}

The onChildDraw method involves the code doing actual drawing. The variables used in code are discussed below.

  1. ActionState – Checks the state of the recycler view item. We proceed with the below logic if the item is being swiped.
  2. dX – The distance by which the item is swiped. Positive for left and negative for right.
  3. Background – Background of the viewholder. Rectangular in shape and dimensions changed with change in dX.
  4. IconDest – Calculates the position where the icons (close icon or done icon) is placed in canvas
  5. Canvas – Java Canvas on which the drawing is done. We set the background and draw the bitmaps on their location in canvas.
@Override
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() – (float) itemView.getTop();
float width = height / 3;
RectF background;
Paint paint;
Bitmap icon;
RectF iconDest;

if (dX > 0) {
background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom());
paint = paintGreen;
icon = doneIcon;
iconDest = new RectF((float) itemView.getLeft() + width,
(float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width,
(float) itemView.getBottom() – width);
} else {
background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom());
paint = paintRed;
icon = closeIcon;
iconDest = new RectF((float) itemView.getRight() – 2 * width,
(float) itemView.getTop() + width, (float) itemView.getRight() – width,
(float) itemView.getBottom() – width);
}

canvas.drawRect(background, paint);
canvas.drawBitmap(icon, null, iconDest, paint);
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

Now after the item is swiped out or in, we need to restore its original state again. For this we override the onSwiped method and call notifyItemChanged(). Also, the changes in UI (showing green side strip for checked in and red side strip for checked out) are done by. We call the toggleCheckin() method in ViewModel to toggle the checking status of the attendee in server and local database.

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();

orderDetailViewModel.toggleCheckIn(position);
orderAttendeesAdapter.notifyItemChanged(position);
}

Last but not the least, we will override the onMove method to return false. Since we are not supporting drag and drop features therefore this method will never be called.

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}

Resources

  1. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
  2. Official documentation for ItemTouchHelper.SimpleCallback https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback