Displaying a Comments dialogfragment on a Button Click from the Feed Adapter in the Open Event Android App

Developing the live feed of the event page from Facebook for the Open Event Android App, there were questions how best to display the comments in the feed.  A dialog fragment over the feeds on the click of a button was the most suitable solution. Now the problem was, a dialogfragment can only be called from an app component (eg- fragment or an activity). Therefore, the only challenge which remained was to call the dialogfragment from the adapter over the feed fragment with the corresponding comments of the particular post on a button click.

What is a dialogfragment?

A dialogfragment displays a dialog window, floating on top of its activity’s window. This fragment contains a Dialog object, which it displays as appropriate based on the fragment’s state. Control of the dialog (deciding when to show, hide, dismiss it) should be done through the API here, not with direct calls on the dialog (Developer.Android.com).

Solution

The solution which worked on was to define a adapter callback interface with a onMethodCallback method in the feed adapter class itself with the list of comment items fetched at runtime on the button click of a particular post. The interface had to be implemented by the main activity which housed the feed fragment that would be creating the comments dialogfragment with the passed list of comments.

Implementation

Define an interface adapterCallback with the method onMethodCallback parameterized by the list of comment items in your adapter class.

public interface AdapterCallback {
   void onMethodCallback(List<CommentItem> commentItems);
}

 

Create a constructor of the adapter with the adapterCallback as a parameter. Do not forget to surround it with a try/catch.

public FeedAdapter(Context context, AdapterCallback adapterCallback, List<FeedItem> feedItems) {
     this.mAdapterCallback = adapterCallback;
}

 

On the click of the comments button, call onMethodCallback method with the corresponding comment items of a particular feed.

getComments.setOnClickListener(v -> {
   if(commentItems.size()!=0)
       mAdapterCallback.onMethodCallback(commentItems);
});

 

Finally implement the interface in the activity to display the comments dialog fragment populated with the corresponding comments of a feed post. Pass the comments with the help of arraylist through the bundle.

@Override
public void onMethodCallback(List<CommentItem> commentItems) {
   CommentsDialogFragment newFragment = new CommentsDialogFragment();
   Bundle bundle = new Bundle();
   bundle.putParcelableArrayList(ConstantStrings.FACEBOOK_COMMENTS, new ArrayList<>(commentItems));
   newFragment.setArguments(bundle);
   newFragment.show(fragmentManager, "Comments");
}

 

Conclusion

The comments generated with each feed post in the open event android app does complement the feed well. The pagination is something which is an option in the comments and the feed both however that is something for some other time. Until then, keep coding!

Resources

Continue ReadingDisplaying a Comments dialogfragment on a Button Click from the Feed Adapter in the Open Event Android App

Create an App Widget for Bookmarked Sessions for the Open Event Android App

What is an app widget?

App Widgets are miniature application views that can be embedded in other applications (such as the Home screen) and receive periodic updates. These views are referred to as Widgets in the user interface, and you can publish one with an App Widget provider. – (Android Documentation).

Android widget is an important functionality that any app can take advantage of. It could be used to show important dates, things that the user personalizes on the app etc. In the context of the Open Event Android App, it was necessary to create a bookmark widget for the Android phones so that the user could see his bookmarks on the homescreen itself and need not open the app for the same. In the open event android app, the widget was already created but it needed bug fixes and UI enhancements due to migration to the Realm database migration. Therefore, my majority of work circled around that.

Implementation

Declare the app widget in the manifest. All the updates in the application would be received by the class which extends the AppWidgetProvider if it needs to be reflected in the widget.

<receiver
   android:name=".widget.BookmarkWidgetProvider"
   android:enabled="true"
   android:label="Bookmarks">
   <intent-filter>
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
       <action android:name="${applicationId}.ACTION_DATA_UPDATED" />
       <action android:name="${applicationId}.UPDATE_MY_WIDGET" />
   </intent-filter>
   <meta-data
       android:name="android.appwidget.provider"
       android:resource="@xml/widget_info" />
</receiver>

 

Create a layout for the widget that is to be displayed on the homescreen. Remember to use only the views defined in the documentation. After the creation of the layout, create a custom widget updater which will broadcast the data from the app to the receiver to update the widget.

