Ember Data Integration In Badgeyay Frontend

Badgeyay is an open source utility to develop badges for events and tech conferences. Badgeyay project is divided into two components. Frontend part is designed with ember and backend part is designed with Flask and database as PostgreSQL and Firebase as PaaS. After refactoring the backend API for generation of badges, now it is time to consume the API in frontend by ember, and the way to consume the api in ember front--end is with the use of in built ember-data library. Ember data behaves in a way similar to server side ORM’s (Object Relational Mappers). It is a very versatile library and can be equipped with variety of backend services. It can be used with REST as well as sockets and other transfer protocols for communication. For better understanding the working of ember data, let’s see how to use the same to consume the File Upload endpoint in the backend. Procedure Enabling CORS on server, to allow cross-domain requests to the API. from flask_cors import CORS CORS(app, resources={r"*": {"origins": "*"}}) Creating Adapter for the model in frontend. In our case it is csv-file. In the adapter we need to specify the host and the path, because our backend api is not running on the same port. import DS from 'ember-data'; const { RESTAdapter } = DS; export default RESTAdapter.extend({ host : 'http://localhost:5000', pathForType : () => { return 'api/upload/file'; } }); After creating the adapter we need to create the record in the controller of the respective component. The record is like an object of a class, which when pushed to store will make a network request to backend (POST) and fetch the response from the backend. Backend response will provide the id to save in store import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; export default Controller.extend({ routing : service('-routing'), actions : { mutateCSV(csvData) { let csv_ = this.get('store').createRecord('csv-file', { csvFile : csvData, extension : 'csv' }); csv_.save(); }, mutateText(txtData) { console.log(txtData); } } }); Model for the csv-file import DS from 'ember-data'; const { Model, attr } = DS; export default Model.extend({ csvFile : attr('string'), extension : attr('string') }); Next is to create serializers for the model. Serializers gets triggered at two moments, first when the data is sent to the server and second when data is received from the server. Each time an independent function gets executed. As the naming conventions of the functions pretty much explains their role, but for the sake of clarification serialize function gets executed when we send request to the server and normalizeResponse gets executed when we are getting response from the server. import DS from 'ember-data'; const { JSONAPISerializer } = DS; export default JSONAPISerializer.extend({ serialize(snapshot, options) { let json = this._super(...arguments); json.csvFile = { 'csvFile' : json.data.attributes['csv-file'], 'extension' : json.data.attributes.extension }; delete json.data; return json; }, normalizeResponse(store, primaryModelClass, payload, id, requestType) { return payload; } }); After receiving the response a promise is returned by the push method to save the record in the store…

Continue ReadingEmber Data Integration In Badgeyay Frontend

Addition of Bookmarks to the Homescreen in the Open Event Android App

In the Open Event Android app we had already built the new homescreen but the users only had access to bookmarks in a separate page which could be accessed from the navbar.If the bookmarks section were to be incorporated in the homescreen itself, it would definitely improve its access to the user. In this blog post, I’ll be talking about how this was done in the app. These 2 images show the homescreen and the bookmarks section respectively.                   This was the proposed homescreen page for the app. This would provide easy access to important stuff to the user such as event venue,date,description etc. Also the same homescreen would also have the bookmarks showing at the top if there are any. The list of bookmarks in the first iteration of design was modeled to be a horizontal list of cards. Bookmarks Merging Process These are some variables for reference. private SessionsListAdapter sessionsListAdapter; private RealmResults<Session> bookmarksResult; private List<Session> mSessions = new ArrayList<>(); The code snippet below highlights the initial setup of the bookmarks recycler view for the horizontal List of cards. All of this is being done in the onCreateView callback of the AboutFragment.java file which is the fragment file for the homescreen. bookmarksRecyclerView.setVisibility(View.VISIBLE); sessionsListAdapter = new SessionsListAdapter(getContext(), mSessions, bookmarkedSessionList); sessionsListAdapter.setBookmarkView(true); bookmarksRecyclerView.setAdapter(sessionsListAdapter); bookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),LinearLayoutManager.HORIZONTAL,false)); The SessionListAdapter is an adapter that was built to handle multiple types of displays of the same viewholder i.e SessionViewHolder . This SessionListAdapter is given a static variable as an argument which is just notifies the adapter to switch to the bookmarks mode for the adapter. private void loadData() {    bookmarksResult = realmRepo.getBookMarkedSessions();    bookmarksResult.removeAllChangeListeners();    bookmarksResult.addChangeListener((bookmarked, orderedCollectionChangeSet) -> {        mSessions.clear();        mSessions.addAll(bookmarked);        sessionsListAdapter.notifyDataSetChanged();        handleVisibility();    }); } This function loadData() is responsible for extracting the sessions that are bookmarked from the local Realm database. We the update the BookmarkAdapter on the homescreen with the list of the bookmarks obtained. Here we see that a ChangeListener is being attached to our RealmResults. This is being done so that we do our adapter notify only after the data of the bookmarked sessions has been processed from a background thread. if(bookmarksResult != null)    bookmarksResult.removeAllChangeListeners(); And it is good practice to remove any ChangeListeners that we attach during the fragment life cycle in the onStop() method to avoid memory leaks. So now we have successfully added bookmarks to the homescreen. Resources Realm Change Listener : https://realm.io/docs/java/latest/api/io/realm/RealmChangeListener.html Android Activity Lifecycle : https://developer.android.com/guide/components/activities/activity-lifecycle.html Blog Post for Homescreen Details : https://blog.fossasia.org/adding-global-search-and-extending-bookmark-views-in-open-event-android/

