Monthly Archives: February 2016

PlaceAutoCompleteFragment with custom UI

Hello Guys,

I have used PlaceAutocomplete the updated one (fragment), earlier it was a normal textview provided by android but now Android has changed it to fragment and we have to integrate fragment in our xml layout file.

For this we require few things :

1) Getting SHA-1 fingerprint of keystore.

2) Using Project name and SHA-1, Get an API key from Google Console and integrate in project.

3) Including latest google play services gradle (8.4.0)
1) Getting SHA-1 fingerprint of keystore (Android Debug Key):

i) Launch Android studio.
ii) Select project in “Project” mode.
iii) Select “Gradle” tab, at right hand side.
iv) Explore it -> Project Name -> Tasks -> android -> signingReport.
v) At console (run) you will get both MD5 and SHA1 .

2) Getting API Key from Google Console :

i)Go to the Google Developers Console.
ii)Select a project, or create a new one.
iii)Click Continue to enable the Google Places API for Android.
iv)On the Credentials page, create an Android key and set the API credentials.
Note: If you have an existing Android key, you may use that key.
V)In the create key dialog, restrict usage to your Android apps—enter your app’s SHA-1 fingerprint and package name.  (which we created above).
vi) Click Create , now you can find API key and we can use it by integrating in Android Manifest of our project.

<application>
  ...
  <meta-data
      android:name="com.google.android.geo.API_KEY"
      android:value="YOUR_API_KEY"/>
</application>

In Application tag in manifest.

3) Including latest google play services gradle (8.4.0): 

compile 'com.google.android.gms:play-services-location:8.4.0'

just add this in build.gradle of project.

 

Now create Activity and include this in its layout. Note that the name attribute is having path of our custom AutoPlaceComplete class.

<fragment
    android:id="@+id/autocomplete_fragment"
    android:name="com.example.CustomPlaceAutoCompleteFragment"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

 

Over here we have made our own custom place auto-complete by extending the PlaceAutoCompleteFragment, so that we can provide our own UI for AutoComplete.
CustomPlaceAutoCompleteFragment :

 

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;

import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
import com.google.android.gms.location.places.ui.PlaceAutocompleteFragment;
import com.google.android.gms.location.places.ui.PlaceSelectionListener;
import com.google.android.gms.maps.model.LatLngBounds;
import com.example.R;

/**
 * Created by sunny on 22/12/15.
 */
public class CustomPlaceAutoCompleteFragment extends PlaceAutocompleteFragment {

    private EditText editSearch;

    private View zzaRh;
    private View zzaRi;
    private EditText zzaRj;
    @Nullable
    private LatLngBounds zzaRk;
    @Nullable
    private AutocompleteFilter zzaRl;
    @Nullable
    private PlaceSelectionListener zzaRm;

    public CustomPlaceAutoCompleteFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View var4 = inflater.inflate(R.layout.layout_place_autocomplete, container, false);

