Adding Pinterest Integration in Phimpme Android

After establishing Facebook and Twitter share in Phimpme, our next goal was to integrate more social networking sites. Pinterest is an ideal website for us as it is widely used among various photography enthusiasts. Our goal was to share the image on the Pinterest website without the use of any other native android application.

Note: First, we added Phimpme app in the developer section https://developers.pinterest.com/apps/. This step is crucial as it generates APP ID and it is necessary for authentication.

Adding Pinterest option in Accounts Activity in Phimpme

In accounts activity, we have added a list of accounts that we have integrated in a Recyclerview. Currently, we have integrated Facebook and Twitter. We need to add Pinterest App icon and Pinterest option in the Recyclerview.

To add Pinterest app icon I downloaded the icon from iconfinder in SVG format. Using SVG format allows icon to resize itself according to the screen sizes of the users. We saved the icon file name as ic_pinterest_black.xml

Pinterest icon SVG:

<vector android:height="24dp" android:viewportHeight="32.0"
   android:viewportWidth="32.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
   <path android:fillColor="#231F20" android:pathData="M16,0.27C7.16,0.27 0,7.43 0,16.27c0,6.55 3.94,12.18 9.58,14.65c-0.05,-1.12 -0.01,-2.46 0.28,-3.67c0.31,-1.3 2.06,-8.72 2.06,-8.72s-0.51,-1.02 -0.51,-2.53c0,-2.37 1.37,-4.14 3.08,-4.14c1.46,0 2.16,1.09 2.16,2.4c0,1.46 -0.93,3.65 -1.41,5.68c-0.4,1.7 0.85,3.08 2.53,3.08c3.03,0 5.07,-3.89 5.07,-8.51c0,-3.51 -2.36,-6.13 -6.66,-6.13c-4.85,0 -7.88,3.62 -7.88,7.66c0,1.39 0.41,2.38 1.05,3.14c0.29,0.35 0.34,0.49 0.23,0.89C9.51,20.37 9.33,21.08 9.26,21.36c-0.11,0.4 -0.44,0.55 -0.8,0.4c-2.23,-0.91 -3.28,-3.36 -3.28,-6.11c0,-4.55 3.83,-9.99 11.44,-9.99c6.11,0 10.13,4.42 10.13,9.16c0,6.28 -3.49,10.97 -8.63,10.97c-1.73,0 -3.35,-0.93 -3.91,-1.99c0,0 -0.93,3.69 -1.13,4.4c-0.34,1.23 -1,2.46 -1.61,3.43C12.9,32.04 14.43,32.27 16,32.27c8.84,0 16,-7.16 16,-16S24.84,0.27 16,0.27z"/>
</vector>

We made an array of all the accounts list in the Accounts Activity. I added Pinterest in the array list. This array list is inflated in the Accounts Activity.

public static String[] accountName = {"Facebook", "Twitter", "Drupal", "NextCloud", "Wordpress", "Pinterest"};

To add the icon the recyclerview, we have to get the resource id from the drawable folder and then set is in the recyclerview. We have done it dynamically so that we don’t have to fetch the id of every accounts icons.

Integer id = getContext().getResources().getIdentifier(context.getString(R.string.ic_) +
               (accountName[position].toLowerCase()) + "_black"
       , context.getString(R.string.drawable)
       , getContext().getPackageName());

holder.accountAvatar.setImageResource(id);

Importing Pinterest SDK in Phimpme project

To import the Pinterest SDK in the project, please download the Pinterest SDK from the link: http://assets.pinterest.com/sdk/android-pdk.tar. Go to File->import new module->Import Gradle Project. Build the project, if there is any error resolve the error and then build Gradle again.

compile project(‘:pdk’)

Add APP ID in the manifest file of the project

We need to add the APP ID that was generated while making the app the Pinterest in the manifest folder of the Phimpme Android. In the case of Phimpme, we have added the APP ID in the intent filter in the Accounts Activity. This is necessary for the interaction of the Phimpme with the Pinterest website.

<activity
   android:name=".accounts.AccountActivity"
   android:screenOrientation="portrait"
   android:theme="@style/Theme.AppCompat.NoActionBar">
   <intent-filter>
       <action android:name="android.intent.action.VIEW" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
       <data android:scheme="pdk4910600717739247160" />
   </intent-filter>
</activity>

Establishing Pinterest authentication in Accounts Activity

