Phimpme: Merging several Android Projects into One Project

To speed up our development of the version 2 of the Phimpme Android app, we decided to use some existing Open Source libraries and projects such as Open Camera app for Camera features and Leafpic for the photo gallery.

Integrating several big projects into one is a crucial step, and it is not difficult. Here are some points to ponder.

  1. Clone project separately. Build project and note down the features which you want to integrate. Explore the manifest file to know the Launcher, services and other permission app is taking.

I explore the Leafpic app manifest file. Found out its Launcher class, and changed it with the our code. Looked at the permissions app required such as Camera, access fine location, write external storage.

  1. Follow Bottom Up approach while integrating. It makes life easier. By the bottom up approach I mean create a new branch and then start deleting the code which is not required. Always work in branch so that no code lost or messed up.

Not everything is required at the moment. So I first integrate the whole leafpic app and then remove the splash screen. Also noted down the features needs to remove such as drawer, search bar etc.

  1. Remove and Commit, In a big project things are interlinked so removing one feature, actually made you remove most of the code. So my strategy is to focus on one feature and when it removed commit the code. This will help you in a reset hard your code anytime.

Like a lot of times I use reset –hard in my code, because it messed up.

  1. Licensing: One thing which is most important in using open source code is their License. Always follow what is written in their License. This is the way we can give credit to their hard work. There are various types of licenses, three famous are Apache, MIT and GNU General Public License. All have their pros and cons.

Apart of License there are some other condition as well. Like in leafpic which we are using in Phimpme has a condition to inform developers about the project in which we are using leafpic.

  1. Aware of File Duplication, sometimes many files have same name which results into file duplication. So refactor them already in the code.

I refactor the MainActivity in leafpic to LFMainActivity to not to clash with the existing. Resolve package name. This is also one of the tedious task. Android Studio already do a lot of refactoring but some part left.

  • Make sure your manifest file contains correct name.
  • The best way to refactor your package name in xml file is to ctrl+Shift+f in Ubuntu or Edit → Find → Find in path in Android Studio. Then search old package name and replace it with new. You can use alt+ j for multi cursor as well.
  • Clean and rebuild project.

Run app at each step so that you can catch the error at a point. Also use debugger for debugging.

Resources

Continue Reading

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 Reading

Use of SwipeRefreshLayout in Phimpme

In an image application, the first thing we want is to get a list of all images from the users device at the start of the application in the splash screen. But what if the number of images in the device is altered by deleting or copying any other images to the device when the application is running. This is where the swipe refresh layout helps us to obtain the real-time list of images from our device by just swiping vertically downwards.

In the Phimpme application, we have made use of the swipe refresh layout to update the image according to the latest data. In this post, I will be discussing how we achieved this in the Phimpme application with the help of some code examples.

Steps to implement the SwipeRefreshLayout:

  1. The first thing we need to do is add the support library to our project build.gradle file.
dependencies {
 compile 'com.android.support:recyclerview-v7:25.3.1'
 compile 'com.android.support:support-v4:25.3.1'
}

2. Now add the SwipeRefreshLayout in Activity.xml file where you want to implement the SwipeRefreshLayout with the recyclerView and the child view should match the parent layout. This can be done using the line of code below:

<android.support.v4.widget.SwipeRefreshLayout
   android:id="@+id/swipeRefreshLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/height_bottombar" > <android.support.v7.widget.RecyclerView
   android:id="@+id/grid_albums"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_gravity="center_horizontal"
   android:scrollbarThumbVertical="@drawable/ic_scrollbar"
   android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>

3. Now we have to implement the onRefreshListener which handles the refresh operation when a vertical swipe is performed.

Activity.java

SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.sr);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
   @Override
   public void onRefresh() {
   }

});

 

Now we can do anything in onRefresh method which we want to do when user swipes vertically.

For example, we want to refresh the recyclerView with latest images and display the content so this should be done in a background thread.

So we will use AsyncTask to update the data.

  @Override
 public void onRefresh() {
new AsyncTask().execute(); //  This will execute the AsyncTask
   }
});

AsyncTask.java

private class AsyncTask extends AsyncTask<Void, Integer, Void> {


   @Override

   protected void onPreExecute() {

       swipeRefreshLayout.setRefreshing(true);

       super.onPreExecute();

   }


   @Override

   protected Void doInBackground(Void... arg0) {

       // refresh the content of recyclerView here

       return null;

   }