        editSearch = (EditText) var4.findViewById(R.id.editWorkLocation);
        editSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                zzzG();
            }
        });

        return var4;
    }


    public void onDestroyView() {
        this.zzaRh = null;
        this.zzaRi = null;
        this.editSearch = null;
        super.onDestroyView();
    }

    public void setBoundsBias(@Nullable LatLngBounds bounds) {
        this.zzaRk = bounds;
    }

    public void setFilter(@Nullable AutocompleteFilter filter) {
        this.zzaRl = filter;
    }

    public void setText(CharSequence text) {
        this.editSearch.setText(text);
        //this.zzzF();
    }

    public void setHint(CharSequence hint) {
        this.editSearch.setHint(hint);
        this.zzaRh.setContentDescription(hint);
    }

    public void setOnPlaceSelectedListener(PlaceSelectionListener listener) {
        this.zzaRm = listener;
    }

    private void zzzF() {
        boolean var1 = !this.editSearch.getText().toString().isEmpty();
        //this.zzaRi.setVisibility(var1?0:8);
    }

    private void zzzG() {
        int var1 = -1;

        try {
            Intent var2 = (new PlaceAutocomplete.IntentBuilder(2)).setBoundsBias(this.zzaRk).setFilter(this.zzaRl).zzeq(this.editSearch.getText().toString()).zzig(1).build(this.getActivity());
            this.startActivityForResult(var2, 1);
        } catch (GooglePlayServicesRepairableException var3) {
            var1 = var3.getConnectionStatusCode();
            Log.e("Places", "Could not open autocomplete activity", var3);
        } catch (GooglePlayServicesNotAvailableException var4) {
            var1 = var4.errorCode;
            Log.e("Places", "Could not open autocomplete activity", var4);
        }

        if (var1 != -1) {
            GoogleApiAvailability var5 = GoogleApiAvailability.getInstance();
            var5.showErrorDialogFragment(this.getActivity(), var1, 2);
        }

    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 1) {
            if (resultCode == -1) {
                Place var4 = PlaceAutocomplete.getPlace(this.getActivity(), data);
                if (this.zzaRm != null) {
                    this.zzaRm.onPlaceSelected(var4);
                }

                this.setText(var4.getName().toString());
            } else if (resultCode == 2) {
                Status var5 = PlaceAutocomplete.getStatus(this.getActivity(), data);
                if (this.zzaRm != null) {
                    this.zzaRm.onError(var5);
                }
            }
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

}

 

 

As we have used custom view so we are require to provide our own layout.
In our layout I have taken EditText and one location pin image at its right side.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <EditText
        android:id="@+id/editWorkLocation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableRight="@drawable/ic_pin_blue" //Pin image
        android:editable="false"
        android:hint="@string/work_business_location"
        android:singleLine="true"
        android:textColor="@color/black"
        android:textSize="@dimen/txt_14sp" />

</LinearLayout>

 

As its been included in xml (layout), now we can call it from code side.
Write the below code in onCreate or onCreateView (if fragment is used) :

 

// Retrieve the PlaceAutocompleteFragment.
PlaceAutocompleteFragment autocompleteFragment = (PlaceAutocompleteFragment)
        getActivity().getFragmentManager().findFragmentById(R.id.autocomplete_fragment);

// Register a listener to receive callbacks when a place has been selected or an error has
// occurred.
autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
    @Override
    public void onPlaceSelected(Place place) {
        // TODO: Get info about the selected place.
        Log.i(TAG, "Place: " + place.getName());
Log.i(TAG, "Place Selected: " + place.getName() + "  " + place.getLatLng());

        double workLatitude = place.getLatLng().latitude;
        double workLongitude = place.getLatLng().longitude;

      //Over we can get the address, rating, price level,etc.

    }

    @Override
    public void onError(Status status) {
        // TODO: Handle the error.
        Log.i(TAG, "An error occurred: " + status);
    }
  });

 

Or implement PlaceSelectionListener in your Activity or fragment.

Now you can finally execute the project and on click of EditText, fragment will get open and you can get locations based on your typed string.
Also do not try to remove Google keyword from fragment (which gets open), as its been mandatory to show  “Powered by Google ” text in PlaceAutoComplete.

Check other blogs like Volley PART I, Image uploading and downloading using volley (in coming blog), etc.

Volley PART I

Hello Guys,
I have been working on different projects and got a very nice experience of using volley.
Although Retrofit is also much efficient, but as Volley provides Caching, Cancelling of request, Cancel of  multiple image request if view or UI is not available and which actually fascinates me to use Volley.

And in this blog I am going to share my code for hitting JSON request (JSON object and JSON array both),String response request, Image upload, Image download, Use of Network Image View which is also much efficient (provided by volley only) and also to have Rounded Network Image View which I have used in few screens.
And for all this I have made base (custom) classes, so that anyone can easily use it and directly integrate in their project.
Now first of all to use Volley, you need to add Volley dependency in build.gradle.

Starting from basic :

1) Make one Project, Goto File -> New Project.

2) Then go in Project Folder -> app -> build.gradle.
Add 
compile ‘com.mcxiaoke.volley:library-aar:1.0.0’ in dependencies {}.