First, we need to import all the necessary class from the Pinterest SDK. These class includes callback functions, PDKClient which manages the interface between the Phimpme app and the Pinterest website. We need to pass the APP ID in the PDKClient function as an argument and also the view in the onConnect function in PDKClient.

pdkClient = PDKClient.configureInstance(this, getResources().getString(R.string.pinterest_app_id));
pdkClient.onConnect(this);
pdkClient.setDebugMode(true);

Before signing In we need to check if we are already signed in or not. This is how we have done in  Phimpme app. If the app is already signed In a Toast will pop up showing that it already signed In.

if (accountPresenter.checkAlreadyExist(accountName[5])) {
   Toast.makeText(this, R.string.already_signed_in,
           Toast.LENGTH_SHORT).show();
}

Authentication happens in these following steps:

Adding permissions in the List                                                                                These include the following permissions:

  • Read permission
  • Write permission
  • Read relationship
  • Write relationship
List scopes = new ArrayList<String>();
scopes.add(PDKClient.PDKCLIENT_PERMISSION_READ_PUBLIC);
scopes.add(PDKClient.PDKCLIENT_PERMISSION_WRITE_PUBLIC);
scopes.add(PDKClient.PDKCLIENT_PERMISSION_READ_RELATIONSHIPS);
scopes.add(PDKClient.PDKCLIENT_PERMISSION_WRITE_RELATIONSHIPS);

Passing the view, scope array and PDKCallback as arguments in the login function

I have passed the view, scope array list and PDKCallback as arguments in the login function in PDKClient class. This will initiate the authentication process and the user will be redirected to the Pinterest web page after the user has filled up the credentials properly and logged in to the Pinterest the user will be redirected to the Phimpme accounts page. If the user has logged In successfully onSuccess function with being called where PDKResponse will pass an argument. This response object can be used to fetch the username of the logged In user or the Pin and the BoardID of the user.

@Override
public void onSuccess(PDKResponse response) {
   Log.d(getClass().getName(), response.getData().toString());

   // Begin realm transaction
   realm.beginTransaction();

   // Creating Realm object for AccountDatabase Class
   account = realm.createObject(AccountDatabase.class,
           accountName[5]);


   // Writing values in Realm database
   account.setUsername(String.valueOf(response.getUser()));

   // Finally committing the whole data
   realm.commitTransaction();

   Toast.makeText(AccountActivity.this, R.string.success, Toast.LENGTH_SHORT).show();
}

If the user fails to log in to the account onFailure function will be called and a Toast will pop up.

@Override
public void onFailure(PDKException exception) {
   Log.e(getClass().getName(), exception.getDetailMessage());
   Toast.makeText(AccountActivity.this, R.string.fail, Toast.LENGTH_SHORT).show();
}

Inserting the credentials to the Realm Database

We have used the Realm database to store all the accounts credentials in one place. This is necessary for the maintenance and to check if the user is logged or not. AccountName array holds the list of the name which is added to the database.

// Begin realm transaction
realm.beginTransaction();

// Creating Realm object for AccountDatabase Class
account = realm.createObject(AccountDatabase.class,
       accountName[5]);


// Writing values in Realm database
account.setUsername(String.valueOf(response.getUser()));

// Finally committing the whole data
realm.commitTransaction();

Conclusion

Now, users can login into the Pinterest from the Accounts Activity. After authenticating the users, we can use the authentication to share the image from the app to the Pinterest website.

Github

Resources

Continue ReadingAdding Pinterest Integration in Phimpme Android

Adding Unit Test For Local JSON Parsing in Open Event Android App

The Open Event project uses JSON format for transferring event information like tracks, sessions, microlocations and other. The event exported in the zip format from the Open Event server also contains the data in JSON format. The Open Event Android application uses this JSON data. Before we use this data in the app, we have to parse the data to get Java objects that can be used for populating views. There is a chance that the model and the JSON format changes in future. It is necessary that the models are able to parse the JSON data and the change in the model or JSON format don’t break JSON parsing.  In this post I explain how to unit test local JSON parsing so that we can ensure that the models are able to parse the local JSON sample data successfully.

Firstly we need to access assets from the main source set into the unit test. There is no way to directly access assets from main source set. We need to first add assets in test/resources directory. If assets are present in test/resources directory then we can use it using ClassLoader in the unit test. But we can’t just copy assets from the main source set to resources directory. If there is any change in sample JSON then we need to maintain both resources and it may make the sample inconsistent. We need to make assets shared.