   @Override

   protected void onPostExecute(Void result) {

    swipeRefreshLayout.setRefreshing(false);

   }

}

 

When the user swipes up vertically the progress bar is shown to screen and which can be done by swipeRefreshLayout.setRefreshing(true) in onPreExecute() method.

In doInBackground() method we have to load the content of RecyclerView through an adapter.

Once the data is loaded and set to recyclerView onPostExecute() method is called and now we have to dismiss the loading progress bar by  

  swipeRefreshLayout.setRefreshing(false);

This is how SwipeRefreshLayout works.

The above code helps us to reload the album content in the gallery.

For more details refer here in the Phimpme project for SwipeRefreshLayout.

https://github.com/fossasia/phimpme-android/blob/development/app/src/main/java/vn/mbm/phimp/me/leafpic/activities/LFMainActivity.java

Resources :

https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html

https://developer.android.com/training/swipe/add-swipe-interface.html

Continue Reading

Creating a basic Gallery in Phimpme Android

In the process of optimizing our Phimpme Android photo application, I had to change the implementation of the gallery in the application, as its loading time and smoothness is not satisfactory. How did I implement it?

In any gallery application, the primary necessity is having a details’ list of all available images on the device. For creating such a list, it is not necessary to iterate in all folders of the device storage for images. It is very time consuming task if we perform every time the app opens. Instead we can use database created by Android system for storing details of all available media on the device which can be accessed by any application. If we query the database with proper arguments, we get a cursor pointing to our target, using which we can form a list for our need.

Querying the mediastore for images can be implemented as shown below

Uri uri;
Cursor cursor;
int column_index;
String path = null,sortOrder;
ArrayList<String> imageList = new ArrayList<>();
uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.MediaColumns.DATA }; 
//DATA is the path to the corresponding image. We only need this for loading //image into a recyclerview

sortOrder = MediaStore.Images.ImageColumns.DATE_ADDED + “ DESC”;
//This sorts all images such that recent ones appear first

cursor = getContentResolver().query(uri, projection, null,null, sortOrder);

try {
       if (null != cursor) {
           column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
           while (cursor.moveToNext()) {
                 path = cursor.getString(column_index_data);
                 imageList.add(path);
            }
            cursor.close(); 
//imageList gets populated with paths to images by here
       }
}catch (Exception e){
       e.printStackTrace();
}

As we now have the list of paths to all images, we can proceed with displaying images using image file path. We can use RecyclerView for displaying grid of images as it is more optimized that default GridView. The whole process of displaying images can be summarized in following simple steps.

  1. Add RecyclerView widget to main layout and create a layout for gallery item.
  2. Create an adapter for populating RecyclerView.
  3. Initialize adapter with imageList and attach it to the recyclerView.

Setting Layout: 

Add the below code to the layout resource for your activity(activity_main.xml).

<android.support.v7.widget.RecyclerView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/gallery_grid”/>

Inorder to use RecyclerView in the project, the following dependency for recyclerView has to be added in the build.gradle file

compile "com.android.support:recyclerview-v7:$supportLibVer"
//change supportLibVer to its corresponding value.

 

Now create a layout for item in the gallery. For this, you can create something like below.

gallery_item.xml

<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent">
     <ImageView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:scaleType="centerCrop"
         android:id="@+id/image"/>
 </RelativeLayout>

Setting the Adapter:

Adapter populates the recyclerView with the list of resources given to it. For displaying images in RecyclerView, if we use normal method for setting image i.e imageview.setImageDrawable(xyz); the recyclerView would load extremely slow everytime we open the app. So, we can use an open-source image caching library Glide, for this purpose. It caches the images on its first load and lazy loads images into imageView giving a smooth experience. It can be used only if the following dependency is added to build.gradle.

compile 'com.github.bumptech.glide:glide:4.0.0-RC0'

Its sample implementation is also shown in the adapter code below.

OnItemClickListener() cannot be used with recyclerView directly, like gridView. So, a method for implementing such kind of listener is also shown in below implementation of adapter.

import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.ArrayList;