3) Now we are creating a base class for creating volley request object.
We are making this a singleton class, as we require only a single object for requesting throughout application.
Also we are creating object “Image Loader” which is responsible of downloading images, which we are going to use further in our example.
Image Loader uses LRUCache.
And another object of “Request Queue”, which will keep all request in queue and call it based on Priority.

import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.text.TextUtils;

import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.ImageLoader;

/**
 * Created by sunny on 8/12/15.
 */
public class MyVolley {

    private static MyVolley mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;
    public static final String TAG = MyVolley.class.getSimpleName();

    private MyVolley(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
                    private final LruCache<String, Bitmap>
                            cache = new LruCache<String, Bitmap>(20);

                    @Override
                    public Bitmap getBitmap(String url) {
                        return cache.get(url);
                    }

                    @Override
                    public void putBitmap(String url, Bitmap bitmap) {
                        cache.put(url, bitmap);
                    }
                });

        //Added to make image upload and download faster!
        //mImageLoader.setBatchedResponseDelay(0);
    }

    public static synchronized MyVolley getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MyVolley(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            Cache cache = new DiskBasedCache(mCtx.getCacheDir(), 10 * 1024 * 1024);
            Network network = new BasicNetwork(new HurlStack());

// We can give Thread Pool Size
 mRequestQueue = new RequestQueue(cache, network,10);

 // Don't forget to start the volley request queue
 mRequestQueue.start();
 //mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
 }
 return mRequestQueue;
 }

 public <T> void addToRequestQueue(Request<T> req) {
 getRequestQueue().add(req);
 }

 public <T> void addToRequestQueue(Request<T> req, String tag) {
 // set the default tag if tag is empty
 req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
 getRequestQueue().add(req);
 }

 public ImageLoader getImageLoader() {
 return mImageLoader;
 }


}

Now we are creating one generic class for JSON requests (JSON array and JSON object both), please note that I have used GSON for parsing json object.
So first add latest version of Gson in build.gradle.

compile 'com.google.code.gson:gson:2.5'

GSONRequest Class :

import android.app.ProgressDialog;
import android.content.Context;

import com.android.volley.AuthFailureError;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.example.utility.dialog.ProgressBarDialog;
import com.example.utility.logs.Logcat;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Created by sunny on 8/12/15.
 */
public class GsonRequest<T> extends Request<T> {
    private final Gson gson = new Gson();
    private final Class<T> clazz;
    private Map<String, String> headers;
    private final Response.Listener<T> listener;
    private final Context ctxt;
    private final Map<String, String> params;
    public static final String TAG = GsonRequest.class.getSimpleName();

    //Adding CustomErrorListener, so that we will not be required to mention in all activities!
    private CustomErrorListener customErrorListener;
    private ProgressBarDialog progressBarDialog;

    private ProgressDialog progressDialog;