public class WidgetUpdater {
   public  static  void updateWidget(Context context){
       int widgetIds[] = AppWidgetManager.getInstance(context.getApplicationContext()).getAppWidgetIds(new ComponentName(context.getApplicationContext(), BookmarkWidgetProvider.class));
       BookmarkWidgetProvider bookmarkWidgetProvider = new BookmarkWidgetProvider();
       bookmarkWidgetProvider.onUpdate(context.getApplicationContext(), AppWidgetManager.getInstance(context.getApplicationContext()),widgetIds);
       context.sendBroadcast(new Intent(BookmarkWidgetProvider.ACTION_UPDATE));
   }
}

 

Next, create a custom RemoteViewService to update the views in the widget. The reason this is required is because the app widget does not operate in the usual lifecycle of the app. And therefore a remote service is required which acts as the remote adapter to connect to the remote views. In your class, override the onGetViewFactory() method and create a new remoteViewsFactory object to get the the data from the app on updation of the bookmark list. To populate the remote views, override the getViewsAt() method.

public class BookmarkWidgetRemoteViewsService extends RemoteViewsService {

@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {

return new RemoteViewsFactory() {
   private MatrixCursor data = null;

   @Override
   public void onCreate() {
       //Called when your factory is first constructed.
   }

   @Override
   public void onDataSetChanged() {
       }

   @Override
   public RemoteViews getViewAt(int position) {
       } 
   }
}

 

Finally, create a custom AppWidgetProvider which parses the relevant fields out of the intent and updates the UI. It acts like a broadcast receiver, hence all the updates by the widgetUpdater is received here.

public class BookmarkWidgetProvider extends AppWidgetProvider {

   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
	  RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.bookmark_widget);
             setRemoteAdapter(context, views);

   }

   @Override
   public void onReceive(@NonNull Context context, @NonNull Intent intent) {
       super.onReceive(context, intent);
   }

   private void setRemoteAdapter(Context context, @NonNull final RemoteViews views) {
       views.setRemoteAdapter(R.id.widget_list,
               new Intent(context, BookmarkWidgetRemoteViewsService.class));
   }

}

 

Conclusion

For any event based apps, it is crucial that it regularly provide updates to its users and therefore app widget forms an integral part of that whole experience.

References

 

 

Continue ReadingCreate an App Widget for Bookmarked Sessions for the Open Event Android App

Implement Marker Clustering in the Open Event Android App

Markers are an integral part of any map based service. In the Open Event Android App for samples like Mozilla All Hands 2017, there are a lot of microlocations that the organizers want to integrate into the app’s map fragment. Due to the presence of large number of markers, the map fragment clutters, thereby harming the user experience. As an example, imagine yourself as the user and you see the map as in the image given below!

Therefore to tackle problem like this, the markers are grouped into clusters. On click of the cluster, the markers get declustered and fall into their respective locations with the map zoomed in.

Implementation

First and foremost, define the libraries to be used by the utilities in the build.gradle of your app module. Make to import the latest versions.

// Googleplay Variant
googleplayCompile 'com.google.android.gms:play-services-maps:10.2.6'
googleplayCompile 'com.google.android.gms:play-services-location:10.2.6'
googleplayCompile 'com.google.maps.android:android-maps-utils:0.4'

 

Implement the ClusterItem interface in your location POJO which will house a marker’s location. The POJO will therefore override the getPostion() method of the ClusterItem interface where you will return the LatLng.

public class MicrolocationClusterWrapper implements ClusterItem {

@Override
public LatLng getPosition() {
   return latLng;
}

}

 

Create a custom Cluster Renderer class that will extend the default cluster renderer with you location POJO as parameter. Implement ClusterManager’s onClusterItemClickListener to listen to marker clicks and add custom colors to them. Set the custom marker properties before the marker items are rendered with the markerOptions inside the onBeforeClusterItemRendered().

