Creating SharedPreferences Util in Open Event Android

In the Open Event Android we have the fragment for schedule, speakers which has the option to sort the list. Schedule Fragment have the option to sort by Title, Tracks and  Start Time. Speakers Fragment has the option to sort by Name, Organization and Country. If the user preferred to sort by name then it should always sort the list by name whenever the user uses the app. For this we need to store user preference for sorting list. Another part of the app like Live feed, About fragment also needs to store event id, facebook page id/name etc.

In Android there is a SharedPreferences class to store key value pair in the App specific storage. To store data in SharedPreferences we need to create SharedPreferences Object in different activities and fragment. In this post I explain how to create SharedPreferences Util which can be used to store key value pairs from all over the App.

1. Create SharedPreferencesUtil Class

The first step is to create SharedPreferncesUtil.java file which will contain static SharedPreferences object.

public class SharedPreferencesUtil {
    ...
}

2. Create static objects

Create static SharedPreferences and SharedPreferences.Editor object in the SharedPreferncesUtil.java file.

private static SharedPreferences sharedPreferences;
private static SharedPreferences.Editor editor;

3. Initialize objects

Now after creating objects initialize them in the static block. The code inside static block is executed only once: The first time you make an object of that class or the first time you access a static member of that class.

static {
        sharedPreferences = OpenEventApp.getAppContext().getSharedPreferences(ConstantStrings.FOSS_PREFS, Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();
}

 

Here make sure to use the Application context to avoid a memory leak. The getSharedPreferences() method takes two arguments name of the shared preference and mode. Here we are using Context.MODE_PRIVATE File creation mode where the created file can only be accessed by the calling application.

4. Add methods

Now create static methods to store data so that we can use these methods directly from the other activities or classes. Here I am only adding methods for integer you can add more methods for String, long, boolean etc.

public static void putInt(String key, int value) {
        editor.putInt(key, value).apply();
}

public static int getInt(String key, int defaultValue) {
        return sharedPreferences.getInt(key, defaultValue);
}

5. Use SharedPreferencesUtil class

Now we are ready to use this Util class to store key value pair in SharedPreferences.

SharedPreferencesUtil.putInt(ConstantStrings.PREF_SORT, sortType);

Here the putInt() methods take two arguments one the key and second the value. To get the stored value use getInt() method.

SharedPreferencesUtil.getInt(ConstantStrings.PREF_SORT, 0);

To know more how I solved this issue in Open Event Project visit this link.

Adding JSON API support to ember-models-table in Open Event Front-end

Open Event Front-end project uses ember-models-table for handling all the table components in the application. Although ember-models-table is great for handling server requests for operations like pagination, sorting & filtering, but it does not support JSON API used in the Front-end project.

In this blog we will see how we integrated JSON API standards to ember-models-table. Lets see how we added support for JSON API to table and made requests to the Open Event Orga-server.

Adding JSON API support for filtering & sorting

The JSON API specs follow a strict structure for supporting meta data & filtering options, the server expects an array of objects for specifying the name of the field, operation and the value for filtering. The name attribute specifies the column for which we need to apply the filter. eg we use `name` for the events name in the. `op` attribute specifies the operation to be used for filtration, `val` attribute is used to provide a value for comparison. You can check the list of all the supported operations here.

For implementation of filter we will check if the column filter is being used i.e if the filter string is empty or not, if the string is not empty we add a filter object of the column using the specified specs, else we remove the filter object of the column.

if (filter) {
  query.filter.pushObject({
    name : filterTitle,
    op   : 'ilike',
    val  : `%${filter}%`
  });
} else {
  query.filter.removeObject({
    name : filterTitle,
    op   : 'ilike',
    val  : `%${filter}%`
  });
}

For sort functionally we need to pass a query parameter called `sort` which is a string value in the URL. Sorting can be done in ascending or descending order for which the server expects different values. We pass `sort=name` & `sort=-name` for sorting in ascending order & descending order respectively.

const sortSign = {
  none : '',
  asc  : '-',
  desc : ''
};
let sortedBy = get(column, 'sortedBy');
if (typeOf(sortedBy) === 'undefined') {
  sortedBy = get(column, 'propertyName');
}

Adding support for pagination

The pagination in JSON API is implemented using query parameters `page[size]` & `page[number]` which specify the size of the page & the current page number respectively eg

page[size]=10&page[number]=1

This will load the first ten events from the server in the application.

Once the data is loaded in the application we calculate the number of pages to be rendered. The response from the server has attached meta-data which contains the total number of the events in the following structure:

meta: {
  count: 100
}

We calculate the number of pages by dividing the total count by the size of the page. We check if the number of items is greater than the pageSize, and calculate the number of the pages using the formula `items / pagesize + (items % pagesize ? 1 : 0)`. If the items are less than the pageSize we do not have to calculate the pages and we simply hide the pagination in the footer.

if (pageSize > items) {
  this.$('.pagination').css({
    display: 'none'
  });
} else {
  this.$('.pagination').removeAttr('style');
  pages = parseInt((items / pageSize));
  if (items % pageSize) {
    pages = pages + 1;
  }
}

Adding dynamic routing support to ember-models-table

We may want to use the ember-models-table for dynamic routes like `events/list` route, where we load live, drafted & past events based on the current route. The ember-models-table by default do not support the dynamic routes. To add this we override the didReceiveAttrs() method of the component which is executed every time the component updates. We add reset the pageSize, currentPageNumber and the content of the table, as the routes change.

didReceiveAttrs() {
  set(this, 'pageSize', 10);
  set(this, 'currentPageNumber', 1);
  set(this, 'filteredContent', get(this, 'data'));
}

The result of this we now have tables supporting JSON API in the Open Event Front-end application

Thank you for reading the blog, you can check the source code for the example here.

Resources

Implementation of Speech UI in Susper

Recently, we have implemented a speech recognition feature in Susper where user could search by voice but it does not have an attractive UI. Google has a good user experience while recording the voice. We have implemented a similar Speech UI in Susper,

How we have implemented this?

  1. First we made a component speechtotext. It takes care of all the styling and functional changes of the speech UI and rendering the speech and any instructions required for the user. https://github.com/fossasia/susper.com/tree/master/src/app/speechtotext
  2. Initially when user clicks on the microphone in the search bar, it triggers the speechRecognition()

searchbar.component.html

<div class="input-group-btn">
 <button class="btn btn-default" id="speech-button" type="submit">
   <img src="../../assets/images/microphone.png" class="microphone" (click)="speechRecognition()"/>
 </button>

searchbar.component.ts

speechRecognition() {
 this.store.dispatch(new speechactions.SearchAction(true));
}

3) This dispatches an action speechaction.SearchAction(true), the app.component.ts is subscribed to this action and whenever this action is triggered the app component will open the speechtotext component.