    /**
     * Make a GET request and return a parsed object from JSON.
     *
     * @param url     URL of the request to make
     * @param clazz   Relevant class object, for Gson's reflection
     * @param headers Map of request headers
     */
    public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
                       Response.Listener<T> listener, Response.ErrorListener errorListener, Map<String, String> params, Context ctxt, int methodType, boolean showDialog) {
        super(methodType, url, errorListener);
        setRetryPolicy(new DefaultRetryPolicy(VolleyUtils.DEFAULT_TIMEOUT_MS,
                VolleyUtils.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        this.clazz = clazz;
        this.headers = headers;
        this.listener = listener;
        this.params = params;
        this.ctxt = ctxt;
        customErrorListener = (CustomErrorListener) errorListener;


        if (showDialog == true) {
            showProgressDialog();
        }

    }

      public void showProgressBarDialog() {
        if (progressBarDialog == null) {
            progressBarDialog = ProgressBarDialog.getInstance(ctxt);

            if (null != customErrorListener) {
                //customErrorListener.setProgressBarDialog(progressBarDialog);
            }
        }
        progressBarDialog.showProgressDialog();
    }


    public void closeProgressDialog() {
        if (null != progressBarDialog && progressBarDialog.isShowing()) {
            progressBarDialog.dismissDialog();
        }
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        if (headers != null) {
            return headers;
        } else {
            headers = VolleyGlobal.getInstance(ctxt).getHeaders();
            Logcat.showLog(TAG, headers.values().toString());
            return headers;
        }

    }


    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        return params;
    }


    @Override
    protected void deliverResponse(T response) {
        listener.onResponse(response);
        //closeProgressDialog();
        dismissProgressDialog();
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            Logcat.showLog(TAG, json);
            T myPojo = gson.fromJson(json, clazz);
            return Response.success(myPojo, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        } catch (IOException e) {
            return Response.error(new ParseError(e));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
    }


    public void showProgressDialog() {
        progressDialog = ProgressDialog.show(ctxt, "Please wait", "Processing...", true, false);
        customErrorListener.setProgressBarDialog(progressDialog);
    }

    public void dismissProgressDialog() {
        if (null != progressDialog) {
            progressDialog.dismiss();
        }
    }

}

 

In the above class we have used constructor to pass data and initialize object, also we have have used boolean to show dialog or not.

VolleyGlobal Class :

import android.content.Context;

import com.android.volley.AuthFailureError;
import com.example.utility.Constants;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by sunny on 8/12/15.
 */
public class VolleyGlobal {

    private static VolleyGlobal mInstance;
    private static Context mCtx;
    public static final String TAG = VolleyGlobal.class.getSimpleName();
    private static HashMap<String, String> headers = new HashMap<String, String>();

    private VolleyGlobal(Context context) {
        mCtx = context;
    }


    public static synchronized VolleyGlobal getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new VolleyGlobal(context);
        }
        return mInstance;
    }

    public Map<String, String> getHeaders() throws AuthFailureError {
        //headers.put("Content-Type", "application/json");
        return headers;
    }

  }


This class will keep header parameters if required (like access token, api key or user id, etc.) any for your application.

 

I) Example of simple JSON request :

Now we will create one UI for hitting the simple JSON request. I have made one screen for changing password, which will hit a background call. This screen consists of 3 edittext that is, old password, new password and confirm password, and one save button.

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.Response;
import com.example.R;
import com.example.custom.activities.BaseAppCompactActivity;
import com.example.pojo.login.BasicPojo;
import com.example.utility.Constants;
import com.example.utility.Utils;
import com.example.volley.CustomErrorListener;
import com.example.volley.GsonRequest;
import com.example.volley.MyVolley;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by sunny on 3/12/15.
 */
public class ChangePwdActivity extends BaseAppCompactActivity implements View.OnClickListener, Response.Listener {

    private Toolbar toolbar;
    private TextView txtSave;
    private EditText editOldPwd, editNewPwd, editConfirmPwd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_change_password);
        toolbar = (Toolbar) findViewById(R.id.toolbar);

        toolbar.setTitle(R.string.change_password);

        toolbar.setNavigationIcon(R.drawable.header_icon_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        txtSave = (TextView) findViewById(R.id.txtSave);
        txtSave.setOnClickListener(this);

        editOldPwd = (EditText) findViewById(R.id.editOldPwd);
        editNewPwd = (EditText) findViewById(R.id.editNewPwd);
        editConfirmPwd = (EditText) findViewById(R.id.editConfirmPwd);

    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.txtSave) {
            callChangePwdReq();
        }
    }

    public boolean validateFields() {
        String pwd = new String(editNewPwd.getText().toString().trim());
        String confPwd = new String(editConfirmPwd.getText().toString().trim());
        if (TextUtils.isEmpty(pwd)) {
            Utils.makeShortToast(this, getString(R.string.please_enter_pwd));
            return false;
        } else if (TextUtils.isEmpty(confPwd)) {
            Utils.makeShortToast(this, getString(R.string.please_enter_confirm_pwd));
            return false;
        } else if (pwd.length() < getResources().getInteger(R.integer.password_minimum_length)) {
            Utils.makeShortToast(this, getResources().getString(R.string.please_enter_pwd_six_characters));
            return false;
        } else if (!pwd.equals(confPwd)) {
            Utils.makeShortToast(this, getString(R.string.your_pwd_confpwd_not_match));
            return false;
        }
        return true;
    }


    public void callChangePwdReq() {
        if (validateFields()) {
            String url = Constants.SERVER_URL + "user/change_password";
            String tag_chng_pwd_req = "change_pwd";


            Map<String, String> params = new HashMap<String, String>();
            params.put("old_password", Utils.checkForNullValue(editOldPwd.getText().toString().trim()));
            params.put("new_password", Utils.checkForNullValue(editNewPwd.getText().toString().trim()));

            CustomErrorListener customErrorListener = new CustomErrorListener(ChangePasswordActivity.this);
            GsonRequest gsonRequest = new GsonRequest(url, BasicPojo.class, null, this, customErrorListener, params, this, Request.Method.POST, true);
           MyVolley.getInstance(getApplicationContext()).addToRequestQueue(gsonRequest, tag_chng_pwd_req);
        }
    }

    
    @Override
    public void onResponse(Object response) {
        if (response instanceof BasicPojo) {
            BasicPojo pwdPojo = (BasicPojo) response;
//If requests gets success!
            if (Integer.valueOf(pwdPojo.getSuccess()) == Constants.REQUEST_HIT_SUCCESS) {
                Utils.makeShortToast(this, pwdPojo.getMessage());
                finish();
            } else {
//Else if we get failure from server.
                Utils.makeShortToast(this, pwdPojo.getMessage());
            }
        }
    }
}