@Override
   protected void onBeforeClusterItemRendered(MicrolocationClusterWrapper item, MarkerOptions markerOptions) {
       super.onBeforeClusterItemRendered(item, markerOptions);

       markerOptions.title(item.getMicrolocation().getName());
       if (microlocationClusterWrapper != null && item.equals(microlocationClusterWrapper)) {
           markerOptions.icon(ImageUtils.vectorToBitmap(context, R.drawable.map_marker, R.color.color_primary));
       } else {
           markerOptions.icon(ImageUtils.vectorToBitmap(context, R.drawable.map_marker, R.color.dark_grey));
       }
   }

   @Override
   protected void onClusterItemRendered(final MicrolocationClusterWrapper clusterItem, Marker marker) {
       super.onClusterItemRendered(clusterItem, marker);
       clusterItem.setMarker(marker);
  }

   @Override
   public boolean onClusterItemClick(MicrolocationClusterWrapper item) {
       if (microlocationClusterWrapper != null) {
           getMarker(microlocationClusterWrapper).setIcon(ImageUtils.vectorToBitmap(context, R.drawable.map_marker, R.color.dark_grey));
       }
       microlocationClusterWrapper = item;
       getMarker(item).setIcon(ImageUtils.vectorToBitmap(context, R.drawable.map_marker, R.color.color_primary));
       return false;
   }
}

 

Finally in your map fragment, initialize your map, cluster manager class and your custom cluster renderer you just created. Implement the MapReadyCallback so that the Google Map object is not null. Remember to pass the cluster renderer as a listener for the cluster manager’s cluster item click listener. Use the setOnClusterClickListener to zoom the map on the click of cluster.

private void handleClusterEvents() {
   clusterManager.setOnClusterItemClickListener(clusterRenderer);

   clusterManager.setOnClusterClickListener(cluster -> {
               mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                       cluster.getPosition(), (float) Math.floor(mMap
                               .getCameraPosition().zoom + 2)), 300,
                       null);

               return true;
           });

   mMap.setOnMapClickListener(clusterRenderer);
}

 

Conclusion

Maps are an integral part of any event based apps and marker clustering undoubtedly enhances the user experience in Maps.

Resources

  • Marker Clustering Android documentation

https://developers.google.com/maps/documentation/android-api/utility/marker-clustering

  • Complete Code Reference

https://github.com/fossasia/open-event-android/pull/1777

  • Marker Customization in the case of Clustering

https://github.com/googlemaps/google-maps-ios-utils/issues/21

Continue ReadingImplement Marker Clustering in the Open Event Android App

Implement Caching in the Live Feed of Open Event Android App

In the Open Event Android App, a live feed from the event’s Facebook page was recently implemented. Since it was a live feed, it was decided that it was futile to store it in the Realm database of the app. The data of the live feed didn’t persist anywhere, hence the feed used to be empty when the app ran without the internet connection.

To solve the problem of data persistence, it was decided to store the feed in the cache. Now, there were two paths before us – use retrofit okhttp cache management or use volley. Since retrofit is used to make the API requests in the app, we used the former. To implement caching with retrofit, its API response should include the cache control header. Since it was not a response generated by a personal server, interceptors were needed to force change the request.

Interceptors

Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. The solution was to use interceptors to rewrite the calls to force use of cache. Two interceptors were added, application interceptor for the request and the network interceptor for the response.

Implementation

Create a cache file to store the response.

private static Cache provideCache() {
   Cache cache = null;
   try {
       cache = new Cache(new File(OpenEventApp.getAppContext().getCacheDir(), "facebook-feed-cache"),
               10 * 1024 * 1024); // 10 MB
   } catch (Exception e) {
       Timber.e(e, "Could not create Cache!");
   }
   return cache;
}

 

Create a network interceptor by chaining the response with the cache control header and removing the pragma header to force use of cache.

private static Interceptor provideCacheInterceptor() {
   return chain -> {
       Response response = chain.proceed(chain.request());

       // re-write response header to force use of cache
       CacheControl cacheControl = new CacheControl.Builder()
               .maxAge(2, TimeUnit.MINUTES)
               .build();

       return response.newBuilder()
               .removeHeader("Pragma")
               .header(CACHE_CONTROL, cacheControl.toString())
               .build();
   };
}

 

Create an application interceptor by chaining the request with the cache control header for stale responses and removing the pragma header to make the feed available for offline usage.