Add the following code in the app level build.gradle file.

android {
    ...
    sourceSets.test.resources.srcDirs += ["src/main/assets"]
}

It will add src/main/assets as a source directory for test/resources directory.So after building the project the test will have access to the assets.

Create readFile() method

Now create a method readFile(String name) which takes a filename as a parameter and returns data of the file as a string.

private String readFile(String name) throws IOException {
        String json = "";
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(name);
            int size = inputStream.available();
            byte[] buffer = new byte[size];
            inputStream.read(buffer);
            inputStream.close();
            json = new String(buffer, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return json;
}

Here the getResourceAsStream() function is used to open file as a InputStream. Then we are creating byte array object of size same as inputStream data. Using read function we are storing data of file into byte array. After this we are creating a String object using a byte array.

Create ObjectMapper object

Create and initialize an ObjectMapper object in the Test class.

private ObjectMapper objectMapper;

    @Before
    public void setUp() {
        objectMapper = OpenEventApp.getObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
}

Here setting DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES to true is very important. It will fail the test for any unrecognized fields in the sample.

Create doModelDeserialization() method

In the Open Event Android App we are using Jackson for JSON serialization and deserialization. Converting JSON data to java is called deserialization or parsing.

Now create doModelDeserialization() method which takes three parameters,

  • Class<T> type: Model Class type of the data
  • String name: Name of the JSON data file
  • boolean isList: true if JSON string contains the list of object else false

This method returns true if parsing is successful and false if there is any error in parsing.

private <T> boolean doModelDeserialization(Class<T> type, String name, boolean isList) throws IOException {
        if (isList) {
            List<T> items = objectMapper.readValue(readFile(name), objectMapper.getTypeFactory().constructCollectionType(List.class, type));
            if (items == null)
                return false;
        } else {
            T item = objectMapper.readValue(readFile(name), type);
            if (item == null)
                return false;
        }
        return true;
}

Here ObjectMapper is doing the main work of parsing data and returns parsed object using readValue() method.

Add Test

Now all the setup is done we just need to assert value returned by doModelDeserialization() method by passing appropriate parameters.

@Test
public void testLocalJsonDeserialization() throws IOException {
        assertTrue(doModelDeserialization(Event.class, "event", false));
        assertTrue(doModelDeserialization(Microlocation.class, "microlocations", true));
        assertTrue(doModelDeserialization(Sponsor.class, "sponsors", true));
        assertTrue(doModelDeserialization(Track.class, "tracks", true));
        assertTrue(doModelDeserialization(SessionType.class, "session_types", true));
        assertTrue(doModelDeserialization(Session.class, "sessions", true));
        assertTrue(doModelDeserialization(Speaker.class, "speakers", true));
}

Here only event JSON file doesn’t have a list of the objects so passing false as isList parameter for others we are passing true because its data contains a list of objects.

Conclusion:

Running unit tests after every build helps you to quickly catch and fix software regressions introduced by code changes to your app

Continue ReadingAdding Unit Test For Local JSON Parsing in Open Event Android App

Generic Social Links Implementation in Open Event Android App

The Open Event Android App has an About Fragment which displays all the info about the event like name, time, location etc. It also shows social media buttons for the event. The problem was that the implementation of showing the social media buttons was not generic. The implementation was working fine for current FOSSASIA sample. If we generate the app for other events it creates a problem. It shows static buttons without proper mapping from social media button to social media link which creates a problem like on clicking GitHub button it opens Facebook link (issue #1792).

One solution to this problem is to implement recyclerview with social media buttons. In this post I explain how I have made social links implementation generic using RecyclerView.

Add RecyclerView in layout

The first step to do is to add recyclerview in the layout xml file and to create a list item for recyclerview which holds the image for the social link button. Then in the About Fragment find recyclerview element added in the xml file using findFragmentById() method.

1. Add recyclerview in xml file

In the layout xml file of About Fragment add recyclerview element. Define id, width, height, and gravity of recyclerview. Then specify list item for recyclerview using listitem attribute.

<android.support.v7.widget.RecyclerView
            android:id="@+id/list_social_links"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:overScrollMode="never"
            android:clipToPadding="false"
            tools:listitem="@layout/item_social_link" />

2. Create item_social_link.xml

Now create a item_social_link.xml file and add a FrameLayout element. Inside the FrameLayout add an ImageView with appropriate id, width, height and padding.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_social_link_parent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/img_social_link"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:contentDescription="@string/social_link"
        android:padding="@dimen/padding_large"
        android:tint="@color/white" />
</FrameLayout>

Add and initialize RecyclerView

After adding recyclerview in xml file we need to add RecyclerView field in the About Fragment java file. Now add and initialize SocialLinksListAdapter which extends RecyclerView.Adapter and will be used for populating the recyclerview with the social links.

@BindView(R.id.list_social_links)
protected RecyclerView socialLinksRecyclerView;

private SocialLinksListAdapter socialLinksListAdapter;
private List<SocialLink> mSocialLinks = new ArrayList<>();

Here mSocialLinks is the list of social links which is fetched from the Event object.

Create ViewHolder for social link button

Now create SocialLinkViewHolder which extends RecyclerView.ViewHolder and holds one social link item defined in item_social_link.xml file. This file is where the magic happens. Add ImageView, FrameLayout, SocialLink and context fields in it.

public class SocialLinkViewHolder extends RecyclerView.ViewHolder {

    @BindView(R.id.img_social_link)
    protected ImageView imageView;

    @BindView(R.id.layout_social_link_parent)
    protected FrameLayout layout;

    private SocialLink socialLink;
    private Context context;

    public SocialLinkViewHolder(View itemView, Context context) {
        super(itemView);
        ButterKnife.bind(this, itemView);
        this.context = context;
    }

    public void bindSocialLink(@NonNull SocialLink socialLink) {...}

    private void setImageDrawable(@NonNull String name,@NonNull String link) {...}

    private Drawable getDrawable(@DrawableRes int id) {...}

    private void showView(boolean show) {...}
}

The bindSocialLink(SocialLink socialLink) method is called in the onBindViewHolder() method of the SocialLinksListAdapter. In this method initialize socialLink field and set drawable for ImageView according to SocialLink name using setImageDrawable() method.

public void bindSocialLink(@NonNull SocialLink socialLink) {
        this.socialLink = socialLink;
        setImageDrawable(socialLink.getName(), socialLink.getLink());
}

The setImageDrawable() method finds and sets appropriate image for social link button using the name of the social link. If the image for the social link is not found then it sets the visibility of the image view to GONE using showView(boolean show).

private void setImageDrawable(@NonNull String name,@NonNull String link) {

        if (Utils.isEmpty(name) || Utils.isEmpty(link) || Utils.getSocialLinkDrawableId(name) == 1) {
            showView(false);
            return;
        }
        imageView.setImageDrawable(getDrawable(Utils.getSocialLinkDrawableId(name)));
}

The showView(boolean show) method handles visibility of ImageView and ImageView’s parent FrameLayout using setVisibility() method.

private void showView(boolean show) {
        if (show) {
            imageView.setVisibility(View.VISIBLE);
            layout.setVisibility(View.VISIBLE);
        } else {
            imageView.setVisibility(View.GONE);
            layout.setVisibility(View.GONE);
        }
}

Set onClickListener to ImageView

Now it’s time to set the onClickListener in the constructor of the SocialLinkViewHolder to define what to do when user clicks on the ImageView.

imageView.setOnClickListener(view -> {
            if (socialLink != null && !Utils.isEmpty(socialLink.getLink())) {
                Utils.setUpCustomTab(context, socialLink.getLink());
            }
        });

Here we are setting custom tab for the social link using the setUpCustomTab() util method which will open the link.

Now run the app on the device or emulator. Here’s how it looks like,

Conclusion

The SocialLink impementation using the RecyclerView gives the great user experience in the application.

Additional resources

Continue ReadingGeneric Social Links Implementation in Open Event Android App

Acceptance Testing of a Feature in Open Event Frontend

In Open Event Frontend, we have integration tests for ember components which are used throughout the project. But even after those tests, user interaction could pose some errors. We perform acceptance tests to alleviate such scenarios.

Acceptance tests interact with application as the user does and ensures proper functionality of a feature or to determine whether or not the software system has met the requirement specifications. They are quite helpful for ensuring that our core features work properly.

Let us write an acceptance test for register feature in Open Event Frontend.

import { test } from 'qunit';
import moduleForAcceptance from 'open-event-frontend/tests/helpers/module-for-acceptance';

moduleForAcceptance('Acceptance | register');

In the first line we import test from ‘ember-qunit’ (default unit testing helper suite for Ember) which contains all the required test functions. For example, here we are using test function to check the rendering of our component. We can use test function multiple times to check multiple components.

Next, we import moduleForAcceptance from ‘open-event-frontend/tests/helpers/module-for-acceptance’ which deals with application setup and teardown.

test('visiting /register', function(assert) {
  visit('/register');

  andThen(function() {
    assert.equal(currentURL(), '/register');
  });
});

Inside our test function, we simulate visiting  /register route and then check for the current route to be /register.

test('visiting /register and registering with existing user', function(assert) {
  visit('/register');
  andThen(function() {
    assert.equal(currentURL(), '/register');
    fillIn('input[name=email]', 'opev_test_user@nada.email');
    fillIn('input[name=password]', 'opev_test_user');
    fillIn('input[name=password_repeat]', 'opev_test_user');
    click('button[type=submit]');
    andThen(function() {
      assert.equal(currentURL(), '/register');
      // const errorMessageDiv = findWithAssert('.ui.negative.message');
      // assert.equal(errorMessageDiv[0].textContent.trim(), 'An unexpected error occurred.');
    });
  });
});

Then we simulate visiting /register route and register a dummy user. For this, we first go to /register route and then check for the current route to be register route. We fill the register form with appropriate data and hit submit.

test('visiting /register after login', function(assert) {
  login(assert);
  andThen(function() {
    visit('/register');
    andThen(function() {
      assert.equal(currentURL(), '/');
    });
  });
});

The third test is to simulate visiting /register route with user logged in and this is very simple. We just visit /register route and then check if we are are at / route or not because a user redirects to / route when he tries to visit /register after login.

And since we checked for all the possible combinations, to run the test we simply use the following command-

ember test --server

But there is a little demerit to acceptance tests. They boot up the whole EmberJS application and start us at the application.index route. We then have to navigate to the page that contains the feature being tested. Writing acceptance tests for each and every feature would be a big waste of time and CPU cycles. For this reason, only core features are tested for acceptance.

Resources

Continue ReadingAcceptance Testing of a Feature in Open Event Frontend

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

Resource Injection Using ButterKnife in Loklak Wok Android

Loklak Wok Android being a sophisticated Android app uses a lot of views, and of those most are manipulated at runtime. In Android to play with a View or ViewGroup defined in XML at runtime requires developers to add the following line:

(TypeOfView) parentView.findViewById(R.id.id_of_view);

 

This leads to lengthy code. And very often, more than one Views respond to a particular event. For example, hiding Views if a network request fails and showing a message to the user to “Try Again!”. Let’s say you have to hide 4 Views, are you going to do the following:

view1.setVisibility(View.GONE);
view2.setVisibility(View.GONE);
view3.setVisibility(View.GONE);
view4.setVisibility(View.GONE);
textView.setVisibility(View.VISIBLE); // has "Try Again!" message.

// more 5 lines of code when hiding textView and displaying 4 other Views

 

Surely not! And the old fashioned way to get a string value defined as a resource in string.xml

String appName = getActivity().getResources().getString(R.id.app_name);

 

Surely, all this works good, but being a developer while working on a sophisticated app you would like to focus on the logic of the app, rather than scratching your head to debug whether you properly did a findViewById or not, did you typecast it to the proper View, or where did you miss to change the visibility of a view in response to an event.

Well, all of this can be easily handled by using a library which provides you the dependency, here resources. All you need to do is just declare your resources, and that’s it, the library provides the resources to you, yes you don’t need to initialize it using findViewById. So let’s dive in and see how ButterKnife is used in Loklak Wok Android to handle these issues.

Adding ButterKnife to Android Project

In the app/build.gradle:

dependencies {
    compile 'com.jakewharton:butterknife:8.6.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
    ...
}

Dealing with Views in Fragments

When views are declared, BindView annotation is used with its parameter as the ID of the view, for example, views in TweetHarvestingFragment :

@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.harvested_tweets_count)
TextView harvestedTweetsCountTextView;
@BindView(R.id.harvested_tweets_container)
RecyclerView recyclerView;
@BindView(R.id.network_error)
TextView networkErrorTextView;

NOTE: Views declared can’t be private.

Once Views are declared, then it needs to be injected, it is done using ButterKnife.bind(Object target, View Source). Here in TweetHarvestingFragment the target will be the fragment itself and source i.e. the parent view will be rootView (obtained by inflating the layout file of fragment). All this needs to be done in onCreateView method

View rootView = inflater.inflate(R.layout.fragment_tweet_harvesting, container, false);
ButterKnife.bind(this, rootView);

That’s it, we are done!

The same paradigm can be used to bind views to a ViewHolder of a RecyclerView, as implemented in HarvestTweetViewHolder:

@BindView(R.id.user_fullname)
TextView userFullname;
@BindView(R.id.username)
TextView username;
@BindView(R.id.tweet_date)
TextView tweetDate;
@BindView(R.id.harvested_tweet_text)
TextView harvestedTweetTextView;

public HarvestedTweetViewHolder(View itemView) {
   super(itemView);
   ButterKnife.bind(this, itemView);
}

 

Injecting resources like strings, dimensions, colors, drawables etc. is even easier, only the related annotation and ID needs to be provided. Example the string app_name is used in TweetHarvestingFragment to display the app name i.e. “Loklak Wok” in toolbar

@BindString(R.string.app_name)
String appName;

// directly used inside onCreateView to set the title in toolbar
toolbar.setTitlet(appName);

 

Using ButterKnife onClickListeners can be implemented in separate method, a clean way to define click events rather than polluting onCreate(in Activity) or onCreateView(in Fragment), as implemented in SuggestFragment

@OnClick(R.id.clear_image_button)
public void onClickedClearImageButton(View view) {
   tweetSearchEditText.setText("");
}

 

Multiple Views responding to a single Event

Using @BindViews annotation a list of multiple views can be created, and then a common ButterKnife.Action can be defined to act on the list of views. In TweetHarvestingFragment visibility of some views are changed if a network request fails or succeeds using this feature of ButterKnife:

// declaring List of Views
@BindViews({
       R.id.harvested_tweets_count,
       R.id.harvested__tweet_count_message,
       R.id.harvested_tweets_container})
List<View> networkViews;

// defining action for views
private final ButterKnife.Action<View> VISIBLE = (view, index) -> view.setVisibility(View.VISIBLE);
private final ButterKnife.Action<View> GONE = (view, index) -> view.setVisibility(View.GONE);

// applying action on the views
ButterKnife.apply(networkViews, VISIBLE);
ButterKnife.apply(networkViews, GONE);

Resources

Continue ReadingResource Injection Using ButterKnife in Loklak Wok Android

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

Add Autocomplete SearchView in Open Event Android App

The Open Event Android App has a map for showing all locations of sessions. All the locations have a marker in the map. It is difficult to find a particular location on the map because to know the name of location user has to click on the marker. Adding autocomplete SearchView will improve user experience by providing an ability to search the location by name and by suggesting name according to the search query. In this post I explain how to add autocomplete SearchView in the fragment or activity.

Add search icon in actionbar

The first step to do is to create a menu xml file and add a search menu item in it. Then inflate this menu xml file in Fragment in onCreateOptionsMenu() method.

1. Create menu.xml file

In this file add search menu element. Inside menu element add search menu item. Define id, title, and icon of search menu item. Add android.support.v7.widget.SearchView” as actionViewClass which will be used as action view when the user clicks on the icon.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    
   <item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_search_white_24dp"
        android:title="@string/search"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="ifRoom | collapseActionView"/>
</menu>

2. Inflate menu.xml file in Fragment

In the fragment’s onCreateOptionsMenu() method inflate menu.xml file using MenuInflater’s inflate() method. Then find search menu item using menu’s findItem() method by passing id of search menu item as parameter.

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_map, menu);
        MenuItem item = menu.findItem(R.id.action_search);
}

