Preparing a release for Phimpme Android

Most of the essential features are now in a stable state in our Phimpme Android app. So we decided to release a beta version of the app. In FOSSASIA we follow branch policy where in development all current development will take place and in master branch the stable code resides.

Releasing an app is not just building an apk and submitting to the distribution platform, certain guidelines should follow.

How I prepare a released apk for Phimpme

List down the feature

We discussed on our public channel what features are now in stable state and can be released. Features such as account manager and Share Activity is excluded because it is not complete and in under development mode. We don’t want to show and under development feature. So excluded them. And made a list of available features in different category of Camera, Gallery and Share.

Follow the branch policy.

The releasable and stable codebase should be on master branch. It is good to follow the branch policy because it helps if we encounter any problem with the released apk. We can directly go to our master branch and check there. Development branch have very volatile because of active development going on.

Every Contributor’s contribution is important

When we browse our old branches such as master in case of ours. We see generally it is behind 100s of commits to the development. In case of that when we create a PR for that then it generally contains all the old commits to make the branch up to the latest.

In this case while opening and merging do not squash the commits.

Testing from Developer’s end

Testing is very essential part in development. Before releasing it is a good practice that Developer should test the app from their end. We tested app features in different devices with varying Android OS version and screen size.

  • If there is any compatibility issue, report it right away and there are several tools in Android to fix.
  • Support variety of devices and screen sizes

Changing package name, application ID

Package name, application ID are vitals of an app. Which uniquely identifies them as app in world. Like I changed the package name of Phimpme app to org.fossasia.phimpme. Check all the permission app required.

Create Release build type

Build types are great to way categorize the apps. Debug and Release are two. There are various things in codebase which we want only in Debug modes. So when we create Release mode it neglect that part of the code.

Add build types in you application build.gradle

buildTypes {
   release {
       minifyEnabled false
   }

Rebuild app again and verify from the left tab bar

Generate Signed apk and Create keystore (.jks) file

Navigate to Build → Generate Signed APK

Fill all details and proceed further to generate the signed apk in your home directory.

Adding Signing configurations in build.gradle

Copy the keystore (.jks) file to the root of the project and add signing configurations

signingConfigs {
       config {
           keyAlias 'phimpme'
           keyPassword 'phimpme'
           storeFile file('../org.fossasia.phimpme.jks')
           storePassword 'XXXXXXX'
       }
   }

InstallRelease Gradle task

Navigate to the right sidebar of Android Studio click on Gradle


Click on installRelease to install the released apk. It take all the credentials from the signing configurations.

Resources

Continue Reading

Adding Stickers Feature on Phimpme Android App with ViewFlipper and Scrollview

For any image editing application stickers play an important role in editing an image. Phimpme is no different. There can be many different kinds of stickers. As Phimpme is an Open Source application. The user can add their own stickers to be implemented on Phimpme. But this gives a long and non-exhaustive list of stickers that the user has to go through. Sorting the stickers into different categories is one way of doing it. It provides quick access to the desired sticker a user wants to add to her or his photo. In order to achieve this, I am using ViewFlipper and Scrollview.

Categorizing the types of Stickers.

How it is done in Phimpme?

Currently, there are six different categories for stickers. Namely: Facial, Hearts, Objects, Comments, Wishes, Animals. The following code is used to store the category name:

public static final String[] stickerPath = {"stickers/type1", "stickers/type2", "stickers/type3", "stickers/type4", "stickers/type5", "stickers/type6"};
public static final String[] stickerPathName = {"Facial", "Hearts", "Objects", "Comments", "Wishes", "Animals"};

 

We need to populate the sticker fragment with the different categories. This is done by overriding onBinderHolder() function and using ImageHolder to handle onClickListener.

private ImageClick mImageClick = new ImageClick();

Getting the position of the stickerPathName and setting it in stickerFragment.

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String name = stickerPathName[position];
        imageHoler.text.setText(name);
        imageHoler.text.setTag(stickerPath[position]);
        imageHoler.text.setOnClickListener(mImageClick);
    }

 