private static Interceptor provideOfflineCacheInterceptor() {
   return chain -> {
       Request request = chain.request();

       if (!NetworkUtils.haveNetworkConnection(OpenEventApp.getAppContext())) {
           CacheControl cacheControl = new CacheControl.Builder()
                   .maxStale(7, TimeUnit.DAYS)
                   .build();

           request = request.newBuilder()
                   .removeHeader("Pragma")
                   .cacheControl(cacheControl)
                   .build();
       }

       return chain.proceed(request);
   };
}

 

Finally add the cache and the two interceptors while building the okhttp client.

OkHttpClient okHttpClient = okHttpClientBuilder.addInterceptor(new HttpLoggingInterceptor()
       .setLevel(HttpLoggingInterceptor.Level.BASIC))
       .addInterceptor(provideOfflineCacheInterceptor())
       .addNetworkInterceptor(provideCacheInterceptor())
       .cache(provideCache())
       .build();

 

Conclusion

Working of apps without the internet connection builds up a strong case for corner cases while testing. It is therefore critical to persist data however small to avoid crashes and bad user experience.

Resources

Continue ReadingImplement Caching in the Live Feed of Open Event Android App

Create an AutocompleteTextView dropdown for the email input in the Open Event Orga Android App

In the first version of the Open Event Organizer App, the event organizer was required to enter his full email each time he logged out of his account and therefore it was hindering the user experience. AutoCompleteTextView with shared preferences is a solution to this problem. This feature provides an editable text view that shows completion suggestions automatically while the user is typing. The list of suggestions is displayed in a drop down menu. The user can choose an item to replace the content of the edit box with. It is extremely useful in enhancing user experience.

The solution we implemented was to create an autocomplete textview for the email input, store the email address of the user on a successful login in the shared preference in a set of strings to prevent duplicacy and display it in the dropdown on subsequent login attempts.

Implementation

Change your TextInputLayout structure to accommodate the autocompletetextview. Remember to create a separate autocompletetextview object with the specific id of the view.

<android.support.v7.widget.AppCompatAutoCompleteTextView
       android:id="@+id/email_dropdown"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="@string/email"
       android:inputType="textEmailAddress" />

 

Create Utility methods to get/store the emails in the shared preferences. The set data structure has been used here so that there is no duplicacy while storing the emails in the shared preferences.

public Set<String> getStringSet(String key, Set<String> defaultValue) {
   return sharedPreferences.getStringSet(key, defaultValue);
}

public void saveStringSet(String key, Set<String> value) {
   SharedPreferences.Editor editor = sharedPreferences.edit();
   editor.putStringSet(key, value);
   editor.apply();
}

public void addStringSetElement(String key, String value) {
   Set<String> set = getStringSet(key, new HashSet<>());
   set.add(value);
   saveStringSet(key, set);
}

 

Create helper methods to add an email and retrieve the list of emails from the shared preferences to provide it to the views.

private void saveEmail(String email) {
   utilModel.addStringSetElement(Constants.SHARED_PREFS_SAVED_EMAIL, email);
}

private Set<String> getEmailList() {
   return utilModel.getStringSet(Constants.SHARED_PREFS_SAVED_EMAIL, null);
}

 

Create an autocompleteTextView object in your activity with the help of the R id from the layout and set the adapter with the set of strings retrieved from the shared preferences. You could create a custom adapter for this case too, but as far as the Open Event Orga App was concerned, using the array adapter made sense.

autoCompleteEmail.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
   new ArrayList<String>(emails)));

 

Conclusion

It is important that the user is served with the best possible experience of the application and the autocomplete text view for the email serves just that.

Resources

Continue ReadingCreate an AutocompleteTextView dropdown for the email input in the Open Event Orga Android App

Generate a Live feed from Facebook for the Open Event Android App

To increase the user engagement in the Open Event Android app, the feed was a welcome feature for the users. In order to fetch data from a public facebook page, firstly the facebook page id should be fetched from an API GET request. To fetch the page id, the page name have to be included in the API request along with the access token generated from the facebook developers page itself.

To fetch the data from Facebook, it requires the user to login to Facebook. Facebook requires its SDK to be set up first on the android itself to make the requests. Hence it destroys the motive to have feed of a public Facebook page.

Solution