public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.mViewHolder> {

static ArrayList<String> galleryImageList;
private Context context;

public GalleryAdapter(ArrayList<String> imageList){
     galleryImageList = imageList;
}

public class mViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener {

     ImageView imageView;
     ItemClickListener itemClickListener;

     public mViewHolder(View view) {
         super(view);
         imageView = (ImageView)view.findViewById(R.id.image);
         view.setOnClickListener(this);
         view.setOnLongClickListener(this);
     }

     public void setClickListener(ItemClickListener itemClickListener) {
         this.itemClickListener = itemClickListener;
     }

     @Override
     public void onClick(View v) {
         itemClickListener.onClick(v, getPosition(), false);
     }

     @Override
     public boolean onLongClick(View v) {
         itemClickListener.onClick(v, getPosition(), true);
         return true;
     }
}

@Override
public GalleryAdapter.mViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     context = parent.getContext();
     View view = LayoutInflater.from(context).inflate(R.layout.gallery_item,parent,false);

//this gallery item is the one that we created above.

     DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

     RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(metrics.widthPixels/3,metrics.widthPixels/3);    //3 columns

     view.setLayoutParams(layoutParams);
     return new mViewHolder(view);
}

@Override
public void onBindViewHolder(GalleryAdapter.mViewHolder holder, final int position) {
     Glide.with(context)
             .load(Uri.fromFile(new File(galleryImageList.get(position)))
             //get path from list and covertin to URI
             .override(100,100) //final image will be this size(100x100)
             .thumbnail(0.1f)   //instead of empty placeholder, a                                              //thumbnail of 0.1th size of original image is used as //placeholder before complete loading of image
             .into(holder.imageView);
     holder.setClickListener(new ItemClickListener() {
         @Override
         public void onClick(View view,int position, boolean isLongClick) {
             if (isLongClick)
                 Toast.makeText(context, "Long Clicked " + position, Toast.LENGTH_SHORT).show();

             else
                 Toast.makeText(context, "Clicked " + position , Toast.LENGTH_SHORT).show();
         }
     });
}

@Override
public int getItemCount() {
     return (null != galleryImageList) ? galleryImageList.size() : 0;
}
}

With this, we reached the final step. 

Attaching the adapter to RecyclerView:

A grid layout manager with a required number of columns is made and attached to recyclerView.

The adapter is also initialized with the created Image List and is set to the recyclerView as shown below.

recyclerView = (RecyclerView)findViewById(R.id.gallery_grid);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this,3);
// 3 columns
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setAdapter(new GalleryAdapter(createAndGetImageList()));

createAndGetImageList() is the first method discussed in post.

Continue Reading

Sharing the Phimpme Android App using Firebase deep-linking

The share feature in the Android application is really important for the growth of your Android application and to increase the user base. In the Phimpme Android application, we have made use of the Firebase deep link to share the application as it is the most recommended way to do it. In this tutorial, I am discussing two ways to share your Android application with the help of code snippets below. The following are:

Generate the APK and share:

Sharing the APK of the application directly can be achieved very easily but it is the least recommended way to do this. To share the APK of the application via bluetooth or other applications, we can make use of the package manager to generate the apk and pass it to the share intent to get things done smoothly. The following code snippet will be used to generate the apk and to pass it on to the share intent.

PackageManager pm = getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(getPackageName(), 0);
File srcFile = new File(ai.publicSourceDir);
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("application/vnd.android.package-archive");
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(srcFile));
startActivity(Intent.createChooser(share, "APK"));

Sharing the application’s apk directly is not the most recommended way to do it as sometimes the apk generated by the PackageManager might not install properly in another device.

Using the Firebase Deep links:

The best thing about this is that it can behave differently when clicked on different devices. For example, you have Phimpme Android application installed in your mobile phone and someone else might send you the invitation link to download it again. So when you click on the deep link, rather than opening play store or any other storage from where you can download the apk, it will open up the Phimpme Application directly.

Due to these advantages, in the Phimpme Application, we have made use of this service to share the application. In this tutorial, I will discuss on how to achieve this step by step:

Step 1:

Sign in to your google account and go the Firebase console then click on the Add project button and fill in the Project name and your country and press Create Project button to proceed.

Step 2:

Click on Add firebase to your Android application button and fill in the your Android application’s package name and the SHA-1 key. You can generate this key very easily with the help of Android studio.

For this, import and open your Android application in Android studio and then press the Gradle button at the right hand side of your screen. It will open up the screen like that mentioned in the screenshot below:

Go to app>android> and double click on the signingReport and wait for the build to complete. On successful completion of the build, the SHA-1 key will be displayed on the Gradle console below.

Step 3:

Click on the register application button below to proceed. It will provide an option to download googleServices.json file but you can skip that as it is not necessary for sharing the application.

Step 4:

Click on the Dynamic links section and add create a new dynamic link. Fill in the link to locate your application and select the behaviour of how you want the link to behave on different devices. For example, in Android if the application is installed I want to directly open the application so I have made the configuration as seen from the screenshot below:

After doing this, click on the option to Create dynamic link from below and copy the generated link.

Step 5:

After generating the URL, you can simply create a share button in your android application and can share the application with the help of code snippet provided below:

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.install_phimpme) + "\n "+ getString(R.string.invitation_deep_link));
sendIntent.setType("text/plain");
startActivity(sendIntent);

Now go to your String.xml and create a new String resource with name invitation_deep_link and copy the dynamic link you generated using the steps mentioned above.

Resources:

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

https://console.firebase.google.com/

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

Continue Reading

Combining multiple images into one image in the Phimpme Android Application

The Phimpme app comes with a lot of image editing functionalities. One such fascinating feature Phimpme provides is to combine two or more multiple images into one with just a button click. To do this in the Phimpme Application, long click two or more medias and click the affix button to combine them into one from the menu options. This is how two images when affixed looks like:

 

In this post, I will talk about how to achieve this functionality in any Android application with some code snippets. Here is the step by step tutorial on how to achieve this:

Step 1:

The first step is to reduce the bitmap of the selected images to a smaller size to avoid the OutOFMemory Exception while affixing images. As by affixing the images we will be making the images smaller, we don’t need the high quality bitmaps for it. To reduce the bitmaps use the function provided below:

private Bitmap getBitmap(String path) {

       Uri uri = Uri.fromFile(new File(path));
       InputStream in = null;
       try {
       final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
       in = getContentResolver().openInputStream(uri);

       // Decode image size
       BitmapFactory.Options o = new BitmapFactory.Options();
       o.inJustDecodeBounds = true;
       BitmapFactory.decodeStream(in, null, o);
       in.close();

       int scale = 1;
           while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) >
               IMAGE_MAX_SIZE) {
                   scale++;
       }

       Bitmap bitmap = null;
       in = getContentResolver().openInputStream(uri);
       if (scale > 1) {
           scale--;
           // scale to max possible inSampleSize that still yields an image
           // larger than target
           o = new BitmapFactory.Options();
           o.inSampleSize = scale;
           bitmap = BitmapFactory.decodeStream(in, null, o);

           // resize to desired dimensions
           int height = bitmap.getHeight();
           int width = bitmap.getWidth();

           double y = Math.sqrt(IMAGE_MAX_SIZE
                   / (((double) width) / height));
           double x = (y / height) * width;

           Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x,
                   (int) y, true);
           bitmap.recycle();
           bitmap = scaledBitmap;

           System.gc();
       } else {
           bitmap = BitmapFactory.decodeStream(in);
       }
       in.close();

       Log.d(TAG, "bitmap size - width: " +bitmap.getWidth() + ", height: " +
               bitmap.getHeight());
       return bitmap;
   } catch (IOException e) {
       Log.e(TAG, e.getMessage(),e);
       return null;
   }
}

Step 2:

Create a bitmap array of the selected images using the following snippet:

for (int i = 0; i < selectedMedias.size(); i++) {
    bitmapArray.add(getBitmap(selectedMedias.get(i).getPath()));
}

Here the selectedMedias is the ArrayList<Media> files which is used to create a bitmap array of reduced pixels using the function getBitmap we created in the first step.

Step 3:

Create a union bitmap of the two images of particular width and height and then draw your bitmaps into a canvas using the Android Canvas class :

unionBitmap = Bitmap.createBitmap(getBitmapsWidth(bitmapArray), getMaxBitmapHeight(bitmapArray), Bitmap.Config.ARGB_8888);
Canvas comboImage = new Canvas(unionBitmap);

Step 4:

The last step is to combine the two images and get the affixed Canvas as the result. This can be done using the function given below which takes the Canvas and ArrayList of bitmaps as a parameter and returns a Canvas of images.

private static Canvas combineBitmap(Canvas cs, ArrayList bpmList){
       int width = bpmList.get(0).getWidth();
       cs.drawBitmap(bpmList.get(0), 0f, 0f, null);

       for (int i = 1; i < bpmList.size(); i++) {
           cs.drawBitmap(bpmList.get(i), width, 0f, null);
           width += bpmList.get(i).getWidth();
       }
       return cs;
   }

This is how we achieved the affixing images feature in our phimpme Android application. For the complete working source code. Refer to the Affix.java class in Phimpme Android project source code.

Resources:

  1. https://developer.android.com/guide/topics/graphics/2d-graphics.html
  2. https://github.com/fossasia/phimpme-android/
  3. https://github.com/HoraApps/LeafPic
  4. https://stackoverflow.com/questions/11740362/merge-two-bitmaps-in-android
Continue Reading

Implementing Travis for Testing Phimpme Android

What is Travis CI? It’s importance in Android Testing.

Travis CI is continuous integration service which is hosted online and makes it easy for developers to automate any kinds of test on their Github projects. In an open source project, there are many developers from around the world working together and the repository experiences many new commits in a single day. Their might be a chance that while making the changes, they have broken something in the application. In the first blog, I discussed how to add Espresso UI tests. After this the question arises is how to use these tests to check the stability at each push or a pull request.

How I ran Espresso Tests in Phimpme using Travis?

After adding the Espresso Tests in the application, we  need to configure .travis.yml file to run the tests on each push in the repository. This can be achieved by following the steps below:

Step 1:

In the .travis.yml file of the repository, check whether the existing configuration exists to check for gradle build of the application. Create one in case it is not their and add the configurations to do a build check. You can take help from the official site of Travis or from the .travis.yml file in the Phimpme repository.

Step 2:

Now in the before script part of the file add the following codes which will start the emulator in the Travis to run the Espresso Tests.

    Before_script:

    – echo no | android create avd force n test t android19 – –abi armeabiv7a

    emulator avd test noaudio nowindow &

     sleep 300

     adb shell input keyevent 82 &

The sleep 300 is used to wait for a specified time so that the emulator starts perfectly before running the test.

Step 3:

In the script part of the file, run the Espresso test written in the application by adding the following line of codes.

Script:

./gradlew connectedAndroidTest

After adding these, make a test commit on your repository. Login to your Travis account to check the status of the test.

For the complete code of how to implement Travis test, refer to this Pull request in the Phimpme Android repository. In this Pull request, I did not make use of the sleep 300, due to which sometimes the test was failing without any broken UI. Hence, make sure to add this line of script for better results.

Resources:

https://docs.travis-ci.com/user/languages/android/

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

Continue Reading

Testing User Interactions with Espresso

Espresso is a testing framework which provides the facility to write the tests for user interactions and unitary tests. Since the release of its version 2 it is now a part of Android Testing Support Library.

The android apps we build at FOSSASIA follow rigorous testing methods. See this simple UI test  in the Phimp.me app using espresso to check if button and bottom navigation are displayed in an activity. You can also find our other tests related to API and databases in the Open Event Android App.

In this blog we learn how to add this facility to your app and write a test for a simple app that takes the name of from the user and prints it on the other screen on button click.

Adding espresso support

  • Install android support repository if not already present. You do it by following Tools -> Android -> SDK Manager
Tools you need to download for testing
  • Add the following dependencies to your app’s build.gradle file
dependencies {
    androidTestCompile 'com.android.support.test:runner:0.5'
    androidTestCompile 'com.android.support.test:rules:0.5'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'

}
  • Specify the test instrumentation runner in default config