Add and initialize SearchView  

Now after adding search icon we need to add SearchView and SearchAutoComplete fields in the fragment.

private SearchView searchView;
private SearchView.SearchAutoComplete   mSearchAutoComplete;

Initialize SearchView in onCreateOptionMenu() method by passing search menu item in the getActionView() method of MenuItemCompat.

Here SearchAutoComplete is a child object of SearchView so initialize it using findViewById method of SearchView by passing the id as parameter.

searchView = (SearchView) MenuItemCompat.getActionView(item);
mSearchAutoComplete = (SearchView.SearchAutoComplete) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text);

Define properties of SearchAutoCompleteView

By default background of drop down menu in SearchAutoComplete is black. You can change background using setDropDownBackgroundResource() method. Here i’m making it white by providing white drawable resource.

mSearchAutoComplete.setDropDownBackgroundResource(R.drawable.background_white);
mSearchAutoComplete.setDropDownAnchor(R.id.action_search);
mSearchAutoComplete.setThreshold(0)

The setDropDownAnchor() method sets the view to which the auto-complete drop down list should anchor. The setThreshold() method specifies the minimum number of characters the user has to type in the edit box before the drop down list is shown.

Create array adapter

Now it’s time to make the ArrayAdapter object which will provide the data set (strings) which will be used to run search queries.

ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, searchItems);