The solution involves making custom API requests to the Facebook servers in order to fetch the data. 3 API requests have to be made. The first fetches the access token for the developer to access the facebook servers for the data. It’s going to be a one time call, so just make the API request in the browser itself and fetch the token with the help of the app ID and the app secret. The second fetches the page id of a particular facebook page from the given facebook page name. The third fetches the data using the page id with the fields specified in the query. The API requests will be made with the help of the retrofit library and the object communication throughout the app will be catered by rxJava library.

Implementation

Create a Facebook developer account. Generate the app ID for your app along with its secret.

Make a GET request to fetch the access token from the app ID and app secret.

https://graph.facebook.com/oauth/access_token?client_id={APP_ID}&client_secret={APP_SECRET}&grant_type=client_credentials

#NOTE: Remove the brackets!

Make the appropriate JSON getters and setters of the feed from the GET request. Use http://www.jsonschema2pojo.org/ to autogenerate them with the help of a JSON input.

Create the Facebook Graph API interface to make the GET requests. Make them observable for object communication using rxJava.

public interface FacebookGraphAPI {

  @GET(“/{event_name}”)
  Observable<FacebookPageId> getPageId(@Path(“event_name”) String eventName, @Query(“access_token”) String accessToken);

  @GET(“/{page_id}/feed”)
  Observable<Feed> getPosts(@Path(“page_id”) String pageId, @Query(“fields”) String fields, @Query(“access_token”) String accessToken);

}

Create an API client to build the retrofit along with the OkHttpClient

public final class APIClient {

  private static final int CONNECT_TIMEOUT_MILLIS = 20 * 1000; // 15s

  private static final int READ_TIMEOUT_MILLIS = 50 * 1000; // 20s

  private static FacebookGraphAPI facebookGraphAPI;

  private static Retrofit.Builder retrofitBuilder;

  static {
      OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient().newBuilder()
              .connectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
              .readTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
      if (BuildConfig.DEBUG)
          okHttpClientBuilder.addNetworkInterceptor(new StethoInterceptor());
      OkHttpClient okHttpClient = okHttpClientBuilder.addInterceptor(new HttpLoggingInterceptor()
              .setLevel(HttpLoggingInterceptor.Level.BASIC))
              .build();

      retrofitBuilder = new Retrofit.Builder()
              .addConverterFactory(JacksonConverterFactory.create(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)))
              .client(okHttpClient);
  }

  public static FacebookGraphAPI getFacebookGraphAPI() {
      if (facebookGraphAPI == null)
          facebookGraphAPI = retrofitBuilder
                  .baseUrl(Urls.FACEBOOK_BASE_URL)
                  .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                  .build()
                  .create(FacebookGraphAPI.class);
      return facebookGraphAPI;
  }

}

Create a utility method to get the relative time from the timestamp of the post.

private static final Locale defaultLocale = Locale.getDefault();
private static String iso8601WithTimezone = “yyyy-MM-dd’T’HH:mm:ssZ”;
private static final String ISO_TIMEZONE_FORMATTER = new SimpleDateFormat(iso8601WithTimezone, defaultLocale);

@NonNull
private static Date getDate(@NonNull SimpleDateFormat formatter, @NonNull String isoDateString) throws ParseException {
  return formatter.parse(isoDateString);
}

public static String getRelativeTimeFromTimestamp(String timeStamp) throws ParseException {
  Date timeCreatedDate = getDate(ISO_TIMEZONE_FORMATTER, timeStamp);

  return (String) android.text.format.DateUtils.getRelativeTimeSpanString(
          (timeCreatedDate.getTime()),
          System.currentTimeMillis(), android.text.format.DateUtils.SECOND_IN_MILLIS);
}

Create a feed fragment, download and subscribe to the feed with the help of the retrofit and rxjava library. Notify the adapter when the data set changes.

private void downloadFeed() {
  APIClient.getFacebookGraphAPI()
          .getPosts(sharedPreferences.getString(ConstantStrings.FACEBOOK_PAGE_ID, null),
                  getContext().getResources().getString(R.string.fields),
                  getContext().getResources().getString(R.string.facebook_access_token))
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(feed -> {
              feedItems.clear();
              feedItems.addAll(feed.getData());
              feedAdapter.notifyDataSetChanged();
              handleVisibility();
          }, throwable -> {
              Snackbar.make(swipeRefreshLayout, getActivity()
                      .getString(R.string.refresh_failed), Snackbar.LENGTH_LONG)
                      .setAction(R.string.retry_download, view -> refresh()).show();
              Timber.d(“Refresh not done”);
          }, () -> {
              swipeRefreshLayout.setRefreshing(false);
              Timber.d(“Refresh done”);
          });
}

Conclusion

The open event android project currently only fetches feed from the facebook. Feeds from twitter and youtube will be fetched too in the near future. Watch out the next blog on how to display the comments of Facebook post on a dialogfragment over the feed fragment.

For more information on the code, please take help from here.

Continue ReadingGenerate a Live feed from Facebook for the Open Event Android App

Iteration through the Android File System in the phimpme project

Android uses the single file system structure which has a single root. The task involved creating a custom folder chooser to whitelist folders while displaying images in the gallery in the Phimpme Photo App. The challenge arose in iterating over the files in the most efficient way. The best possible way to represent the file structure is in the form of tree data structure as given below.

Current Alternative

Currently, the MediaStore class contains metadata for all available media on both internal and external storage devices. Since it only returns a list of a particular media file format, it refrains the developer from customizing the structure in his way.

Implementation

Create a public class which represents the file tree. Since each subtree of the tree could itself be represented as file tree itself, therefore the parent of a node will be a FileTree object itself. Therefore declare a list of FileTree objects as children of the node, a FileTree object as the parent of the particular node, node’s own File object along with string values filepath and display name associated with it.

public class FileTree {
 public final String filepath;
 public final String displayName;
 public final List<FileTree> childFileTreeList = new ArrayList<>();
 public final FileTree parent;
 public boolean hasMedia = false;

 public FileTree(String filepath, String displayName, FileTree parent) {
    this.filepath = filepath;
    this.displayName = displayName;
    this.parent = parent;
 }
}

For iterating through the file system, we create a recursive function which is called on the root of the Android file system. If the particular file is a directory, with the help of Depth First traversal algorithm, the directory is traversed. Else, the file is added to the list of the file. The below code snippet is the recursive function.

public static void walkDir(FileTree fileTree, List<File> files) {
  File listFile[] = fileTree.file.listFiles();

  if (listFile != null) {
      for (File file : listFile) {
          if (file.isDirectory()) {
              FileTree childFileTree = new FileTree(file, file.getName(), fileTree);
              fileTree.childFileTreeList.add(childFileTree);
              walkDir(childFileTree, files);
          }
          else {
                  files.add(file);

          }
      }
  }
}

Conclusion

The android file system was used to whitelist folders so that the images of the folders could neither be uploaded nor edited.

For the complete guide to whitelisting folders, navigate here

Continue ReadingIteration through the Android File System in the phimpme project

Allow users to change language in the Open Event Android app via settings

Localisation of the application is a major step towards maximizing market penetration especially in cases like that of India which houses about 22 official languages. Therefore it is important to allow users to change their language preference from the Open Event Android App itself. There are two different ways through which this could be implemented.

  1. Navigate the user to the phone settings page by firing an implicit intent.
  2. Build an in-app language chooser specific to the country and prompt user to choose language on the first launch of the application.

Out of the two, the open event developers community along the mentors decided to go with the first option since it seemed more apt.

Implementation

In order to implement this, the first and foremost requirement is to keep culture-specific resources separated from the rest of your app. Android resolves language and culture-specific resources based on the system locale setting. Support is provided for different locales by using the resources directory in the Android project. The resources especially the string resources have their own language specific string.xml files and they get resolved with the help of key-value pair mapping. The following steps implement it –

Create a static variable in the application class of the app and assign it the default display language. Make sure to override OnConfigurationChanged method to change the language of the app when the user changes the language.

public class OpenEventApp extends Application {

public static String sDefSystemLanguage;

@Override
public void onCreate() {
 super.onCreate();
 
 sDefSystemLanguage = Locale.getDefault().getDisplayLanguage();
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
 super.onConfigurationChanged(newConfig);
 sDefSystemLanguage = newConfig.locale.getDisplayLanguage();
}
}

Include a preference category in your application’s settings.xml which corresponds to the preference activity of the app.