Adding Stickers to the different categories.

Now that we have different categories, we can assign different types of stickers to specific categories.

We are using RecyclerView.Adapter<ViewHolder>, which implements onCreateViewHolder() method. onCreateViewHolder method is created every time we need a new view, hence RecyclerView comes handy in such situation rather than a normal list.

First, we need to define the xml file where each sticker will be displayed in the form of ImageView. This ImageView has specific maxHeight and maxWidth for a uniform view.

The xml file name is: view_sticker_item.xml

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/img"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:maxHeight="50dp"
    android:maxWidth="50dp"
    android:paddingLeft="5dp"
    android:paddingRight="5dp"
    android:scaleType="centerInside" />


After we set up view_sticker_item.xml, we have to inflate it.

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewtype) {
        View v = null;
        v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.view_sticker_item, parent, false);
        ImageHolder holder = new ImageHolder(v);
        return holder;
    }

 

I have saved all the images in the assets folder and distributed it among type1, type2, type3, type4, type5 and type6.

One thing is to be kept in mind that the images should be in png format. PNG format images are only pixelated on the required areas. So that it does not hamper the background of the image.

Displaying stickers

Displaying sticker are done by overriding onBindViewHolder again. It accepts two parameters: ViewHolder holder and int position. ImageHolder is used to set the image of the sticker and then handling it by onClickListner().

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String path = pathList.get(position);
        ImageLoader.getInstance().displayImage("assets://" + path,
                imageHoler.image, imageOption);
        imageHoler.image.setTag(path);
        imageHoler.image.setOnClickListener(mImageClick);
    }

 