android {

    defaultConfig {

        // ....

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

}

Before we begin with writing our tests knowing some basic components will help in understanding the code better. Writing tests with espresso is easy as its construction is similar to English language.

The three major components are 

  • ViewActions        : Allows you to interact with views
  • ViewAssertions   : Allows you to assert the state of a view.
  • ViewMatchers     : Allows you to locate a view in the current view hierarchy.

Suppose we want to test if text is displayed in the view, we can do it by

onView(withId(R.id.textView))                              //ViewMatcher

 .perform(click())                                         //ViewAction

 .check(matches(isDisplayed()));                           //ViewAssertion

Example

Consider an app which takes a name from the user and displays it on the next screen on clicking the button.

To perform this kind of test we will write

//Locate the view with id "name" and type the text "Natalie"

onView(withId(R.id.name)).perform(typeText("Natalie"));

//Locate the view with id "next" and click on it

onView(withId(R.id.next)).perform(click());

//Locate the view with id "new_name" and check its text is equal with "Natalie"

onView(withId(R.id.new_name)).check(matches(withText("Natalie")));

You can run tests by right clicking on the class and selecting the “run test” option. If the interaction is not as expected then the message will be displayed.

Up until now unit test were in main focus but as we move towards the more complex apps where user interaction plays an essential role, UI testing becomes equally necessary.

References:

Continue Reading

Integrating an Image Editing Page in Phimpme Android

The main aim of the Phimpme is to develop image editing and sharing application as an alternative to proprietary solutions like Instagram. Any user can choose a photo from the gallery or click a picture from the camera and upload it on the various social media platform including Drupal and wordpress. As most of the image editor applications in the app store currently my team and I discussed and listed down the basic functionality of the Image editing activity. We have listed down the following features for image Editing activity:

  • Filters.
  • Stickers
  • Image tuning

Choosing the Image Editing Application

There are number of existing Open Source projects that we went through to check how they could be integrated into Phimpme. We looked into those projects which are licensed under the  MIT Licence. As per the MIT Licence the user has the liberty to modify the use the code, modify it, merge, publish it without any restrictions. Image-Editor Android is one such application which has MIT Licence. The Image-Editor Android has extensive features for manipulating and enhancing the image. The features are as follows:

  • Edit Image by drawing on it.
  • Applying stickers on the image.
  • Applying filters.
  • Crop.
  • Rotating the image.
  • Text on the image.

It is an ideal application to be implemented in our project.

The basic flow of the application

First, getting the image either by gallery or camera. The team has implemented leafPic and openCamera. Second, redirecting the image from the leafPic gallery to the Image editing activity by choosing edit option from the popup menu.

Populating the Menu in the popup menu in XML:

<menu> tag is the root node, which contains ites in the popup menu. The following code is used to populate the menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/action_edit"
          android:icon="@drawable/ic_edit"
          android:title="@string/Edit"
          android:showAsAction="ifRoom"/>
    <item android:id="@+id/action_use_as"
          android:icon="@drawable/ic_use_as"
          android:title="@string/useAs" />
</menu>

Setting up the Image Editing Activity

Image-Editor Android application contains two main sections.

  • MainActivity (To get the image).
  • imageeditlibrary(To edit the image)

We need to import imageeditlibrary module. Android studios gives easy method to import a module from any other project using GUI. This can be done as follows: File->new->import module then choosing the module from the desired application.

Things to remember after importing any module from any other project:

  • Making sure that the minSdkVersion and targetSdkVersion in the gradle of the imported module and the current working project is same. In Phimpme the minSdkVersion is 16 and tagetSdkVersion is 25, which is used as standard SDK version.
  • Importing all the classes in the used in the imageeditlibrary module before using them in the leadPic gallery.

Sending Image to Image Editing Activity

This includes three tasks:

  • Handling onclick listeners.
  • Sending the image from the leafPic Activity
  • Receiving the the image in EditImageActivity.

Handling onClick Listener:

public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
case R.id.action_edit:
// setOnclick listener here.
  }
}

Sending Image to EditImageActivity:

First we need to get the path of the image to be send. For this we need FileUtils class to handle the source address of the image. In FileUtils we use the function getEditFile().

public static File genEditFile(){
        return FileUtils.getEmptyFile("croppedImage"
                + System.currentTimeMillis() + ".png");
    }

Which calls the function getEmptyFile(String name):

public static File getEmptyFile(String name) {
        File folder = FileUtils.createFolders();
        if (folder != null) {
            if (folder.exists()) {
                File file = new File(folder, name);
                return file;
            }
        }
        return null;
    }

After getting the path of the file we need to send the path of the file to the EditImageActivity:

Uri uri = Uri.fromFile(new File(getAlbum().getCurrentMedia().getPath()));
                File outputFile = FileUtils.genEditFile();
                EditImageActivity.start(this,String.valueOf(uri),outputFile.getAbsolutePath(),ACTION_REQUEST_EDITIMAGE);

Receiving the image in EdtiImageActivity:

This is done by calling getdata() function in onCreate function.

private void getData() {
        filePath = getIntent().getStringExtra(FILE_PATH);
        saveFilePath = getIntent().getStringExtra(EXTRA_OUTPUT);
        loadImage(filePath);
    }

EditImageActivity Layout:

Conclusion

When integrating files from another activity we have to keep the API version of both the projects same. The best way to send an image to another activity is to save the image internally and then call the image path from the other activity.

Continue Reading
Close Menu