Selecting Best persistent storage for Phimpme Android and how to use it

As we are progressing in our Phimpme Android app. I added account manager part which deals with connecting all other accounts to phimpme. Showing a list of connected accounts. We need a persistent storage to store all the details such as username, full name, profile image url, access token (to access API). I researched on various Object Relation mapping (ORMs) such as: DBFlow: https://github.com/Raizlabs/DBFlow GreenDAO: https://github.com/greenrobot/greenDAO SugarORM: http://satyan.github.io/sugar/ Requery: https://github.com/requery/requery and other NoSQL databases such as Realm Database : https://github.com/realm/realm-java. After reading a lot from some blogs on the benchmarking of these ORMs and database, I came to know that Realm database is quite better in terms of Speed of writing data and ease of use. Steps to integrate Realm Database: Installation of Realm database in android Following these steps https://realm.io/docs/java/latest/#installation quickly setup realm in android. Add classpath "io.realm:realm-gradle-plugin:3.3.2" in Project level build.gradle file and Add apply plugin: 'realm-android' in app level build.gradle, That’s it for using Realm Generating required Realm models Firstly, make sure what you need to store in your database. In case of phimpme, I first go through the account section and noted down what needs to be there.  Profile image URL, username, full name, account indicator image name. Below image illustrate this better. This is the Realm Model class I made in Kotlin to store name, username and access token for accessing API. open class AccountDatabase(       @PrimaryKey var name: String = "",       var username: String = "",       var token: String = "" ) : RealmObject() Writing data in database In Account manager, I create a add account option from where a dialog appear with a list of accounts. Currently, Twitter is working, when onSuccess function invoke in AccountPickerFragment I start a twitter session and store values in database. Writing data in database: // Begin realm transaction realm.beginTransaction(); // Creating Realm object for AccountDatabase Class account = realm.createObject(AccountDatabase.class,       accountsList[0]); account.setUsername(session.getUserName()); account.setToken(String.valueOf(session.getAuthToken())); realm.commitTransaction(); Begin and commit block in necessary. There is one more way of doing this is using execute function in Realm Use Separate Database Helper class for Database operations It’s good to use a separate class for all the Database operations needed in the project. I created a DatabaseHelper Class and added a function to query the result needed. Query the database public RealmResults<AccountDatabase> fetchAccountDetails(){   return realm.where(AccountDatabase.class).findAll(); } It give all of the results, stored in the database like below Problems I faced with annotation processor while using Kotlin and Realm together The Kotlin annotation processor not running due to the plugins wrong order. This issue https://github.com/realm/realm-java/pull/2568 helped me in solving that. I addded apply plugin: 'kotlin-kapt'. In app gradle file and shift apply plugin: 'realm-android' In below the order. Resources: Realm for Android (official): https://news.realm.io/news/realm-for-android/ Write in Realm Database: https://realm.io/docs/java/latest/#writes Queries in Realm Database: https://realm.io/docs/java/latest/#queries A detailed Blog post on Realm Database: https://medium.com/@Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149  

Continue ReadingSelecting Best persistent storage for Phimpme Android and how to use it

Datewise splitting of the Bookmarks in the Homescreen of Open Event Android