As volley has two listeners that is, ErrorListener and Response.Listener (for Success of request).

We have made our own custom error listener so that it can be used at multiple activities and also to handle multiple types of error at one place only.

CustomErrorListener:

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkError;
import com.android.volley.NoConnectionError;
import com.android.volley.ParseError;
import com.android.volley.ServerError;
import com.android.volley.TimeoutError;
import com.android.volley.VolleyError;
import com.android.volley.Response.ErrorListener;
import com.example.pojo.login.BasicPojo;
import com.example.utility.Utils;
import com.example.utility.logs.Logcat;
import com.example.volley.VolleyGlobal;

public class CustomErrorListener implements ErrorListener {
    private Context context;
    private ProgressDialog progressBarDialog;
    private final String TAG = CustomErrorListener.class.getSimpleName();

    public CustomErrorListener(Context context) {
        this.context = context;
    }

    public void setProgressBarDialog(ProgressDialog progressBarDialog) {
        this.progressBarDialog = progressBarDialog;
    }

    public void onErrorResponse(VolleyError error) {
        if(null != this.progressBarDialog && this.progressBarDialog.isShowing()) {
            this.progressBarDialog.dismiss();
        }

        if(null == error.getMessage()) {
            Logcat.showLog(this.TAG, error.getClass().toString());
        } else {
            Logcat.showLog(this.TAG, error.getMessage());
        }

        String errorText = null;
        if(error instanceof BasicPojo) {
            errorText = error.getMessage();
        } else if(error instanceof ServerError) {
            errorText = "ServerError";
        } else if(error instanceof AuthFailureError) {
            errorText = "AuthFailureError";
        } else if(error instanceof ParseError) {
            errorText = "ParseError";
        } else if(error instanceof NoConnectionError) {
            String msg = error.getCause().getMessage();
            if(msg.equalsIgnoreCase("No Authentication challenges found")) {
                errorText = this.context.getString(2131099844);
                this.callLoginScreen();
            } else {
                errorText = this.context.getString(2131099820);
            }
        } else if(error instanceof TimeoutError) {
            errorText = this.context.getString(2131099740);
        } else if(error instanceof NetworkError) {
            errorText = this.context.getResources().getString(2131099813);
        }

        if(null != errorText) {
            Utils.makeShortToast(this.context, errorText);
        } else {
            Utils.makeShortToast(this.context, this.context.getString(2131099813));
        }

    }

}

Basic Pojo (which consists of success flag and message), It can be different in your case :

import com.android.volley.VolleyError;

public class BasicPojo extends VolleyError {
    private String message;
    private String success;

    public BasicPojo() {
    }

    public String getMessage() {
        return this.message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getSuccess() {
        return this.success;
    }

    public void setSuccess(String success) {
        this.success = success;
    }
}



Next part will consists of image uploading (with data and without data), with integrating image cropping library, and downloading of image. (Volley PART II)