public void addStickerImages(String folderPath) {
        pathList.clear();
        try {
            String[] files = mStirckerFragment.getActivity().getAssets()
                    .list(folderPath);
            for (String name : files) {
                pathList.add(folderPath + File.separator + name);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.notifyDataSetChanged();
    }

 

So sticker fragment on Phimpme looks like this:  

                                        

Link:

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

Resources

 

Continue Reading

Adding Twitter Integration with MVP Architecture in Phimpme Android

The Account manager layout part of Phimpme is set. Now we need to start adding different account to make it functional. We first start with twitter. Twitter functionality is integrated with the help of Twitter Kit provided by Twitter itself. We followed the steps provided on the installation guide.

Note: Before Starting this first go to apps.twitter.com and create new app, add the relevant information such as name, description, URL etc.

How twitter works in Phimpme

A dialog box appear when user selected the add account option in Account manager. Select Twitter option from it.

Twitter guides to use custom TwitterLoginButton for sign in. But as we are using a common dialog box. How to initiate login from there?

Using TwitterAuthClient

Twitter Auth Client invoke the Twitter callback and popup the login window. On authorized the correct user it goes inside the onSuccess method and start a Twitter session which helps us to get the information which we want to store in database such as username, access token.

client.authorize(getActivity(), new Callback<TwitterSession>() {
   @Override
   public void success(Result<TwitterSession> result) {

       // Creating twitter session, after user authenticate
       // in twitter popup
       TwitterSession session = TwitterCore.getInstance()
               .getSessionManager().getActiveSession();
       TwitterAuthToken authToken = session.getAuthToken();


       // Writing values in Realm database
       account.setUsername(session.getUserName());
       account.setToken(String.valueOf(session.getAuthToken()));

   }

Working with MVP architecture to show Twitter data to User in a recyclerView

Finally after successful login from Twitter, we also need to show user that you are successfully logged in Phimpme app and also provide a sign out feature so that user can logout from Twitter anytime.

Account manager has a recyclerView which takes data from the database and show it to the User.

Steps:

class AccountContract {
   internal interface View : MvpView{

       /**
        * Setting up the recyclerView. The layout manager, decorator etc.
        */
       fun setUpRecyclerView()

       /**
        * Account Presenter calls this function after taking data from Database Helper Class
        */
       fun setUpAdapter(accountDetails: RealmResults<AccountDatabase>)

       /**
        * Shows the error log
        */
       fun showError()
   }

   internal interface Presenter {

       /**
        * function to load data from database, using Database Helper class
        */
       fun loadFromDatabase()

       /**
        * setting up the recyclerView adapter from here
        */
       fun handleResults(accountDetails: RealmResults<AccountDatabase>)
   }
}

This class clearly show what functions are in View and what are in Presenter. The View interface extended to MvpView which actually holds some common functions such as onComplete()

  • Implement View interface in AccountActivity

class AccountActivity : ThemedActivity(), AccountContract.View

And perform all the action which are happening on the View such as setting up the RecyclerView

override fun setUpRecyclerView() {
   val layoutManager = LinearLayoutManager(this)
   accountsRecyclerView!!.setLayoutManager(layoutManager)
   accountsRecyclerView!!.setAdapter(accountAdapter)
}
  • Main Business Logic should not be in Activity class

That’s why using MVP we have very less number of lines of code in our Main Activity because it separate the work on different zones which help developer to easily work, maintain and other user to contribute.

So like in our case I need to update the RecyclerView adapter by taking data from database. That work should not be in activity that’s why I create a class AccountPresenter and extend this to our Presenter interface in contract class

class AccountPresenter extends BasePresenter<AccountContract.View>
       implements AccountContract.Presenter

I added the function which take care of loading data from database

@Override
public void loadFromDatabase() {
   handleResults(databaseHelper.fetchAccountDetails());
}
  • Always consider the future and keep an eye for future development

Right now I not need to do alot on Database, I just need to pick the whole data and show it on View. But I need to take case of future development in this part as well. There might be more complex operation on Database in future, then it will create complexity in the codebase, if it is not architectured well.

So, I created a DatabaseHelper class which takes care of all the database operations, the advantage of this is, anyone who have to contribute in Database part or debugging the databse need not to search for every activity and scroll lines of code, the work will be in DatabaseHelper for sure.

Added DatabaseHelper in data package

public class DatabaseHelper {

   private Realm realm;

   public DatabaseHelper(Realm realm) {
       this.realm = realm;
   }

   public RealmResults<AccountDatabase> fetchAccountDetails(){
       return realm.where(AccountDatabase.class).findAll();
   }

   public void deleteSignedOutAccount(String accountName){
       final RealmResults<AccountDatabase> deletionQueryResult =  realm.where(AccountDatabase.class)
               .equalTo("name", accountName).findAll();

       realm.executeTransaction(new Realm.Transaction() {
           @Override
           public void execute(Realm realm) {
               deletionQueryResult.deleteAllFromRealm();
           }
       });
   }
}

Flow Diagram:

Browse the Phimpme GitHub Repository for complete illustration.

Resources

  1. Twitter KIt overview : https://dev.twitter.com/twitterkit/android/overview
  2. Login with Twitter: https://dev.twitter.com/twitterkit/android/log-in-with-twitter
  3. MVP Architecture by Ribot: https://github.com/ribot/android-boilerplate

 

Continue Reading

Adding Image Editor in Phimpme Android app

Image editing is a core feature of our Phimpme Android Application. We are using this already existing components Image Editor. Currently our app flow is Gallery → Open images. Now we add an option menu in the top named “Edit” which redirect to the edit Image Activity with that image path. Perform the edit operations there and apply the changed and finally updated the album with new file.

The task is to first get the image path pass to the EditImageActivity and then save the edited image in different folder after user finish with editing.

Below are the high level architecture of how it is achieved with help of ImageEditor.

Steps to integrate Editor:

  • Get the selected Image URI

Firstly, add the permission in manifest file

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Get the local images in your internal or SD Card using Media Store content provider.

Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 

String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID,MediaStore.Images.Media.BUCKET_DISPLAY_NAME }; 

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

After that you can iterate the cursor to end and get details.

  • Create a separate folder where edited images will be saved.

Now after editing we have to place the edited image into a seperate folder with a new name. You can create folder like this:

File aviaryFolder = new File(baseDir, FOLDER_NAME);

Generating new file which will be saved as edited image

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;

}
FileUtils.getEmptyFile("edited"

     + System.currentTimeMillis() + ".png");

 

It will save new image as named edited<timeinmillis> so it will be unique.

  • StartActivityforResult with the image URI and edit activity

So firstly, when user open image and click on edit. We create an empty file using the above method. Getting the image uri and pass it into the editImageActivity. Other than Uri, also pass the output file path.

And startActivityForResult with intent and request code.

You can track the editing done by user and finally save if there is some edit done, like in Phimpme we keep a counter which increased on change of Bitmap.

Update the Album by putting the image Media Store

ContentValues values = new ContentValues(2);

String extensionName = getExtensionName(dstPath);

values.put(MediaStore.Images.Media.MIME_TYPE, "image/" + (TextUtils.isEmpty(extensionName) ? "jpeg" : extensionName));

values.put(MediaStore.Images.Media.DATA, dstPath);

context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

Please visit the whole code here: github.com/fossasia/phimpme-android

Resources

Continue Reading
Using Activities with Bottom navigation view in Phimpme Android
BottonNavigationView

Using Activities with Bottom navigation view in Phimpme Android

Can we use Bottom Navigation View with activities? In the FOSSASIA Phimpme Project we integrated two big projects such as Open Camera and Leafpic. Both are activity based projects and not developed over fragment. The bottom navigation we use will work with fragment.

In Leafpic as well as in Open Camera, there are a lot of Multi level inheritance which runs the app components. The problem faced to continue to work on activities. For theme management using base activity is necessary. The base activity extend to AppcompatActivity. The leafpic code hierarchy is very complex and depend on various activities. Better way is to shift to activities for using Bottom navigation view and  work with activity similar like fragments.

Image source: https://storage.googleapis.com/material-design/

Solution:

This is possible if we copy the bottom navigation item in each of the activity and setting the clicked state of it as per the menu item ID.

Steps to Implement (How I did in Phimpme)

  • Add a Base Activity which takes care of two thing

    • Taking the layout element and setting in the activity.
    • Set up the correct navigation menu item clicked

Function selectBottomNavigationBarItem(int itemId) will take care of what item is currently active. Called this function in onStart(), getNavigationMenuItemId() function will return the menu id and selectBottomNavigagionBarItem update the bottom navigation view.

public abstract class BaseActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener

private void updateNavigationBarState() {
   int actionId = getNavigationMenuItemId();
   selectBottomNavigationBarItem(actionId);
}

void selectBottomNavigationBarItem(int itemId) {
   Menu menu = navigationView.getMenu();
   for (int i = 0, size = menu.size(); i < size; i++) {
       MenuItem item = menu.getItem(i);
       boolean shouldBeChecked = item.getItemId() == itemId;
       if (shouldBeChecked) {
           item.setChecked(true);
           break;
       }
   }
}
  • Add two abstract functions in the BaseActivity for above task
public abstract int getContentViewId();

public abstract int getNavigationMenuItemId();
  • Extend every activity to the Base Activity

For example I created a blank Activity: AccountsActivity and extend to BaseActivity

public class AccountsActivity extends BaseActivity
  • Implement the abstract function in the every activity and set the correct layout and menu item id.

@Override
public int getContentViewId() {
 return R.layout.content_accounts;
}

@Override
public int getNavigationMenuItemId() {
   return R.id.navigation_accounts;
}
  • Remove the setContentView(R.layout.activity_main);
  • Add the onNavigationItemSelected in BaseActivity

@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
   switch (item.getItemId()) {
       case R.id.navigation_home:
    startActivity(new Intent(this, LFMainActivity.class));
           Break;
       case R.id.navigation_camera:
           startActivity(new Intent(this, CameraActivity.class));
           Break;
       case R.id.navigation_accounts:
           startActivity(new Intent(this, AccountsActivity.class));
           Break;
   }
   finish();
   return true;
}

Output:

The transition is as smooth as we use bottom navigation views with Fragment. The bottom navigation also appear on above of the activity.

Source: https://stackoverflow.com/a/42811144/2902518

Resources:

Continue Reading

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
Close Menu