Here searchItems is List of strings. Now set this adapter to the mSearchAutoComplete object using setAdapter() method.

mSearchAutoComplete.setAdapter(adapter);

Now we are all set to run the app on device or emulator. Here’s demo how it will look

Conclusion

The SearchView with an ability to give suggestions serves the great user experience in the application.

Additional resources:

Continue ReadingAdd Autocomplete SearchView in Open Event Android App

Communicate between Child and Parent Components in React JS of SUSI Web Chat

When we were developing SUSI AI web chat  some components became huge. So the team wanted to break some components into parts. Since the Login dialog-box is used in several  places we decided to make a separate component for Login Dialog-box. In this post I am discussing how we implemented the feature as a separate component and how we have changed the state of the parent component of the child component.

Login Dialog-box contains all the things of the login dialog-box component.

Child-component (Login Dialog-box component) is here:

This method executes the ‘switchDialog’ function of the parent component.

export default class LoginDialog extends React.Component {

   handleClose = () => {
      this.props.switchDialog(false);
   };

   render() {
       this.state = { open: this.props.open }
       const actions = <RaisedButton
           label="Cancel"
           backgroundColor={
               UserPreferencesStore.getTheme() === 'light' ? '#607D8B' : '#19314B'}
           labelColor="#fff"
           width='200px'
           keyboardFocused={true}
           onTouchTap={this.handleClose}
       />;

       return (
           <Dialog
               actions={actions}
               modal={false}
               open={this.props.open}
               autoScrollBodyContent={true}
               bodyStyle={bodyStyle}
               contentStyle={{ width: '35%', minWidth: '300px' }}
               onRequestClose={this.handleClose}>
               <Login {...this.props} />
           </Dialog>
       );
   }

};