< PreferenceCategory
android: key = “change_language_category”
android: title = “@string/language_preferences” >

< Preference
android: defaultValue = “@string/default_mode_language”
android: key = “@string/language_key”
android: title = “@string/change_language” / >
< /PreferenceCategory>

Finally in your preference activity, set your language preference to the string defined in the application class and implement a click listener on the preference to fire an implicit intent.

public class SettingsActivity extends PreferenceActivity implements Preference.OnPreferenceChangeListener {

private Preference languagePreference;
private SharedPreferences preferences;

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 preferences = PreferenceManager.getDefaultSharedPreferences(this);
 addPreferencesFromResource(R.xml.settings);
 setContentView(R.layout.activity_settings);

 languagePreference = findPreference(LANGUAGE_PREF_MODE);
 languagePreference.setSummary(OpenEventApp.sDefSystemLanguage);
}

@Override
public boolean onPreferenceChange(Preference preference, Object o) {

  if (preference.getKey().equals(getResources().getString(R.string.language_key))) {
  languagePreference.setSummary((String) o);
 }

 return false;
}

 @Override
 public void onResume() {
 super.onResume();
 languagePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
  @Override
  public boolean onPreferenceClick(Preference preference) {
   startActivity(new Intent((Settings.ACTION_LOCALE_SETTINGS)));
   return true;
  }
 });

 languagePreference.setSummary(OpenEventApp.sDefSystemLanguage);
}

}

Conclusion

Language can be the greatest asset for an app to reach maximum market capitalisation. It is therefore critical for the apps like open event android to reach scalability for the events to have maximum outreach.

Continue ReadingAllow users to change language in the Open Event Android app via settings

Enhance the Map Fragment in the Open Event Android app

The usual map fragment in the Open Event Android App didn’t match to the other UI enhancements in the app. Therefore it was needed to enhance it in order for the users to take full advantages of the map based services that the app offered. The enhancements primarily included the following features –

  • Better marker drawables – The usual markers were dull and didn’t match the app theme of the rest of the app.
  • Location searching with autocomplete dropdown – Initially, there was no way the user could search for the location of the sessions happening in the event. Also, it was decided that it was best if the search auto suggested locations for easy navigation.
  • Navigation to the location marker – The google map class comes bundled with in built UI features such as floor recognition, location pointer etc. Navigation to the location is one of those features which appears on a marker click. The user gets directed to the Google Maps android application for navigation from the current location.

Implementation

Add the following dependencies to your app’s Gradle build file. Do update the libraries if a newer version is available.

dependencies {
  compile ‘com.google.maps.android:android-maps-utils:0.4+’
  compile ‘com.google.android.gms:play-services-location:8.1.0’
  compile ‘com.google.android.gms:play-services-maps:8.1.0’
}

Use vector drawables instead of raster icons for the marker

<!– drawable/map_marker.xml –>
<vector xmlns:android=“http://schemas.android.com/apk/res/android”
  android:height=“40dp”
  android:width=“40dp”
  android:viewportWidth=“40”
  android:viewportHeight=“40”>
  <path android:fillColor=“#000” android:pathData=“M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z” />
</vector>

Customize the marker events including color selections onclick and title display.

private Marker handleMarkerEvents(LatLng location, String locationTitle) {
  if (mMap != null) {
      locationMarker = mMap.addMarker(new MarkerOptions().position(location).title(locationTitle)
              .icon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey)));
      mMap.setOnMarkerClickListener(marker -> {
          locationMarker.setIcon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey));
          locationMarker = marker;
          marker.setIcon(vectorToBitmap(getContext(), R.drawable.map_marker, R.color.color_primary));
          return false;
      });
      mMap.setOnMapClickListener(latLng -> locationMarker.setIcon(
              vectorToBitmap(getContext(), R.drawable.map_marker, R.color.dark_grey)));
  }
  return locationMarker;
}

//Vector currently cannot be directly used with the markers hence it is necessary to convert it into bitmap first