Continue ReadingAddition of Bookmarks to the Homescreen in the Open Event Android App

Addition Of Track Tags In Open Event Android App

In the Open Event Android app we only had a list of sessions in the schedule page without  knowing which track each session belonged to. There were several iterations of UI design for the same. Taking cues from the Google I/O 17 App we thought that the addition of track tags would be informative to the user.  In this blog post I will be talking about how this feature was implemented. TrackTag Layout in Session <Button                android:layout_width="wrap_content"                android:layout_height="@dimen/track_tag_height"                android:id="@+id/slot_track"                android:textAllCaps="false"                android:gravity="center"                android:layout_marginTop="@dimen/padding_small"                android:paddingLeft="@dimen/padding_small"                android:paddingRight="@dimen/padding_small"                android:textStyle="bold"                android:layout_below="@+id/slot_location"                android:ellipsize="marquee"                android:maxLines="1"                android:background="@drawable/button_ripple"                android:textColor="@color/white"                android:textSize="@dimen/text_size_small"                tools:text="Track" /> The important part here is the track tag was modelled as a <Button/> to give users a touch feedback via a ripple effect. Tag Implementation in DayScheduleAdapter All the dynamic colors for the track tags was handled separately in the DayScheduleAdapter. final Track sessionTrack = currentSession.getTrack();       int storedColor = Color.parseColor(sessionTrack.getColor());       holder.slotTrack.getBackground().setColorFilter(storedColor, PorterDuff.Mode.SRC_ATOP);       holder.slotTrack.setText(sessionTrack.getName());       holder.slotTrack.setOnClickListener(v -> {            Intent intent = new Intent(context, TrackSessionsActivity.class);            intent.putExtra(ConstantStrings.TRACK, sessionTrack.getName());            intent.putExtra(ConstantStrings.TRACK_ID, sessionTrack.getId());            context.startActivity(intent);      }); As the colors associated with a track were all stored inside the track model we needed to obtain the track from the currentSession. We could get the storedColor by calling getColor() on the track object associated with the session. In order to set this color as the background of the button we needed to set a color filter with a PorterDuff Mode called as SRC_ATOP. The name of the parent class is an homage to the work of Thomas Porter and Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images". In this paper, the authors describe 12 compositing operators that govern how to compute the color resulting of the composition of a source (the graphics object to render) with a destination (the content of the render target). Ref : https://developer.android.com/reference/android/graphics/PorterDuff.Mode.html To exactly understand what does the mode SRC_ATOP do we can refer to the image below for understanding. There are several other modes with similar functionality.                       SRC IMAGE             DEST IMAGE           SRC ATOP DEST Now we also set an onClickListener for the track tag so that it directs us to the tracks Page. So now we have successfully added track tags to the app. Resources Porter Duff Modes : https://developer.android.com/reference/android/graphics/PorterDuff.Mode.html

Continue ReadingAddition Of Track Tags In Open Event Android App

GlobalSearchAdapter Setup in Open Event Android App