Speechtotext.component.ts

Speech to text component on getting initialised calls the speech service’s record function which activates standard browser’s speech API

constructor(private speech: SpeechService) {
 
 this.speechRecognition();
}
speechRecognition() {
 this.speech.record('en_US').subscribe(voice => this.onquery(voice));
}

On recording the user’s voice and converting it to text, the text is sent to the onquery method as input and the recognised text is sent to other components through ngrx store.

onquery(event: any) {
 this.resettimer();
 this.store.dispatch(new queryactions.QueryServerAction({ 'query': event, start: 0, rows: 10, search: true }));
 this.message = event;
}

We have some UI text transitions where the user is shown with messages like ‘Listening…’ ,‘Speak Now’ and ‘Please check your microphone’ which are handle by creating a timer observable in angular.

ngOnInit() {
 this.timer = Observable.timer(1500, 2000);
 this.subscription = this.timer.subscribe(t => {
   this.ticks = t;

   if (t === 1) {
     this.message = "Listening...";
   }
   if (t === 4) {
     this.message = "Please check your microphone and audio levels.";
     this.miccolor = '#C2C2C2';
   }
   if (t === 6) {
     this.subscription.unsubscribe();
     this.store.dispatch(new speechactions.SearchAction(false));
   }
 });
}

The related PR regarding speech to text is at https://github.com/fossasia/susper.com/issues/624 .

With this now we have achieved a good UI for handling requests on Speech.