private BitmapDescriptor vectorToBitmap(Context context, @DrawableRes int id, int color) {
  Drawable vectorDrawable = ResourcesCompat.getDrawable(context.getResources(), id, null);
  assert vectorDrawable != null;
  Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
          vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
  DrawableCompat.setTint(vectorDrawable, ContextCompat.getColor(context, color));
  vectorDrawable.draw(canvas);
  return BitmapDescriptorFactory.fromBitmap(bitmap);
}

Enable the google maps UI enhancements by getting the UI settings and subsequently setting true to google map toolbar

mMap.getUiSettings().setMapToolbarEnabled(true);

Conclusion

The google maps is an important service for the open event android project since events are all about happenings and happenings are best served with accurate locations of their whereabouts.

For more information on the code, please visit the link here.

Continue ReadingEnhance the Map Fragment in the Open Event Android app

Remove redundancy of images while uploading it from the Phimpme photo app

Uploading images is an important feature for the Phimpme Photo App. While uploading images in the Phimpme application, there was a high probability that the user uploaded redundant images since there was no check on the upload. Therefore to tackle this problem, images should be hashed so that no two same images gets uploaded. To generate a hash of the file, the md5 algorithm and the sha1 algorithm is used.

What is MD5 algorithm?

The MD5 hashing algorithm is a one-way cryptographic function that accepts a message of any length as input and returns as output a fixed-length digest value to be used for authenticating the original message. It produces a 128 bit hash value. 

WhatIs.com

What is SHA1 algorithm?

SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function designed by the United States National Security Agency and is a U.S. Federal Information Processing Standard published by the United States NIST. SHA-1 produces a 160-bit (20-byte) hash value known as a message digest. A SHA-1 hash value is typically rendered as a hexadecimal number, 40 digits long.  

 – Wikipedia

Reason for using MD5 + SHA1 instead of SHA256

  1. The file becomes highly secured while maintaining its integrity.
  2. The algorithm MD5 + SHA1 is faster in hashing than SHA256.

Implementation

Create a separate Utils class named FileUtils.java. The class contains the static function to get the hash of any file in general. The message digest class is being used in both the algorithms.

The hash is the combination of the md5 hash string along with the SHA1 hash string. The message digest takes up algorithm as its argument to specify which hashing function would be used. The fileinputstream takes up the input file for initialization and the message digest is updated with the data bytes of the file. The StringBuffer class is then used to generate the fixed length hexadecimal value from the message digest since the string is going to be mutable in nature. The string value of the generated string buffer object is returned.

public class FileUtils {

//The utility method to get the hash of the file
 public static String getHash(final File file) throws NoSuchAlgorithmException, IOException {

  return md5(file) + "_" + sha1(file);
 }

//The md5 alogorithm
 public static String md5(final File file) throws NoSuchAlgorithmException, IOException {

  MessageDigest md = MessageDigest.getInstance("MD5");
  FileInputStream fis = new FileInputStream(file);
  byte[] dataBytes = new byte[1024];
  int nread = 0;
  while ((nread = fis.read(dataBytes)) != -1) {
   md.update(dataBytes, 0, nread);
  }

  //convert the byte to hex format
  StringBuffer sb = new StringBuffer("");
  for (byte mdbyte: md.digest()) {
   sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1));
  }

  fis.close();
  md.reset();
  return sb.toString();
 }

//The SHA1 algorithm
 public static String sha1(final File file) throws NoSuchAlgorithmException, IOException {

  MessageDigest md = MessageDigest.getInstance("SHA1");
  FileInputStream fis = new FileInputStream(file);
  byte[] dataBytes = new byte[1024];
  int nread = 0;
  while ((nread = fis.read(dataBytes)) != -1) {
   md.update(dataBytes, 0, nread);
  }

  //convert the byte to hex format
  StringBuffer sb = new StringBuffer("");
  for (byte mdbyte: md.digest()) {
   sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1));
  }

  fis.close();
  md.reset();
  return sb.toString();
 }
}

Conclusion

Physical memory is limited. Hence it is of utmost importance that a particular file is identified using a unique identifier in terms of a fixed hash value. Not only will it be beneficial for optimum space utilization, it will also be useful to track it if necessary.

To learn more about the hash alogorithms, visit – https://www.tutorialspoint.com/cryptography/cryptography_hash_functions.htm  

 

Continue ReadingRemove redundancy of images while uploading it from the Phimpme photo app