In the Open Event Android app we had already incorporated bookmarks in the homescreen along with an improved UI. Now there was scope for further improvement in terms of user experience. The bookmarks were already sorted date wise but we needed to place them under separate date headers. In this blog I will be talking about how this was done in the app.                 Initially the user had no way of knowing which session belonged to which day. This could be fixed with a simple addition of a header indicating the day each bookmark belonged to. One way to do this was to add a day header and then get the bookmarks for each day and so on. But this proved to be difficult owing to the fact the number of days could be dynamic owing to the fact that this is a generic app. Another issue was that adding change listeners for the realm results to the bookmarks list for each day produced view duplication and other unexpected results whenever the bookmark list changed. So another approach was chosen that was to get all the bookmarks first and then add the date header and traverse through the bookmarks and only add sessions which belong to the date for which the date header was added earlier. Bookmark Item Support in GlobalSearchAdapter The main reason why we are reusing the GlobalSearchAdapter is that we have already defined a DIVIDER type in this adapter which can be reused as the date header. We needed to initialize a constant for the Bookmark type. private final int BOOKMARK = 5; //Can be any number Then we add the Bookmark type in the getItemViewType() function which would return a constant that we defined earlier to indicate that in the filteredResultList we have an object of type Bookmark. @Override public int getItemViewType(int position) {    if (filteredResultList.get(position) instanceof Track) {        return TRACK;    }     //Other Cases here    } else if(filteredResultList.get(position) instanceof Session){        return BOOKMARK;    } else {        return 1;    } } Now we create the viewholder if the list item is of the type Session which in this case will be a bookmark. @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {    RecyclerView.ViewHolder resultHolder = null;    LayoutInflater inflater = LayoutInflater.from(parent.getContext());    //Other cases for Track,Location etc case BOOKMARK:    View bookmark = inflater.inflate(R.layout.item_schedule, parent, false);    resultHolder = new DayScheduleViewHolder(bookmark,context);    break;   //Some code } Now we do the same in onBindViewHolder(). We bind the contents of the object to the ViewHolder here by calling the bindSession() function. We also pass in an argument which is our database repository i.e realmRepo here. @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {     switch (holder.getItemViewType()) {     //Other cases handled here case BOOKMARK:    DayScheduleViewHolder bookmarkTypeViewHolder = (DayScheduleViewHolder) holder;    Session bookmarkItem = (Session) getItem(position);    bookmarkTypeViewHolder.setSession(bookmarkItem);    bookmarkTypeViewHolder.bindSession(realmRepo);    break; } Updating the AboutFragment private GlobalSearchAdapter bookMarksListAdapter; private List<Object> mSessions =…

Continue ReadingDatewise splitting of the Bookmarks in the Homescreen of Open Event Android

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 Bookmark Icon in Schedule ViewHolder in Open Event Android App

In the Open Event Android app we only had a list of sessions in the schedule page without  the ability to bookmark the session unless we went into the SessionDetailPage or visited the session list via the tracks page or the locations page. This was obviously very inconvenient. There were several iterations of UI design for the same. Taking cues from the Google I/O 17 App I thought that the addition of the Bookmark Icon in the Schedule ViewHolder would be helpful to the user. In this blog post I will be talking about how this feature was implemented. Layout Implementation for the Bookmark Icon <ImageButton    android:id="@+id/slot_bookmark"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_gravity="end"    android:layout_alignParentEnd="true"    android:layout_alignParentRight="true"    android:layout_alignParentBottom="true"    android:tint="@color/black"    android:background="?attr/selectableItemBackgroundBorderless"    android:contentDescription="@string/session_bookmark_status"    android:padding="@dimen/padding_small"    app:srcCompat="@drawable/ic_bookmark_border_white_24dp" /> The bookmark Icon was modelled as an ImageButton inside the item_schedule.xml file which serves as the layout file for the DayScheduleViewHolder. Bookmark Icon Functionality in DayScheduleAdapter The Bookmark Icon had mainly 3 roles : Add the session to the list of bookmarks (obviously) Generate the notification giving out the bookmarked session details. Generate a Snackbar if the icon was un-clicked which allowed the user to restore the bookmarked status of the session. Update the bookmarks widget. Now we will be seeing how was all this done. Some of this was already done previously in the SessionListAdapter. We just had to modify some of the code to get our desired result.        Session not bookmarked                      Session bookmarked                       First we just set a different icon to highlight the Bookmarked and the un-bookmarked status. This code snippet highlights how this is done. if(session.isBookmarked()) {    slot_bookmark.setImageResource(R.drawable.ic_bookmark_white_24dp); } else { slot_bookmark.setImageResource(R.drawable.ic_bookmark_border_white_24dp); } slot_bookmark.setColorFilter(storedColor,PorterDuff.Mode.SRC_ATOP); We check if the session is bookmarked by calling a function isBookmarked() and choose one of the 2 bookmark icons depending upon the bookmark status. If a session was found out to be bookmarked and the Bookmark Icon was we use the WidgetUpdater.updateWidget() function to remove that particular session from the  Bookmark Widget of the app. During this a  Snackbar is also generated “Bookmark Removed” with an UNDO option which is functional. realmRepo.setBookmark(sessionId, false).subscribe(); slot_bookmark.setImageResource(R.drawable.ic_bookmark_border_white_24dp); if ("MainActivity".equals(context.getClass().getSimpleName())) {    Snackbar.make(slot_content, R.string.removed_bookmark, Snackbar.LENGTH_LONG)            .setAction(R.string.undo, view -> {                realmRepo.setBookmark(sessionId, true).subscribe();                slot_bookmark.setImageResource(R.drawable.ic_bookmark_white_24dp);                WidgetUpdater.updateWidget(context);            }).show(); else {    Snackbar.make(slot_content, R.string.removed_bookmark, Snackbar.LENGTH_SHORT).show(); } If a session wasn’t bookmarked earlier but the Bookmark Icon was clicked we would firstly need to update the bookmark status within our local Realm Database. realmRepo.setBookmark(sessionId, true).subscribe(); We would also create a notification to notify the user. NotificationUtil.createNotification(session, context).subscribe(        () -> Snackbar.make(slot_content,                R.string.added_bookmark,                Snackbar.LENGTH_SHORT)                .show(),        throwable -> Snackbar.make(slot_content,                R.string.error_create_notification,                Snackbar.LENGTH_LONG).show()); The static class Notification Util is responsible for the generation of notifications. The internal working of that class is not necessary right now. What this snippet of code does is that It creates a Snackbar upon…

Continue ReadingAddition of Bookmark Icon in Schedule ViewHolder 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

Control flow of SUSI AI on Android and database management using Realm

While developing a chat based android application, one of the most important things is keeping track of user’s messages. Since the user might want to access them in the absence of Internet connectivity (i.e remotely) as well, storing them locally is also important. In SUSI we are using Realm to keep things organized in a systematic manner and constructing model (or adding appropriate attributes) for every new data type which the application needs. Right now we have three main models namely ChatMessage, WebLink and WebSearchModel. These three java classes define the structure of each possible message.  ChatMessage evaluates and classifies incoming response from server either to be an image or map or pie chart or web search url or other valid types of response. WebSearchModel and WebLink models are there to manage those results which contains link to various web searches. Various result based lists are maintained for smooth flow of application. Messages sent in absence of Internet are stored in a list - nonDelivered. All the messages have an attribute isDelivered which is set to true if and only if they have been queried, otherwise the attribute is set to false which puts it in the nonDelivered list. Once the phone is connected back to the internet and the app is active in foreground, the messages are sent to server, queried and we get the response back in the app’s database where the attributes are assigned accordingly.   I will explain a functionality below that will give a more clear view about our coding practices and work flow. When a user long taps a message, few options are listed(these actions are defined in recycleradapters->ChatFeedRecyclerAdapter.java) from which you may select one. In the code, this triggers the method onActionsItemClicked(). In this Overridden method, we handle what happens when a user clicks on one of the following options from item menu. In this post I’ll be covering only about the star/important message option. case R.id.menu_item_important: nSelected = getSelectedItems().size(); if (nSelected >0) { for (int i = nSelected - 1; i >= 0; i--) { markImportant(getSelectedItems().get(i)); } if(nSelected == 1) { Toast.makeText(context,nSelected+" message marked important",Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, nSelected + " messages marked important", Toast.LENGTH_SHORT).show(); } Important = realm.where(ChatMessage.class). equalTo("isImportant",true) .findAll().sort("id"); for(int i=0;i<important.size();++i) Log.i("message ","" + important.get(i).getContent()); Log.i("total ",""+important.size()); actionMode.finish(); } return true; We have the count of messages which were selected. Each message having a unique id is looped through and the attribute “isImportant” of each message object is modified accordingly. To modify this field, We call the method markImportant() and pass the id of message which has to be updated. public void markImportant(final int position) { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { ChatMessage chatMessage = getItem(position); chatMessage.setIsImportant(true); realm.copyToRealmOrUpdate(chatMessage); } }); } This method copies the instance of the message whose id it has received and updates the attribute “isImportant” and finally updates the message instance in the database. Below given is the code for ImportantMessage activity which will help you understand properly how lists are used…

Continue ReadingControl flow of SUSI AI on Android and database management using Realm