In this part we validate property types that has passed from the parent component.

LoginDialog.propTypes = {
   open: PropTypes.bool,
   switchDialog: PropTypes.func
};

In render() method I have returned the element.
To open and close dialog we have to communicate with parent component. We can send an instruction as an attribute of the element and we can refer it inside the element as “props”. This is how I have sent an instruction to the child element.
Parent-component:
‘handleOpen’ function opens the dialog when user hit on the login button.

   handleOpen = () => {
       this.setState({ open: true });
   };

‘switchDialog’ function is using for change the state of parent component from child Component (Login Dialog-box component).

   switchDialog=(dialogState)=>{
       this.setState({open:dialogState});
   };

   render() {

       const styles = {
           'margin': '60px auto',
           'width': '100%',
           'padding': '20px',
           'textAlign': 'center'
       }

       return (
           <div className="signUpForm">
               <Paper zDepth={1} style={styles}>
                   <h1>Sign Up with SUSI</h1>
                   <form onSubmit={this.handleSubmit}>
                       <div>
                           <h4>If you have an Account Please Login</h4>
                           <RaisedButton
                               onTouchTap={this.handleOpen}
                               label='Login'
                               backgroundColor={
                                   UserPreferencesStore.getTheme()==='light'
                                   ? '#607D8B' : '#19314B'}
                               labelColor="#fff" />
                       </div>
                   </form>
               </Paper>

               <LoginDialog {...this.props} open={this.state.open} switchDialog={this.switchDialog} />
           </div>
       );
   };

To open and close the dialog-box we have to send the state of the parent component to child component. To close the dialog-box we have to update the parent component’s state from child component.

To change the parent component’s state we have used this in element.

switchDialog={this.switchDialog}

To send the state to the child component we used this.

open={this.state.open}

To send other properties to the element we used this.

{...this.props}

After closing the dialog-box it calls this method and it updates the state of the parent component.

handleClose = () => {
      this.props.switchDialog(false);
};

This is how we can communicate between child and parent components using react.

Resources:

Component Communication: http://andrewhfarmer.com/component-communication/
Material UI Dialogs: http://www.material-ui.com/#/components/dialog

Continue ReadingCommunicate between Child and Parent Components in React JS of SUSI Web Chat