In this blog post I describe how the GlobalSearchAdapter in Open Event Android was made which enabled users to search quickly within the app. This post also outlines how to create Recycler Views with heterogenous layouts and explains how to write ViewHolders. Adapter Logic A custom adapter was built for the population of views in the Recycler View in the SearchActivity. private List<Object> filteredResultList = new ArrayList<>(); //ViewType Constants private final int TRACK = 0; private final int SPEAKER = 2; private final int LOCATION = 3; private final int DIVIDER = 4; The DIVIDER constant was assigned to the Result Type Header View. In a gist all the item types such as Speaker, Track, Location, Divider etc have been designated some constants. Getting the ItemViewType @Override public int getItemViewType(int position) {   if(filteredResultList.get(position) instanceof Track){       return TRACK;   }   else if(filteredResultList.get(position) instanceof String){       return DIVIDER;   }    ...Similarly for other ItemTypes such as Session or Location   else{       return 1;   } } As the filteredResultList is of type Object we can insert objects of any type into the list as Object is a superclass of all classes. We would want a view which represents a TRACK if we have an object of type Track in the filteredResultList. And similarly for the other result types we could insert objects of type LOCATION, SPEAKER types in this list. getItemViewType() basically determines the type of the item that is visible to us. If the list consists of an item of type SPEAKER, in the RecyclerView. Code for onCreateViewHolder in GlobalSearchAdapter for the Recycler View @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {   RecyclerView.ViewHolder resultHolder = null;   LayoutInflater inflater = LayoutInflater.from(parent.getContext());   switch(viewType) {       case TRACK:           View track = inflater.inflate(R.layout.item_track, parent, false);           resultHolder = new TrackViewHolder(track,context);           break;       case SPEAKER:           View speaker = inflater.inflate(R.layout.search_item_speaker, parent, false);           resultHolder = new SpeakerViewHolder(speaker,context);           break;       //Similarly for other types       default:           break;   }   return resultHolder; } Depending upon the the viewType returned the desired layout is inflated and the desired ViewHolder is returned. Code for onBindViewHolder in GlobalSearchAdapter for the Recycler View @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {    switch (holder.getItemViewType()){        case TRACK:            TrackViewHolder trackSearchHolder = (TrackViewHolder)holder;            final Track currentTrack = (Track)getItem(position);            trackSearchHolder.setTrack(currentTrack);            trackSearchHolder.bindHolder();            break;         //Similarly for all the other View Types default:            break;    } } These functions are being used to bind the data to the layouts that have been inflated already in the earlier snippet of code of onCreateViewHolder. The bindHolder functions of each ViewHolder type are being used to do the view binding i.e converting the information in the Object Track into what we see in the TrackViewHolder as seen in TrackViewFormat. All ViewHolders have been defined as separate classes in order to enable re usability of these classes. ViewHolder Implementation There are 4 main ViewHolders that were made to enable such a search. I’ll be talking about the TrackViewHolder in detail. public class TrackViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.imageView)    ImageView trackImageIcon; @BindView(R.id.track_title)    TextView trackTitle; @BindView(R.id.track_description)   …

Continue ReadingGlobalSearchAdapter Setup in Open Event Android App

Global Search in Open Event Android

In the Open Event Android app we only had a single data source for searching in each page that was the content on the page itself. But it turned out that users want to search data across an event and therefore across different screens in the app. Global search solves this problem. We have recently implemented  global search in Open Event Android that enables the user to search data from the different pages i.e Tracks, Speakers, Locations etc all in a single page. This helps the user in obtaining his desired result in less time. In this blog I am describing how we implemented the feature in the app using JAVA and XML. Implementing the Search The first step of the work is to to add the search icon on the homescreen. We have done this with an id R.id.action_search_home. @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_home, menu); // Get the SearchView and set the searchable configuration SearchManager searchManager = (SearchManager)getContext(). getSystemService(Context.SEARCH_SERVICE); searchView = (SearchView) menu.findItem(R.id.action_search_home).getActionView(); // Assumes current activity is the searchable activity searchView.setSearchableInfo(searchManager.getSearchableInfo( getActivity().getComponentName())); searchView.setIconifiedByDefault(true); } What is being done here is that the search icon on the top right of the home screen  is being designated a searchable component which is responsible for the setup of the search widget on the Toolbar of the app. @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater();    inflater.inflate(R.menu.menu_home, menu);    SearchManager searchManager =            (SearchManager) getSystemService(Context.SEARCH_SERVICE);    searchView = (SearchView) menu.findItem(R.id.action_search_home).getActionView();    searchView.setSearchableInfo(            searchManager.getSearchableInfo(getComponentName()));    searchView.setOnQueryTextListener(this);    if (searchText != null) {        searchView.setQuery(searchText, true);    }    return true; } We can see that a queryTextListener has been setup in this function which is responsible to trigger a function whenever a query in the SearchView changes. Example of a Searchable Component <?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android"    android:hint="@string/global_search_hint"    android:label="@string/app_name" /> For More Info : https://developer.android.com/guide/topics/search/searchable-config.html If this searchable component is inserted into the manifest in the required destination activity’s body the destination activity is set and intent filter must be set in this activity to tell whether or not the activity is searchable. Manifest Code for SearchActivity <activity        android:name=".activities.SearchActivity"        android:launchMode="singleTop"        android:label="Search App"        android:parentActivityName=".activities.MainActivity">    <intent-filter>        <action android:name="android.intent.action.SEARCH" />    </intent-filter>    <meta-data        android:name="android.app.searchable"        android:resource="@xml/searchable" /> </activity> And the attribute  android:launchMode=”singleTop”  is very important as if we want to search multiple times in the SearchActivity all the instances of our SearchActivity would get stored on the call stack which is not needed and would also eat up a lot of memory. Handling the Intent to the SearchActivity We basically need to do a standard if check in order to check if the intent is of type ACTION_SEARCH. if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {    handleIntent(getIntent()); } @Override protected void onNewIntent(Intent intent) {    super.onNewIntent(intent);    handleIntent(intent); } public void handleIntent(Intent intent) {    final String query = intent.getStringExtra(SearchManager.QUERY);    searchQuery(query); } The function searchQuery is called within handleIntent in order to search for the text that we received…

Continue ReadingGlobal Search in Open Event Android