Resources:

Binding Images Dynamically in Open Event Orga App

In Open Event Orga App (Github Repo), we used Picasso to load images from URLs and display in ImageViews. Picasso is easy to use, lightweight, and extremely configurable but there has been no new release of the library since 2015. We were using Picasso in binding adapters in order to dynamically load images using POJO properties in the layout XML itself using Android Data Binding. But this configuration was a little buggy.

The first time the app was opened, Picasso fetched the image but it was not applied to the ImageView. When the device was rotated or the activity was resumed, it loaded just fine. This was a critical issue and we tried many things to fix it but none of it quite fit our needs. We considered moving on to other Image Loading libraries like Glide, etc but it was too heavy on the size and functionality for our needs. The last resort was to update the library to develop version using Sonatype’s snapshots Repository. Now, the Picasso v2.6.0-SNAPSHOT is very stable but not released to the maven central repository, and a newer develop version v3.0.0-SNAPSHOT was launched too. So we figured we should use that. This blog will outline the steps to include the develop version of Picasso, configuring it for our needs and making it work with Android Data Binding.

Setting up Dependencies

Firstly, we need to include the sonatype repository in the repositories block of our app/build.gradle

repositories {
   ...
   maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

 

Then we need to replace the Picasso dependency entry to this:

compile 'com.squareup.picasso:picasso:3.0.0-SNAPSHOT'

 

Note that if you used Jake Wharton’s OkHttp3 Downloader for Picasso, you won’t need it now, so you need to remove it from the dependency block

And you need to use this to import the downloader

import com.squareup.picasso.OkHttp3Downloader;

 

Next, we set up our Picasso DI this way

Picasso providesPicasso(Context context, OkHttpClient client) {
   Picasso picasso = new Picasso.Builder(context)
       .downloader(new OkHttp3Downloader(client))
       .build();
   picasso.setLoggingEnabled(true);
   return picasso;
}

 

Set the singleton instance in our application:

Picasso.setSingletonInstance(picasso);

 

And we are ready to use it.

Creating Adapters

Circular Image Adapter

We show event logos as circular images, so we needed to create a binding adapter for that:

@BindingAdapter("circleImageUrl")
public static void bindCircularImage(ImageView imageView, String url) {
   if(TextUtils.isEmpty(url)) {
       imageView.setImageResource(R.drawable.ic_photo_shutter);
       return;
   }

   Picasso.with()
       .load(Uri.parse(url))
       .error(R.drawable.ic_photo_shutter)
       .placeholder(R.drawable.ic_photo_shutter)
       .transform(new CircleTransform())
       .tag(MainActivity.class)
       .into(imageView);
}

 

If the URL is empty, we just show the default photo, and otherwise we load the image into the view using standard CircleTransform

Note that there is no context argument in the with method. This was implemented in Picasso recently where they removed the need for context for loading images. Now, they use a Dummy ContentProvider to get application context, which is inspired by how Firebase does it.

Now, we can just normally use this binding in layout to load the event thumbnail like this

<ImageView
   android:layout_width="@dimen/image_small"
   android:layout_height="@dimen/image_small"
   android:contentDescription="@string/event_thumbnail"
   app:circleImageUrl="@{event.thumbnailImageUrl}" />

 

This gives us a layout like this:

Next we need to load the header image with a deafult image.

Default Image Adapter

For this, we write a very simple adapter without CircleTransform

@BindingAdapter(value = { "imageUrl", "placeholder" }, requireAll = false)
public static void bindDefaultImage(ImageView imageView, String url, Drawable drawable) {
   if(TextUtils.isEmpty(url)) {
       if (drawable != null)
           imageView.setImageDrawable(drawable);
       return;
   }

   RequestCreator requestCreator = Picasso.with().load(Uri.parse(url));

   if (drawable != null) {
       requestCreator
           .placeholder(drawable)
           .error(drawable);
   }

   requestCreator
       .tag(MainActivity.class)
       .into(imageView);
}

 

As imageUrl or placeholder can be null, we check for both, and setting correct images if they are not. We use this in our header layout with both the url and default image we need to show:

<ImageView
   android:scaleType="centerCrop"
   app:imageUrl="@{ event.largeImageUrl }"
   app:placeholder="@{ @drawable/header }"
   android:contentDescription="@string/event_background" />

 

And this gives us a nice dynamic header like this:

This wraps up the blog on Picasso’s latest develop version and Binding Adapters. If you want to know more about Picasso and Android Data Binding, check these links:

Implementation of Colour Picker in Phimpme Android Application

In Phimpme Android Project, we have used Color Picker palette which has a various implementation in the project such as changing the theme color of the application, to choose the color for the text while editing an image.

Making XML Layout to choose the specific color from the color palette

The main aim was to provide Line of color, and the user can simply slide through the different colors. A single line should contain all the main colors. I took help of the Open Source project to arrange the layout of the colors. We will display the Color palette in a cardview.

<RelativeLayout
   android:id="@+id/container_edit_text"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   android:padding="@dimen/big_spacing">
   <uz.shift.colorpicker.LineColorPicker
       xmlns:app="http://schemas.android.com/apk/res-auto"
       android:id="@+id/color_picker_accent"
       android:layout_width="match_parent"
       android:layout_height="60dp"
       app:orientation="horizontal"
       app:selectedColorIndex="0" />
</RelativeLayout>

Inflating the list with the colors

We will inflate the list with all basic colors such as Red, Purple, Blue, Light Blue, Green, Yellow, Orange, Deep orange, Brown, Blue Gray. getAccentColors() function returns the specific color HEXCODE. A color code is six string length long code combination consisting of numbers and alphabets which are stored in the colors.xml resource file. For example, the color code for black is  #000000 and for white #FFFFFF.  

public static int[] getAccentColors(Context context){
   return new int[]{
           ContextCompat.getColor(context, R.color.md_red_500),
           ContextCompat.getColor(context, R.color.md_purple_500),
           ContextCompat.getColor(context, R.color.md_deep_purple_500),
           ContextCompat.getColor(context, R.color.md_blue_500),
           ContextCompat.getColor(context, R.color.md_light_blue_500),
           ContextCompat.getColor(context, R.color.md_cyan_500),
           ContextCompat.getColor(context, R.color.md_teal_500),
           ContextCompat.getColor(context, R.color.md_green_500),
           ContextCompat.getColor(context, R.color.md_yellow_500),
           ContextCompat.getColor(context, R.color.md_orange_500),
           ContextCompat.getColor(context, R.color.md_deep_orange_500),
           ContextCompat.getColor(context, R.color.md_brown_500),
           ContextCompat.getColor(context, R.color.md_blue_grey_500),
   };
}

Implementation of ColorPicker to change the Text color in the Edit Image Activity

Color Palette is implemented the followings steps:

 First,                                                                                                                                        A dialog box is made. This dialog will help to display the color palette.

 Second,                                                                                                                                We need to inflate the dialog box with color_picker_accent XML file we created  in the resource folder.

 Third,                                                                                                                                    We set the dialog box with the line of colors.

 Fourth,                                                                                                                                Get the selected color code. To get the selected color code we used the class  LineColorPicker. The color_picker_accent sends an id for the selected color,  where all the color code is stored.   

final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
final View dialogLayout = getActivity().getLayoutInflater().inflate(R.layout.color_piker_accent, null);
final LineColorPicker colorPicker = (LineColorPicker) dialogLayout.findViewById(R.id.color_picker_accent);
final TextView dialogTitle = (TextView) dialogLayout.findViewById(R.id.cp_accent_title);
dialogTitle.setText(R.string.text_color_title);
colorPicker.setColors(ColorPalette.getAccentColors(activity.getApplicationContext()));
changeTextColor(WHITE);

In when a user selects a particular color he can either choose OK and CANCEL

When user selects OK

We implemented onClick event to change the color of the text by using the function changeTextColor() which accepts color code as the parameter.

dialogBuilder.setPositiveButton(getString(R.string.ok_action).toUpperCase(), new DialogInterface.OnClickListener() {
   public void onClick(DialogInterface dialog, int which) {
       changeTextColor(colorPicker.getColor());
   }
});

When user selects Cancel

When the user selects cancel, we have set the default color of the text that is White.

dialogBuilder.setNeutralButton(getString(R.string.cancel).toUpperCase(), new DialogInterface.OnClickListener() {
   @Override
   public void onClick(DialogInterface dialog, int which) {
       changeTextColor(WHITE);
       dialog.cancel();
   }
});

changeTextColor function

It accepts the color code. We used this color to change the text color by using the inbuilt function setBackgroundColor() and setTextColor().

private void changeTextColor(int newColor) {
   this.mTextColor = newColor;
   mTextColorSelector.setBackgroundColor(mTextColor);
   mTextStickerView.setTextColor(mTextColor);
}

Conclusion

In this way, it provides a rich user experience. It adds vibrant colors in the application. A quick way to choose the color from the variety of option.

Github

https://github.com/fossasia/phimpme-android

Resources

Scaling the logo of the generated events properly in Open Event Webapp

In the Facebook Developer Conference, the logo was too small

27190158-334a5446-5211-11e7-95e8-9690dfe8312e.png

In the Open Tech Summit Event, the logo was too long and increased the height of the navigation bar

77dc9fb0-3966-11e7-92c3-d6321dc98ac7.png

We decide some constraints regarding the width and the height of the logo. We don’t want the width of the logo to exceed greater than 110 pixels in order to not let it become too wide. It would look odd on small and medium screen if barely passable on bigger screens. We also don’t want the logo to become too long so we set a max-height of 45 pixels on the logo. So, we apply a class on the logo element with these properties

.logo-image {
 max-width: 110px;
 max-height: 45px;
}

But simply using these properties doesn’t work properly in some cases as shown in the above screenshots. An alternative approach is to resize the logo appropriately during the generation process itself. There are many different ways in which we can resize the logo. One of them was to scale the logo to a fixed dimension during the generation process. The disadvantage of that approach was that the event logo comes in different size and shapes. So resizing them to a fixed size will change its aspect ratio and it will appear stretched and pixelated. So, that approach is not feasible. We need to think of something different.  After a lot of thinking, we came up with an algorithm for the problem. We know the height of the logo would not be greater than 45px. We calculate the appropriate width and height of the logo, resize the image, and calculate dynamic padding which we add to the anchor element (inside which the image is located) if the height of the image comes out to be less than 45px. This is all done during the generation of the app. Note that the default padding is 5px and we add the extra pixels on top of it. This way, the logo doesn’t appear out of place or pixelated or extra wide and long. The detailed steps are mentioned below

  • Declare variable padding = 5px
  • Get the width, height and aspect ratio of the image.
  • Set the height to 45px and calculate the width according to the aspect ratio. If the width <= 110px, then directly resize the image and no change in padding is necessary
  • If the width > 110px, then make width constant to 110px and calculate height according to the aspect ratio. It will surely come less than 45px. Subtract the difference = (45 – height), divide it by 2 and add it to the padding variable.
  • Apply padding variable on the anchor tag. Now every logo should be displayed nicely and we have fixed the height of the navigation bar = 55px for all cases.

Here is an excerpt of the code. The whole work and discussion can be viewed here

var optimizeLogo = function(image, socket, done) {
 sharp(image).metadata(function(err, metaData) {
   if(err) {
     return done(err);
   }
   var width = metaData.width;
   var height = metaData.height;
   var ratio = width/height;
   var padding = 5;
   var diffHeight = 0;

   height = 45;
   width = Math.floor(45 * ratio);
   if (width > 110) {
     width = 110;
     height = Math.floor(width/ratio);
     diffHeight = 45 - height;
     padding = padding + (diffHeight)/2;
   }
   sharp(image).resize(width, height).toFile(image + '.new', function(err, info) {
     return done(null, padding);
   });
 });
};

It solved the problem. Now the logos of all the events were displaying properly. They were neither too wide, long or short. Here are some screenshots to show the improvements.

Facebook Developer Conference

27262455-d3558540-5474-11e7-852e-ef98888ef647.png

Open Tech Summit 2017

27262033-6f9d2df8-546c-11e7-811b-e35f849072eb.png

Resources: