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 : .//adding-global-search-and-extending-bookmark-views-in-open-event-android/

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

Better Bookmark Display Viewholder in Home Screen of the Open Event Android App

Earlier in the Open Event Android app we had built the homescreen with the bookmarks showing up at the top as a horizontal list of cards but it wasn’t very user-friendly in terms of UI. Imagine that a user bookmarks over 20-30 sessions, in order to access them he/she might have to scroll horizontally a lot in order to access his/her bookmarked session. So this kind of UI was deemed counter-intuitive. A new UI was proposed involving the viewholder used in the schedule page i.e DayScheduleViewHolder, where the list would be vertical instead of horizontal. An added bonus was that this viewholder conveyed the same amount of information on lesser white space than the earlier viewholder i.e SessionViewHolder.                   Above are two images, one in the initial design, the second in the new design. In the earlier design the number of bookmarks visible to the user at a time was at most 1 or 2 but now with the UI upgrade a user can easily see up-to 5-6 bookmarks at a time. Additionally there is more relevant content visible to the user at the same time. Additionally this form of design also adheres to Google’s Material Design guidelines. Code Comparison of the two Iterations Initial Design sessionsListAdapter = new SessionsListAdapter(getContext(), mSessions, bookmarkedSessionList); sessionsListAdapter.setBookmarkView(true); bookmarksRecyclerView.setAdapter(sessionsListAdapter); bookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),LinearLayoutManager.HORIZONTAL,false)); Here we are using the SessionListAdapter for the bookmarks. This was previously being used to display the list of sessions inside the track and location pages. It is again being used here to display the horizontal list of bookmarks.To do this we are using the function setBookmarkView(). Here mSessions consists the list of bookmarks that would appear in the homescreen. Current Design bookmarksRecyclerView.setVisibility(View.VISIBLE); bookMarksListAdapter = new DayScheduleAdapter(mSessions,getContext()); bookmarksRecyclerView.setAdapter(bookMarksListAdapter); bookmarksRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); Now we are using the DayScheduleAdapter which is the same adapter used in the schedule page of the app. Now we use a vertical layout instead of a horizontal layout  but with a new viewholder design. I will be talking about the last line in the code snippet shortly. ViewHolder Design (Current Design) <RelativeLayout android:id="@+id/content_frame">    <LinearLayout android:id="@+id/ll_sessionDetails">        <TextView android:id="@+id/slot_title"/>        <RelativeLayout>            <TextView android:id="@+id/slot_start_time”/>            <TextView android:id="@+id/slot_underscore"/>            <TextView android:id="@+id/slot_end_time"/>            <TextView android:id="@+id/slot_comma"/>            <TextView android:id="@+id/slot_location”/>            <Button android:id="@+id/slot_track"/>            <ImageButton android:id="@+id/slot_bookmark"/>        </RelativeLayout>     </LinearLayout>    <View android:id=”@+id/divider”/> </RelativeLayout> This layout file is descriptive enough to highlight each element’s location. In this viewholder we can also access to the track page from the track tag and can also remove bookmarks instantly. The official widget to make a scroll layout in android is ScrollView. Basically, adding a RecyclerView inside ScrollView can be difficult . The problem was that the scrolling became laggy and weird.  Fortunately, with the appearance of Material Design , NestedScrollView was released and this becomes much easier. bookmarksRecyclerView.setNestedScrollingEnabled(false); With this small snippet of code we are able to insert a RecyclerView inside the NestedScrollView without any scroll lag. So now we have successfully updated the UI/UX for the homescreen  to meet…

Continue ReadingBetter Bookmark Display Viewholder in Home Screen of the 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

Using FastAdapter in Open Event Organizer Android Project

RecyclerView is an important graphical UI component in any android application. Android provides RecyclerView.Adapter class which manages all the functionality of RecyclerView. I don't know why but android people have kept this class in a very abstract form with only basic functionalities implemented by default. On the plus side it opens many doors for custom adapters with new functionalities for example, sticky headers, scroll indicator, drag and drop actions on items, multiview types items etc. A developer should be able to make an adapter of his need by extending RecyclerView.Adapter. There are many custom adapters developers have shared which comes with built in functionalities. FastAdapter is one of them which comes with all the good functionalities built in and also it is very easy to use. I just got to use this in the Open Event Organizer Android App of which the core feature is Attendees Check In. We have used FastAdapter library to show attendees list which needs many features which are absent in plane RecyclerView.Adapter. FastAdapter is built in such way that there are many different ways of using it on developer's need. I have found a simplest way which I will be sharing here. The first part is extending the item model to inherit AbstractItem. public class Attendee extends AbstractItem<Attendee, AttendeeViewHolder> {   @PrimaryKey   private long id;   ...   ...   @Override   public long getIdentifier() {       return id;   }   @Override   public int getType() {       return 0;   }   @Override   public int getLayoutRes() {       return R.layout.attendee_layout;   }   @Override   public AttendeeViewHolder getViewHolder(View view) {       return new AttendeeViewHolder(DataBindingUtil.bind(view));   }   @Override   public void bindView(AttendeeViewHolder holder, List<Object> list) {       super.bindView(holder, list);       holder.bindAttendee(this);   }   @Override   public void unbindView(AttendeeViewHolder holder) {       super.unbindView(holder);       holder.unbindAttendee();   } } The methods are pretty obvious by name. Implement these methods accordingly. You may notice that we have used Databinding here to bind data to views but it is not necessary. Also you will have to create your ViewHolder for adapter. You can either use RecyclerView.ViewHolder or you can just create a custom one by inheriting it as per your need. Once this part is over you are half done as most of the things are been taken care in model itself. Now we will be writing code for adapter and setting it to your RecyclerView. FastItemAdapter<Attendee> fastItemAdapter = new FastItemAdapter<>(); fastItemAdapter.setHasStableIds(true); ... // functionalities related code ... recyclerView.setAdapter(fastItemAdapter); Initialize FastItemAdapter which will be our main adapter handling all the direct functions related to the RecyclerView. Set up some boolean constants according to the project need. In our project we have Attendee model which has id as a primary field. FastItemAdapter can take advantage of distinct field of the model called as identifier . Hence it is set true as Attendee model has id field. But you should be careful about setting it to True as then you must have implemented getIdentifier in the model to return correct field which will be used as an identifier by our adapter. And the adapter is good to set to the RecyclerView. Now we got to decide…

Continue ReadingUsing FastAdapter in Open Event Organizer Android Project